The global query/mutation error banner (handleQueryError in src/lib/query-client.ts) showed
"Something went wrong" for every failure without an expected status, and a polling query that kept
failing added one identical banner per attempt, so they stacked at the top of the window.
Banner text
Failure
Text
Line below it
No response (connection failed, reset)
No response from the server
POST /qqq/v1/table/order/query
No response (the 30 s timeout)
The server took too long to respond
the request
A failed fetch (no axios request)
No response from the server
While loading records (query key namespace, in plain words)
Unexpected status (400, 409, 422, ...)
the server's message (userFacingError, error, message of a JSON body), else Request failed (HTTP 409)
the request, plus (HTTP 409) when the text does not already say it
403
the server's message, else Permission denied (was the axios transport text when the body was not JSON)
the request (HTTP 403)
404 / 5xx
unchanged: Resource not found, Server error — retrying…, Server error — please try again
The request line is METHOD /path: no query string, and every path segment that is not a plain word
(a record id, a UUID, an encoded value) is shown as {id}. No body, header, token or query string
reaches a banner or the console.
No stacking
The banner's sonner id is its text plus the request line, so a repeat of the same failure updates
the banner on screen and a different failure gets its own. A lost connection is one cause whichever
request met it, so it has a single banner, which names the request that failed most recently.
Console
Each banner logs once per failure: console.warn('[API] Request failed:', { context, status, code, name, request, area }). Never a message, body, header or query string.
Tests
src/lib/query-client.test.ts covers: no status (axios network error, timeout, failed fetch), an
unexpected 4xx with and without a server message, 403/404/5xx, path masking, a repeat producing one
banner on a real Toaster (and a different failure a second one), one banner for a lost connection,
silence for a cancelled request, an aborted fetch, 401 and handlesOwnErrors, and the console line.
Gates
Run locally, one at a time, as .gitea/workflows/build.yaml runs them. Results are in the comment
below. Playwright e2e was not run locally; it is left to CI.
## What
The global query/mutation error banner (`handleQueryError` in `src/lib/query-client.ts`) showed
"Something went wrong" for every failure without an expected status, and a polling query that kept
failing added one identical banner per attempt, so they stacked at the top of the window.
## Banner text
| Failure | Text | Line below it |
|---|---|---|
| No response (connection failed, reset) | `No response from the server` | `POST /qqq/v1/table/order/query` |
| No response (the 30 s timeout) | `The server took too long to respond` | the request |
| A failed `fetch` (no axios request) | `No response from the server` | `While loading records` (query key namespace, in plain words) |
| Unexpected status (400, 409, 422, ...) | the server's message (`userFacingError`, `error`, `message` of a JSON body), else `Request failed (HTTP 409)` | the request, plus `(HTTP 409)` when the text does not already say it |
| 403 | the server's message, else `Permission denied` (was the axios transport text when the body was not JSON) | the request `(HTTP 403)` |
| 404 / 5xx | unchanged: `Resource not found`, `Server error — retrying…`, `Server error — please try again` | the request `(HTTP nnn)` |
| An error the application raised | that sentence | `While loading <area>` |
| A runtime error (`TypeError`, a schema error) | `The page hit an unexpected error (TypeError)` | `While loading <area>` |
| 401, axios cancel, aborted `fetch`, `meta.handlesOwnErrors` | none | |
The request line is `METHOD /path`: no query string, and every path segment that is not a plain word
(a record id, a UUID, an encoded value) is shown as `{id}`. No body, header, token or query string
reaches a banner or the console.
## No stacking
The banner's sonner `id` is its text plus the request line, so a repeat of the same failure updates
the banner on screen and a different failure gets its own. A lost connection is one cause whichever
request met it, so it has a single banner, which names the request that failed most recently.
## Console
Each banner logs once per failure: `console.warn('[API] Request failed:', { context, status, code,
name, request, area })`. Never a message, body, header or query string.
## Tests
`src/lib/query-client.test.ts` covers: no status (axios network error, timeout, failed fetch), an
unexpected 4xx with and without a server message, 403/404/5xx, path masking, a repeat producing one
banner on a real `Toaster` (and a different failure a second one), one banner for a lost connection,
silence for a cancelled request, an aborted fetch, 401 and `handlesOwnErrors`, and the console line.
## Gates
Run locally, one at a time, as `.gitea/workflows/build.yaml` runs them. Results are in the comment
below. Playwright e2e was not run locally; it is left to CI.
Draft: do not merge, tag or publish.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The global query/mutation error handler showed "Something went wrong" for
every failure without an expected status, and a polling query that kept
failing added one banner per attempt.
- No response: "No response from the server" (or "The server took too long
to respond" for a timeout), with the request below it as METHOD /path:
no query string, and ids shown as {id}.
- Unexpected status: the server's own message, else "Request failed (HTTP 409)".
- An error raised by the application shows its own sentence; a runtime error
shows only its type.
- The banner's sonner id is its text plus the request, so a repeat updates
the banner on screen. A lost connection has one banner for every request.
- 401, cancelled requests and aborted fetches stay silent. 403 falls back to
"Permission denied" instead of the axios transport text.
- Each banner logs status, axios code, error name and request to the console,
never a message, body, header or query string.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Gate results, run locally one command at a time on Windows (Node 24.22.0, pnpm 9.15.9) at 523f59cf. The head 43479246 adds one sentence to docs/releases/unreleased.md only.
Gate
Result
pnpm install --frozen-lockfile
lockfile up to date, nothing to install
pnpm typecheck
pass (exit 0)
pnpm lint
pass: 0 errors, 6 warnings, all in files this PR does not touch; license headers pass
Not run: Playwright e2e and acceptance suites, Storybook build, bundle budget (they run in the GitHub workflows, and this branch is on Gitea only), and no browser check of the banner in the running app. The description line under the banner text is the first use of a sonner description in this app; its look in light and dark mode has not been seen in a browser.
Gate results, run locally one command at a time on Windows (Node 24.22.0, pnpm 9.15.9) at `523f59cf`. The head `43479246` adds one sentence to `docs/releases/unreleased.md` only.
| Gate | Result |
|---|---|
| `pnpm install --frozen-lockfile` | lockfile up to date, nothing to install |
| `pnpm typecheck` | pass (exit 0) |
| `pnpm lint` | pass: 0 errors, 6 warnings, all in files this PR does not touch; license headers pass |
| `pnpm test` | pass: 364 files, 3835 tests passed, 1 skipped |
| `pnpm test:coverage` | pass, thresholds met: statements 93.49 %, branches 87.34 %, functions 94.59 %, lines 95.33 % |
| `pnpm build:export` | pass (exit 0) |
| Gitea `build / maven` | success on `523f59cf` (4 m 33 s) and on `43479246` |
Not run: Playwright e2e and acceptance suites, Storybook build, bundle budget (they run in the GitHub workflows, and this branch is on Gitea only), and no browser check of the banner in the running app. The description line under the banner text is the first use of a sonner `description` in this app; its look in light and dark mode has not been seen in a browser.
james.maes
marked the pull request as ready for review 2026-11-11 00:58:16 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
The global query/mutation error banner (
handleQueryErrorinsrc/lib/query-client.ts) showed"Something went wrong" for every failure without an expected status, and a polling query that kept
failing added one identical banner per attempt, so they stacked at the top of the window.
Banner text
No response from the serverPOST /qqq/v1/table/order/queryThe server took too long to respondfetch(no axios request)No response from the serverWhile loading records(query key namespace, in plain words)userFacingError,error,messageof a JSON body), elseRequest failed (HTTP 409)(HTTP 409)when the text does not already say itPermission denied(was the axios transport text when the body was not JSON)(HTTP 403)Resource not found,Server error — retrying…,Server error — please try again(HTTP nnn)While loading <area>TypeError, a schema error)The page hit an unexpected error (TypeError)While loading <area>fetch,meta.handlesOwnErrorsThe request line is
METHOD /path: no query string, and every path segment that is not a plain word(a record id, a UUID, an encoded value) is shown as
{id}. No body, header, token or query stringreaches a banner or the console.
No stacking
The banner's sonner
idis its text plus the request line, so a repeat of the same failure updatesthe banner on screen and a different failure gets its own. A lost connection is one cause whichever
request met it, so it has a single banner, which names the request that failed most recently.
Console
Each banner logs once per failure:
console.warn('[API] Request failed:', { context, status, code, name, request, area }). Never a message, body, header or query string.Tests
src/lib/query-client.test.tscovers: no status (axios network error, timeout, failed fetch), anunexpected 4xx with and without a server message, 403/404/5xx, path masking, a repeat producing one
banner on a real
Toaster(and a different failure a second one), one banner for a lost connection,silence for a cancelled request, an aborted fetch, 401 and
handlesOwnErrors, and the console line.Gates
Run locally, one at a time, as
.gitea/workflows/build.yamlruns them. Results are in the commentbelow. Playwright e2e was not run locally; it is left to CI.
Draft: do not merge, tag or publish.
🤖 Generated with Claude Code
The global query/mutation error handler showed "Something went wrong" for every failure without an expected status, and a polling query that kept failing added one banner per attempt. - No response: "No response from the server" (or "The server took too long to respond" for a timeout), with the request below it as METHOD /path: no query string, and ids shown as {id}. - Unexpected status: the server's own message, else "Request failed (HTTP 409)". - An error raised by the application shows its own sentence; a runtime error shows only its type. - The banner's sonner id is its text plus the request, so a repeat updates the banner on screen. A lost connection has one banner for every request. - 401, cancelled requests and aborted fetches stay silent. 403 falls back to "Permission denied" instead of the axios transport text. - Each banner logs status, axios code, error name and request to the console, never a message, body, header or query string. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>Gate results, run locally one command at a time on Windows (Node 24.22.0, pnpm 9.15.9) at
523f59cf. The head43479246adds one sentence todocs/releases/unreleased.mdonly.pnpm install --frozen-lockfilepnpm typecheckpnpm lintpnpm testpnpm test:coveragepnpm build:exportbuild / maven523f59cf(4 m 33 s) and on43479246Not run: Playwright e2e and acceptance suites, Storybook build, bundle budget (they run in the GitHub workflows, and this branch is on Gitea only), and no browser check of the banner in the running app. The description line under the banner text is the first use of a sonner
descriptionin this app; its look in light and dark mode has not been seen in a browser.