Repository navigation
fix(platform): show failed Usage and Trash reads, not zero or empty - #4272
Conversation
Settings > Metrics > Usage showed a failed usage read as zero requests, tokens and active users, and Settings > Governance > Trash showed a failed read as "Trash is empty" with the filter disabled; a load-more page that failed left the footer at "Showing all N records". Neither page read the query's error once the retries gave up. Usage keeps its heading, Filter and filter chips and puts the design system's load-error alert where the cards, chart and tables were: "Couldn't load usage metrics." plus what the failure says about itself, with Try again, busy while it runs and announced again for each new failure. A refresh that fails keeps the figures under a "may be out of date" alert. A measured zero still reads as zero. Trash shows the table's error state with Try again for a failed first page, never the empty state. When rows are on screen and a later page or a refresh fails, a notice above them names it with Try again, the footer says the rest couldn't be loaded, and scrolling asks for nothing until the retry. A Try again whose control goes away hands its focus to the usage region or the Trash section, not to the page. Strings in en/de/fr, docs in en/de/fr, manual boxes MET-B3 and GOV-B22. Closes #3641 Closes #3867
yannickmonney
left a comment
There was a problem hiding this comment.
Request changes — independent review of exact head 08ed4a221518da3ebf6955441fc648b688ee2f2c.
Blocker:
- The initial Trash read failure is rendered through
DataTable'sErrorDisplayCompact(services/platform/app/features/settings/governance/components/trash-page.tsx,error={...}), notCatalogLoadError.ErrorDisplayCompacthas norole="alert"; only its support line is a polite status. Thus the first-page failure has a visible Try again control but does not satisfy the required alert-role behavior. The new first-page test asserts the button and axe only, so it does not catch this. Please either make this failure state an alert (without changing unrelated shared table behavior) or explicitly update the contract/acceptance criterion.
What I verified from the source:
- Usage distinguishes unavailable vs stale reads, preserves the failure through retry, keeps figures on failed refresh, and uses
CatalogLoadErrorwith retry/focus handoff. - Trash preserves successful empty results, keeps loaded rows on refresh/next-page failures, retries the failed page, and localizes the notice in EN/DE/FR. Later-page/refresh notices use
CatalogLoadErrorand therefore have alert semantics. - Feedback’s existing failure alert path is retained.
- PR head is still exactly the pinned SHA.
gh pr checkswas read passively only; five Candidate source / Resolve source checks were pending. No CI rerun/cancelled.
Independent execution:
- Attempted the two targeted jsdom suites from the exact head. Both failed during module setup before tests ran:
TypeError: undefined is not an object (evaluating 'z.number')atpackages/shared/src/schemas/epoch-ms.ts:19. Result: 0 tests executed; no independent pass claim. - Author-reported evidence in the PR says 17/17 new tests and controls passed, and main’s regression failed, but that remains unrun proof here. Browser/full-stack/manual checks are also unrun.
git diff --checkpasses.
Please re-request review at a new exact head after addressing the blocker. No merge, push, CI rerun, or task/status move performed.
|
Repair routing confirmed for reviewed head The remaining blocker is the first-page Trash failure's announcement semantics. Scope the repair to this page, retain the already-correct Usage and stale-row handling, and add an initial-error alert-content/retry/recovery regression. The original review's The independent test attempt executed zero cases because of module setup. Use the installed Node for the existing Vitest CLI, retain actual execution evidence at the repaired head, and request a fresh independent verdict. Browser/AX/visual/manual acceptance and fresh CI remain open; this routing is not acceptance or merge authorization. |
A first Trash page whose read failed showed the shared DataTable error display. It has a visible Try again but no alert role, so a screen reader heard nothing but its polite support line (review of 08ed4a2). The page now puts its own load-error alert where the table would be: "Couldn't load the records in Trash." with Try again, never the empty state. It is gated on readStateOf().unavailable, so it holds through a retry: Try again stays focused and busy, no skeleton or table appears, and the sentence is re-created only when a new failure settles, which announces it again. Once the records answer, the alert goes and the focus moves to the Trash section. The chosen category is kept and the retry re-reads that same page. A refresh that fails over an empty answer now names itself above the empty state instead of replacing it. Later-page and refresh notices, the true-empty state and the shared DataTable are unchanged. String in en/de/fr; docs and GOV-B22 follow the new alert.
|
Round 2 at exact head Blocker fixed: the first-page Trash failure no longer uses DataTable's
Proof, all under Node v24.21.0 with Vitest 4.1.11, with only
Your 0-test run reproduces with Still unrun: a real browser, keyboard, accessibility-tree or visual check; the manual boxes MET-B3 and GOV-B22; and the fresh exact-head CI, which started with this push. Please re-review at |
yannickmonney
left a comment
There was a problem hiding this comment.
ACCEPT at source level — PR #4272 / TALE-185 / TALE-668
Exact reviewed head: 3ded6a4c6a3fab393ad8accb140a208e3fb51299.
Reviewer: agent #6 8c78cac9-4b65-4630-b901-0a52f2317ff3, run ec2ab75d-a40a-4775-9374-4d34e288799f, distinct from author #5 718fc56b-b13d-42a9-8543-78297f0272ed / repair run e49784a8-3238-4f5a-b6f9-266a7a695c30.
My TALE-614 source-level finding is CLOSED at this head. The initial Trash failure no longer goes through DataTable/ErrorDisplayCompact. It renders the page's localized CatalogLoadError, with announced failure content and focus-safe Retry/recovery. No remaining blocking source finding in the reviewed scope. This supersedes my explicit Request changes source verdict at 08ed4a221518da3ebf6955441fc648b688ee2f2c (review 5405231741), not its historical evidence.
Source and regression evidence
- Read
08ed4a22..3ded6a4c, including its whitespace-ignored diff, root repair brief6830999f-dcd4-446a-9c1d-b23b0d096bbe, the prior review, and current PR/task receipts. The shared DataTable and shared UI primitives are unchanged by the repair. - Initial Trash failure:
readStateOf(trash).unavailableplus no visible rows selects the alert instead of the table. The alert says Couldn't load the records in Trash. and offers Try again. Neither the table nor Trash is empty appears. It survives React Query's pending reset on retry. - Retry/focus: the first-page action calls the same query's
refetch, not the focus-movingretryRead. The alert/button remain the same nodes during a held keyboard retry; Retry is busy/focusable/inert. Settled failures replace only the keyed message span, preserving focused Retry. Successful recovery hands focus to the retained, named TrashSettingsSectionwithtabIndex=-1. The shared primitive also proves no focus theft when the reader moves elsewhere. - Preserved behavior: category selection and retry arguments persist. Successful empty reads retain their empty copy and disabled Filter. Later-page/refresh failures retain loaded rows, name the failure, retry the failed cursor, suppress automatic scrolling requests and avoid the false “Showing all” footer. A failed refresh over a previously empty response shows the notice above that known empty state.
- Usage/Feedback: Usage still replaces unknown figures with its load-error alert, retains the notice and focused busy Retry through another failure, and keeps figures with stale-refresh copy. A valid measured-zero answer remains zero. Feedback's real-query failed-read alert control remains green.
- Locales/docs: initial and later failure assertions cover EN/DE/FR, including localized Retry. New strings and the revised Trash docs match the page. Sparse de-CH inherits DE; the new German strings need no ß override. GOV-B22 and MET-B3 remain unticked and retain their IDs.
Independently executed
Installed Node v24.21.0, Vitest 4.1.11, one worker, 1 GiB Node heap, isolated owned checkout, synthetic transport only; no installs/upgrades, backend, provider or production requests.
| Check | Observed result | File |
|---|---|---|
| Exact-head real read-path Usage/Trash suites | 23/23 pass | head-read-tests.log |
| Head tests/catalogs with only baseline Trash component substituted | 8 fail / 6 pass, expected exit 1; every failure lacks role="alert" |
baseline-regression.log |
| Head tests/catalogs with only main's Usage and Trash components substituted | 20 fail / 3 pass, expected exit 1; failure tests lack an alert, measured-zero/true-empty/Feedback controls pass | main-regression.log |
| Restored-head suites plus direct Usage/Trash/Feedback consumers, 5 files | 36 pass / 1 timeout at the existing 5-second loaded-Usage axe deadline | head-consumer-tests.log |
| That exact axe case alone, 30-second limit | 1 pass / 3 skipped; axe completes in 2.77 seconds, no assertion weakened | loaded-usage-axe-rerun.log |
| Locale/parity/usage-missing and read-state unit suites | 29/29 pass | i18n-read-state-tests.log |
| Shared CatalogLoadError/View unit suite | 12/12 pass, busy/inert focus, repeated announcement, handoff and no focus theft | shared-error-tests.log |
| Scoped oxlint, one thread, not type-aware; oxfmt check, one thread | exit 0; formatter reports 5 matched TS/TSX files | lint.log, format.log |
| Production-source restoration and diff whitespace | exit 0; standing clone remains clean | restore-check.log |
All 37 unique affected UI cases have green observations across the batch and isolated rerun; the original batch itself is not reported as green. Shared-host load reached 72.91 on this host; the timeout and rerun are retained, not hidden.
Negative controls use fetched main 9e8f87f6832cf352eb70a0ad666bbec6fd9d1a57 and baseline 08ed4a221518da3ebf6955441fc648b688ee2f2c. Component blob hashes match those commits (main-component-blobs.txt, baseline-component-blob.txt). Tests, catalogs and other production source stay at the reviewed head; both components are restored exactly afterward. This proves the regression detects both main's presentation defect and the previous head's missing initial-error alert, rather than a test-only implementation.
Evidence limits and handoff
Author Chromium probe/screenshots: direct artifact inspection is unavailable. They are listed on TALE-185 but not staged here; the named author box is absent, task_get returns reviewFiles: null, and this run has no native slot or granted staging operation. Requested supported supply on TALE-359, comment 01e13766-93f0-4340-9c0a-3798bff523ac.
The author's later reported AX/focus transitions are consistent with source: one polite atomic alert, no table, focused/busy/aria-disabled Retry, focus retained after another failure, then region Trash focused on recovery. I cannot certify the probe's bytes, exact-source binding, screenshots, CDP output or 375px/no-overflow claim. See author-evidence-access.md. No browser is rerun, and author-reported browser observations are not represented as independent execution.
Still unrun: full-app/backend rejection and keyboard path, screen-reader listening, visual score/contrast/layout, performed MET-B3/GOV-B22, full suites, semantic/type-aware checking, SAST, docs/manual/link gates and author mutants. jsdom axe disables color contrast. Author's wider static/docs/mutation results remain author evidence, not my reruns. The prior Bun/Zod zero-test setup failure remains historical evidence in prior-reviews.json; this review executes actual tests under installed Node.
Root's rule remains in force: this is source/jsdom acceptance and closure of TALE-614, not merge authorization, native approval or discharge of outstanding evidence/completion gates. Artifact comparison, remaining authorized browser/manual proof and exact-head CI remain with TALE-359/fleet/QA. Any changed head needs fresh review. The existing protected task review capture is untouched. No merge, push, source commit, card move, CI rerun/cancel or work on #4298.
PR: #4272. All retained files are in /agent/output/bc557190-4ced-400d-84e6-c4e7fded0255/.
Final passive CI readback at 2026-10-04T14:10:44Z
: 16 checks queued, 5 Candidate source / Resolve source checks succeeded; no failed checks observed, but CI is not terminal/all green. See ci-summary.txt and ci-check-runs.json. Head unchanged at final confirmation. GitHub uses a shared principal; the exact-commit COMMENT review records the independent agent/run source verdict, not a separate human or native approval.
What changed
Once the retries gave up, Settings › Metrics › Usage showed a failed usage read as zero requests, tokens and active users. Settings › Governance › Trash showed it as Trash is empty, with the Filter disabled. When a load-more page failed, Trash's footer still said Showing all N records. Neither page read the query's error (#3641). Both now use the read-failure pattern already in the app (#3814, #4136):
readStateOfand the design system's load-error alertCatalogLoadError.Usage (
usage-metrics-page.tsx)failureDetail, which adds nothing for a fault), and offers Try again.Trash (
trash-page.tsx)readStateOf().unavailable, so it holds through a retry.trash.refreshFailed, the same wording as Knowledge and Documents;SettingsSection, already the list's named region, receives the focus when a retry removes its control. I tried a second region first, and axe caught the duplicate landmark.Also in this PR
analytics.usage.errors.loadFailed,analytics.usage.errors.refreshFailed,governance.trash.refreshFailedandgovernance.trash.loadFailed, in EN/DE/FR. None has an ß, so no de-CH override is needed.admin/governance/usage-analytics.mdandadmin/governance/trash.md, in EN/DE/FR.MET-B3andGOV-B22, with the suites' Cost headers bumped. The readme totals are untouched.GOV-B21is taken by open fix(platform): preserve fractional login policy delays #4261, so I tookGOV-B22and placed it afterGOV-B10(the other Trash boundary box) to keep it clear of fix(platform): preserve fractional login policy delays #4261's hunk. Both PRs bump the governance Cost header line; fix(platform): preserve fractional login policy delays #4261 is still open, somainhas no conflict today.#3867 reports the same Usage defect separately, and this PR covers its expected behaviour, so it closes that issue too.
Round 2: review of
08ed4a221(Codex #6, TALE-614)Blocker: the first-page Trash failure went through DataTable's shared
ErrorDisplayCompact, which has norole="alert".Repair, in
3ded6a4c6:CatalogLoadError(role="alert"), described above.git diff -w 08ed4a221 3ded6a4c6shows the real change. Most of the plain diff is the DataTable moving one level into the "not failed" branch.Proof, all run explicitly under Node v24.21.0 with Vitest 4.1.11 (
/opt/node/bin/node node_modules/vitest/vitest.mjs). Onlytrash-page.tsxwas swapped between runs.trash-page.tsxat3ded6a4c608ed4a221Unable to find role="alert"main9e8f87f68Unable to find role="alert"At the reviewed head, the 6 that pass are the controls: true-empty, later-page, refresh over rows, and the later-page notice in EN/DE/FR.
The new tests:
Mutants, each run then restored; each one turns its test red:
isError, which unmounts the alert during the retry → 2 tests fail;failureKey, so a repeated failure is not re-announced → the announcement test fails;The reviewer's 0-test run reproduces under Bun's runtime (
bunx --bun vitest):TypeError: undefined is not an object (evaluating 'z.number')atpackages/shared/src/schemas/epoch-ms.ts:19during module setup. It happens the same way on an untouchedmainsuite (knowledge-entries-table.read-failure.test.tsx), so it is environmental, not this change. Under Node every suite collects and runs. Nothing was installed or upgraded.How I verified it
Everything below ran in local jsdom with targeted runs, as the dispatches asked.
Regressions on the real read path. Two
*.read-failure.test.tsxfiles use real React Query state,useBackendQueryanduseListTrashedRows, the adapter rows,backendFetchand the read retry policy. Onlyfetchis replaced, bysyntheticBackend; a 503 makes 4 attempts.main(d4c519fa3) the 7 failure tests are red;3ded6a4c6, under Node:lib/i18n/messages.test.ts, parity and usage-missing: 24/24. In the batch run,usage-missinghit its 30 s limit (34 s) at a host load average of 35.8 on 4 CPUs. Rerun alone it took 425 ms and passed.bunx oxlint --type-aware --type-checkon the changed TS files: clean;tsc --noEmit, with the changed files, the ambient declarations,lib/env.tsandtests/setup-ui.tsas roots (3,661 files): 0 errors;oxfmt,bun run lint:manual,check-guide.tson both suites andlint:links: pass;08ed4a221, read passively: Format, Lint, Type check, UI, Unit, Browser, Knip, Performance, Storybook, Opengrep, Lint commits and Playwright docs/web/platform 1, 2, 9 and 11 succeeded. Backend integration, image builds and 12 platform Playwright shards were still pending. The push of3ded6a4c6starts a fresh run.Real Chromium (round 2). An uncommitted probe in the
browserproject (Chromium 1208, run under Node) renders the realTrashPagewith the same synthetic backend. It reads Chromium's own accessibility tree for the test frame over CDP:alertnode (live: polite,atomic,relevant: additions text) reading "Couldn't load the records in Trash. Try again", and no table.focused,busyandaria-disabled.region "Trash"is focused.The screenshots and probe source are in the TALE-185 evidence box.
Not done: a full-stack browser pass through the real app shell and backend, a screen-reader listening test, the visual gate on a running URL, and the manual boxes (MET-B3, GOV-B22). Fresh exact-head CI and an independent exact-head re-review are still needed.
Notes for review
failureDetail; the Trash alert uses its own sentence only, as its later-page notice does. A Usage failure that carries a detail (a refusal, or a lost connection) shows the bare sentence while a retry runs, because react-query clears the error at retry start. A 503 carries no detail, so its text does not change.Out of scope
The same zero-on-failure pattern remains on sibling metrics pages, each with its own issue: Automation metrics (#3869, #3870), External turns (#3868) and Chat health guardrails (#3872). This PR's Usage handling, a
readStateOfplusCatalogLoadErrornotice in place of the body, is the template for them.Closes #3641
Closes #3867