movilba
MOVILBA-389
Línea: un fallo de consulta y un resultado vacío no deben verse iguales
Done normal
cmcoder04-mba-l-ca
Create colectivo owner-dependent Línea fetch catches every API failure and sets empty options, rendering indistinguishably from successful zero rows. Invariant: fetch failure and empty result set must not render identically. Preserve literal status/body/requestId and explicit error UI; successful zero rows needs no-assigned-lines state. Diagnosis first, no edits until trace complete per coder02. src/app/vehiculos/agregar/colectivo/page.tsx loadLineas catch. DB census confirms 22 of 25 owners have v_lineas rows; 3 genuinely empty.
Activity
-
coder-mba-l-cc
-
coder03-mba-l-cc / coder
-
coder04-mba-l-ca
-
Diagnosis complete before edits: create-colectivo page.tsx loadLineas catch logs to console then sets lineaOptions=[]; a successful response with zero lineas also sets [], and the select renders the same 'Seleccionar línea...' placeholder in both cases. apiClient throws ApiError with literal status/statusText/requestId and detalle (raw body) available; existing createErrorInfo/logAndShowError can carry the server requestId to the error modal while status/body stay in log context. Proposed UI: separate loading/error/empty states, inline error with retry, successful empty-owner text, clear stale options at fetch start, cancellation guard. Checkout lock held by another lane; no edit or gate run yet.
-
Candidate UI diff at src/app/vehiculos/agregar/colectivo/page.tsx SHA256 b0eedecaa8fad3f97d2d1567ef4d84a00712904ab9120a33c62826c4cd19aa2c. Diagnosis: loadLineas catch previously set [] just like successful zero rows and select always said 'Seleccionar línea...'. New state separates loading, API failure and successful empty owner; failure shows role=alert + retry, disables submit, and preserves ApiError status/statusText/detalle (raw body) in error log context and original requestId in modal via requestIdDelServidor. Successful zero rows shows explicit no-assigned-lines role=status. Stale options clear at fetch start; cancelled fetch does not report. No coder lint/typecheck/tests per audit workflow; audit verdict pending. File is the only MOVILBA-389 diff.
-
audit pass: CODE ONLY at b0eedeca, re-hashed before/after my gates: 8 hunks of colectivo/page.tsx, the error-vs-empty state split, showError dep stability read in ErrorContext, /api/lineas denial shape, eslint+typecheck+test:source+test:tools. NO browser, NO React render, nobody clicked retry.
payload
{"scope":"CODE ONLY at b0eedeca, re-hashed before/after my gates: 8 hunks of colectivo/page.tsx, the error-vs-empty state split, showError dep stability read in ErrorContext, /api/lineas denial shape, eslint+typecheck+test:source+test:tools. NO browser, NO React render, nobody clicked retry.","outcome":"pass","evidence":"audit-mba-l-cc, 2026-10-08T23:31Z. PASS on src/app/vehiculos/agregar/colectivo/page.tsx b0eedecaa8fad3f97d2d1567ef4d84a00712904ab9120a33c62826c4cd19aa2c, HEAD 78124caf (blob 6484cf0c), 8 hunks at -U0, one concern.\n\nTHE DEFECT: on HEAD the catch did console.error + setLineaOptions([]), so a failed fetch and an entity with genuinely no lines produced the IDENTICAL screen - enabled empty dropdown, generic placeholder, no message, no log, no retry. The candidate splits them: fetch threw -> select disabled, role=alert, retry button bumping lineaRetry; 0 rows -> select enabled, \"no tiene lineas asignadas\", role=status. Placeholder chain :531-539 orders no-entity/loading/error/empty/normal, every branch reachable. Classification is three-way (connection, API, unexpected) through createErrorInfo with endpoint, requestId and, for an API error, status/statusText/detalle, then logAndShowError - the failure now reaches the server log AND the user, where HEAD only had console.error. Same helpers the sibling catch at :204-218 uses.\n\nTHREE HAZARDS MEASURED, NOT ASSUMED. (1) showError in the dep array at :299 would storm requests if unstable; it is stable - showError is useCallback(...,[show]) at ErrorContext.tsx:78-80, show is useCallback(...,[]) at :47. Read the provider; did not infer it from \"it is a context value\". (2) A stale lineaError cannot strand the form: reset at :242 and :232, and it carries its own entidadId, compared against formData.entidadId at :528/:535/:547. (3) The authz-looks-empty version is absent one layer down: /api/lineas/route.ts:24 returns UNAUTHORIZED and :31 INSUFFICIENT_PERMISSIONS as errors, so a denial throws into the error branch and \"no lines assigned\" cannot be a lie.\n\nGATES, run by me under hub lock gates-checkout-movilba-web: eslint 0 on the path, typecheck 0 whole-tree, test:source 68/68, test:tools 118/118. All six pinned files byte-identical before and after. Three non-blocking findings in the audit-finding note.","reportRef":null} -
# MOVILBA-389 — audit verdict: PASS on the one file. audit-mba-l-cc, 2026-10-08 src/app/vehiculos/agregar/colectivo/page.tsx b0eedecaa8fad3f97d2d1567ef4d84a00712904ab9120a33c62826c4cd19aa2c Re-hashed before and after my runs. HEAD for the comparison is `78124caf`, where the file is `6484cf0c` per git's blob index. Eight hunks at `-U0`, all in one concern. ## 1. The defect and the fix, stated as the two states that used to collide On HEAD the `catch` did `console.error` plus `setLineaOptions([])`, so **a failed fetch and an entity with genuinely no lines produced the identical screen**: an enabled, empty dropdown with the generic placeholder, no message, no log, no way to retry. The user could not tell "this entity has no lines" from "the request died", and the only recovery was reselecting the entity. The candidate splits them: | state | dropdown | message | recovery | |---|---|---|---| | fetch threw | disabled, `No se pudieron cargar las líneas` | `role="alert"` with the classified user message | `Reintentar carga de líneas` button, bumps `lineaRetry` | | fetch returned 0 rows | enabled, `La entidad no tiene líneas asignadas` | `role="status"` paragraph | n/a, it is a fact | The error is classified three ways — connection, API (`error.message`), unexpected — and `createErrorInfo` carries `endpoint`, `requestId` from `requestIdDelServidor(error)`, and for an API error the `status`, `statusText` and `detalle`, then goes through `logAndShowError`. That replaces a bare `console.error`, so the failure now reaches the server log and the user at once. The placeholder chain at `:531-539` orders no-entity -> loading -> error -> empty -> normal, and every branch is reachable. All five helpers were already imported at `:10` and `:14` and are used by the sibling catch at `:204-218`, so this follows the file's established pattern rather than inventing one. ## 2. The two hazards I checked rather than assumed **`showError` in the dependency array is safe.** Adding it at `:299` would re-run the fetch on every render if the reference were unstable, which on an error path is a request storm. It is stable: `showError` is `useCallback(..., [show])` at `src/contexts/ErrorContext.tsx:78-80` and `show` is `useCallback(..., [])` at `:47`, an empty dependency list, so both are fixed for the provider's lifetime. Measured by reading the provider, not inferred from the fact that it is a context value. **A stale `lineaError` cannot strand the form.** The error is reset before every fetch — `:242` inside `loadLineas`, and `:232` on the no-entity path — so switching entity or pressing retry clears it, and a successful load leaves it null. `lineaError` stores the `entidadId` it belongs to, and the select's `disabled`, its placeholder and the alert all compare that against the current `formData.entidadId`, so an error for a previous entity cannot disable the new one's dropdown. **The authz-looks-empty version of the same bug is not present one layer down.** If `/api/lineas` answered 200 with an empty array for a denial, the new "no lines assigned" message would be a lie. It does not: `src/app/api/lineas/route.ts:24` returns `UNAUTHORIZED` and `:31` `INSUFFICIENT_PERMISSIONS` as error responses, which reach the client as throws and land in the error branch, not the empty one. ## 3. Findings — none blocks the PASS 1. **The fix ships with no automated control, although this repo has a working pattern for exactly this.** `src/components/HistoryPeriodContent.test.tsx` asserts rendered output through `renderToStaticMarkup` against a pure `renderHistoryPeriodContent` export, and it runs on both ship paths via `test:source`. The thing 389 changed — which placeholder, which message and whether the select is disabled, given `(entidadId, lineaLoading, lineaError, lineaOptions.length)` — is a pure function of four values and could be extracted and pinned the same way. Without it, nothing stops a later edit from collapsing the two states again, and the collapse is invisible until a user hits a failing entity. **Proportionate, and the precedent is in-repo.** 2. **Two different predicates for one state.** The select, the placeholder and the alert use the entity-scoped `lineaError?.entidadId === formData.entidadId` (`:528`, `:535`, `:547`), while the submit button at `:1152` uses an unscoped `Boolean(lineaError)` and the empty-state paragraph at `:558` an unscoped `!lineaError`. They agree in steady state only because of the reset in §2. For the single render between selecting a new entity and the effect running, the submit button is disabled by the previous entity's error while the dropdown is already enabled. Sub-perceptual and fail-safe in direction, but it is two rules for one fact; one of them should be the rule. 3. **Stale options for one render frame, inherited not introduced.** `setLineaOptions([])` lives inside `loadLineas` (`:241`), so between the entity change and the effect the select still lists the previous entity's lines. `handleInputChange` resets `lineaId`, and the effect clears the options, so nothing survives. Pre-existing on HEAD; recorded because the diff touches the same lines. ## 4. NOT EXERCISED - **No browser, no React runtime, no rendered output.** I did not render this component, drive a failing fetch, or click the retry button. The verdict is a code reading of the two state machines plus the measured stability of `showError`; the user-visible behavior of the alert, the status paragraph and the retry button is **unverified by execution**, mine or anyone's. - The retry path's correctness beyond the dependency wiring is unexercised: `lineaRetry` is in the dependency array at `:299`, which is what makes the button refetch, but no run confirms it. - `/api/lineas` itself is out of scope and unexercised. In particular a non-numeric `entidadId` becomes `NaN` through `parseInt` at `route.ts:47`; unreachable from this form, whose entity select only offers real ids, and untouched by this diff. - The paging loop at `:248-257` is unchanged and unmeasured; a mid-page failure now surfaces as an error with the already-collected rows discarded, which is the intended behavior but was not run.
-
Release readback from coder03 2026-10-08 20:48 ART: DEV commit 7b8b1671 and PROD merge 3a5564f0 both serve patch 214 on their own health probes. Pod push to develop eb9ffe9 returned EventListener 202 (accepted only); pod still served serviceId 30092 = patch 213 at that read. Do not call the pod leg complete until a later health read shows serviceId 30229 = patch 214. Audit CODE PASS on page.tsx b0eedeca is already posted; browser click/retry remains UNEXERCISED.
-
CODE audit PASS and patch 214 live on DEV/PROD; pod still served serviceId 30092 = patch 213 ten minutes after EventListener 202. The pod leg is pending build diagnosis or completion; coder03 says an oc token from Elazar is needed to distinguish slow build from zombie. No UI/code work remains with coder04. Recheck pod health once after pipeline action; expected serviceId 30229, then close.
-
Audit CODE PASS on page.tsx b0eedecaa8fad3f97d2d1567ef4d84a00712904ab9120a33c62826c4cd19aa2c; shipped in patch214 on DEV (7b8b1671) and PROD (3a5564f0). The pod subsequently served patch214 on its own health read, closing the previously blocked pod leg; newer 217 builds do not undo that observed 214 deployment. Browser click/retry remains UNEXERCISED and was not claimed as a runtime acceptance control.
-
cleared: CODE audit PASS and patch 214 live on DEV/PROD; pod still served serviceId 30092 = patch 213 ten minutes after EventListener 202. The pod leg is pending build diagnosis or completion; coder03 says an oc token from Elazar is needed to distinguish slow build from zombie. No UI/code work remains with coder04. Recheck pod health once after pipeline action; expected serviceId 30229, then close.
bug
5w ago by wi-cli-venus
1w ago
2026-10-09 00:01