Frontend data layer: silent update failures + dead/unreachable mutation error strings #63
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: High. From a frontend deep audit, 2026-06-08. Two issues in the TanStack Query mutation layer that silently lose user-facing error signal.
Problems
useUpdateTerm(web/src/api/queries.ts:444) anduseUpdateAuthority(:521) lackmeta.suppressErrorToastthat every sibling mutation has — and their consumersterm-row.tsx/authority-row.tsxnever readupdate.isError. Result: depending on the path, a failed update produces no inline alert and/or no toast. The create forms render anisErroralert; the update/inline-edit rows do not.throw new Error("update failed" | "create failed" | "rename failed" | …)(queries.ts:184,203,279,331,382,441,475,518,562), butmutationErrorMessage(query-client.ts:11-23) only inspectsInUseError,HttpError503, andmeta.errorMessage— everything else collapses tot("toast.error"). So those bespoke strings never reach the UI, and a 403/500/422 on an update is indistinguishable in the toast. The 422 field-rejection that flows asFieldRejectionon the create→setFieldspath is not surfaced onuseUpdateObject(:203).object-edit-formmislabels fetch errors as "not found".objects/object-edit-form.tsx:17-24destructures only{ data, isLoading }; on a failed fetch (network/500, not 404) it falls through to the not-found branch. Compareobject-detail.tsx:34-46which handlesisLoading → isError → !object.Suggested fixes
suppressErrorToastand add an inlineisErroralert in the row (matching the create-form pattern), or dropsuppressErrorToastso the global toast fires.throw new HttpError(response.status)(or attach status) somutationErrorMessagecan branch; or delete the dead strings to remove the false signal. Consider surfacingFieldRejectiononuseUpdateObject's 422 like the create path.isErrorbranch toobject-edit-form.Source: frontend deep audit (data-layer dimension), 2026-06-08.
Fixed in merge
56076c4.Grounding corrected the framing: the update mutations weren't silent —
useUpdateTerm/useUpdateAuthoritywere the only 2 of 18 mutations withoutsuppressErrorToast, so they fired a disconnected global toast while their create/delete siblings showed inline errors. The fix makes all surfaces consistent and status-aware:api/error-message.tserrorMessageKey(error)mapsInUseError(→ count) andHttpErrorby status (403 forbidden / 404 notFound / 409 conflict / 422 validation / ≥500 server) → i18n keys, fallbacktoast.error. Used by the global toast (query-client.ts) and every inline site.<MutationError error>component renders the inline alert (or nothing) and replaces the duplicatedform.rejectedmarkup at the create/rename forms, the term/authority edit rows, and (via the helper) the delete dialog + object-formformError.HttpError(status)(16 sites) so the status reaches the mapping;InUseError/FieldRejectionbranches preserved.useUpdateTerm/useUpdateAuthoritynow suppress the toast and show an inline message at the row, staying editable on failure, withmutation.reset()clearing stale errors on re-edit.object-edit-formdistinguishes a fetch error (objects.loadError) from "not found" (isErrorguard before the!objectguard).5 new
errors.*i18n keys (en/sv parity). 247 tests pass; typecheck/lint/build clean; check:size 216.2 KB gz; check:colors clean; no new dependency; no codename.Out of scope → follow-ups: structured field-level rejection on core-object 422 (uncertain backend shape); relocating the error classes to
api/errors.ts(tracked in #65);delete-object-dialog.tsx/publish-control.tsxkeep their ownform.rejected/visibility-error handling by design.