feat(app-shell,i18n): render the environment admin's read-rate report from the usage endpoint's readRate reading - #10052
Conversation
The tenant runtime's `GET /api/v1/usage/storage` gained an optional nested `readRate` reading (state, readsPerWrite, ratioThreshold). The data half landed on the cloud side and nothing here consumed it, so a measured anomaly reached nobody. This is the rendering half. `useReadRateReading` reads the endpoint and keeps the three silent answers apart: an absent `readRate` is `unmeasured` (the control plane reported no reading), a measured `ok` is `measured`, and an unreadable endpoint is `unavailable`. All three render nothing; none of them is the others. `ReadRateBanner` renders only on the control plane's `anomalous` verdict, which it reads and never re-derives. An absent `readsPerWrite` means the environment made no writes at all, so the ratio is unbounded — it gets its own title, its own sentence and the heavier tone rather than a dash or a hidden banner. The line comes from `ratioThreshold` on the wire; this repo holds no copy of it. It is a report: no gate, no throttle, no CTA, and the copy says so. Mounted in `ConsoleShell` beside `ImpersonationBanner` so `/home` carries it too, and shown only to a workspace admin, who is also the only session that issues the request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
…its copy Measured under ablation: hiding the banner when `readsPerWrite` is absent left this case green, because an empty render is trivially "different from the ratio copy". The presence assertion makes the comparison mean what its name claims. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
AGENTS.md #2. Sits beside the Notifications section on purpose: both surfaces are "banners" and only one of them is raised through the notification system. The table states what renders for each reading, and names the three silent answers as three different facts rather than one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…er mock `check:vi-mock-inherit` refuses a hand-listed factory: it freezes the mock to the exports written that day, so the next export any module in this file's import graph reads at module scope kills the file during COLLECTION — zero failed assertions, reading green. Why my own pre-push run of that gate said exit 0: it walks `git ls-files`, and the file was still UNTRACKED when I ran it. Measured just now on this head, in one command: an untracked copy carrying the identical violation is reported 0 times while the tracked offender is reported once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
Two pull requests landed on the same ten locale packs while this branch was in review. Taking the merge rather than a rebase, per AGENTS.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Fixes #9954
Clause-②: no
The tenant runtime's
GET /api/v1/usage/storagegained one optional nestedreadRatekey. The data half landed on the cloud side (PR 2376 in that repo, merged 2026-09-18); nothing here consumed it, so a measured anomaly reached nobody. This is the rendering half, on the maintainer's ruling batch #164 item 3.Falsification first, before any code
Triage's instruction was to re-run the
usagescan and stop if a storage banner had landed since. Re-run on this branch's basee86445f57:usage/storageunderpackages+appsusagereadRate/readsPerWrite/ratioThresholdanywhereuseAiUsage/usageURLuseAiUsage.tsin app-shell, which builds it fromapiBase— the AI quota meter, not storageBoth zeros are lit beside a same-subject control in the same command family, so neither is a misfire. No storage banner exists. The scope stands as triage re-stated it: build the reading, build the banner, land the three contract behaviours.
What landed
packages/app-shell/src/hooks/useReadRateReading.ts— reads the endpoint, parses only the one key this repo has a contract for, and exposes a status.packages/app-shell/src/layout/ReadRateBanner.tsx— the surface.ConsoleShellbesideImpersonationBanner.console.readRate.*(four keys) in all ten locale packs.The three contract properties, and the ablation that proves each pin can fail
Every case below was measured from the committed tree: mutate, prove the write landed on disk (anchor count 1 to 0, injected 0 to 1), run, restore with
git checkout HEAD -- PATH, and confirmgit diff HEADempty with the blob hash equal to the HEAD blob.(1) An absent
readRateis not "fine". The hook reportsunmeasured; a measured verdict ofokismeasured; an unreadable endpoint isunavailable. Three values, three facts. All three render nothing, andclassifyReadRatekeeps them apart.unavailableis deliberately not folded intounmeasured: that status is a positive claim about what the control plane said, and a failed read gives no basis to make it.unmeasuredasok: 2 tests red, restored clean.(2) An absent
readsPerWriteis the worst case. It means the environment made no writes at all, so the ratio has no upper bound. Its own title, its own sentence, the heavier tone. The parser keeps the key absent rather than defaulting it, and accepts both spellings of an omitted optional (missing and JSON null) — reading null as off-contract would hide the most severe reading there is, which is the one thing the card forbids.(3) The threshold is data. Rendered from
ratioThresholdon the wire; the verdict is read, never re-derived.And it stays a report. No gate, no throttle, no upgrade call to action; a test asserts there is no button and no link in the banner, that the copy says nothing is limited or blocked, and that it carries none of the refusal vocabulary.
Design readings this repo owns
The card said the shape is this repo's call and prescribed none of it. What I chose, and why:
ConsoleShell, notConsoleLayout, not the notification host.ConsoleLayoutwraps only/apps/*, so an admin sitting on/homewould see nothing — which is the reasoningImpersonationBanneralready records for itself.ConsoleNotificationBannersrenders notifications RAISED through the spec notification system; this reading is neither a notification nor raised by anything in this app.useWorkspaceAdminStatus. The card names the environment's own admin. Gating the hook rather than only the render also keeps the request off every ordinary session instead of spending a 403 per page load.createAuthenticatedFetch, not barefetch. The console authenticates to/api/v1/*with a Bearer token in localStorage and has no session cookie — the fact the MetadataClient auth ratchet in this package exists to protect. It is built inside the effect, so no memoised identity is ever an effect dependency (AGENTS.md [WIP] Enhance every detail of the designer #10).refetch.defaultValueto theenpack value and full parity across ten packs, so this touchedpackages/i18nas well aspackages/app-shell. That is wider than the dispatched file surface and is called out here rather than done quietly.Verification
All commands from the repo root, exit codes captured before any pipe. Heavy runs went through the shared verify lock in the sibling checkout.
pnpm exec vitest run packages/app-shell/pnpm exec vitest run packages/i18n/pnpm --filter @object-ui/app-shell --filter @object-ui/i18n run type-checktsconfig.test.jsonpnpm --filter @object-ui/app-shell --filter @object-ui/i18n run lintpnpm check:i18n-keyspnpm check:i18n-drift·check:i18n-dead-keys·check:i18n-designer-paritypnpm check:eager-locale-cataloguesenresidentpnpm check:eager-closurepnpm check:doc-snippetspnpm check:readme-exportspnpm check:new-line-citationspnpm check:control-bytes·check:changeset-claims·check:test-path-roots·check:esm-specifiers·check:self-import·check:unreferenced-sources·check:component-surface-parity·check:side-effects-array·check:entry-guard·check:doc-fences·check:dist-completeness· the threevi-mockgatesnode scripts/check-changeset-presence.mjsnode scripts/check-governed-queue-guard.mjs --testover the diffAGENTS.mddoes refuseThe doc gates were re-run after the README commit; the suites, type-check and lint ran before it, and the only test that reads that README was re-run after it (7 passed). The measurements above were taken at
41710d21a, this branch's head.Not measured locally, said plainly rather than implied: the repo-wide
pnpm lintandpnpm test, and CI's own matrix.check:doc-linksis not a script in this repo — its non-zero is "no such script", not a finding.Acceptance notes
Noticed while reading, not filed and not fixed here, because nothing in this card's blast radius touches them:
useAiUsagecarries the same "setState synchronously within an effect" lint warning this hook originally had; the new hook derives its inert state instead, so it does not add another instance. Not a defect, not filed.content/docs/guide/console-architecture.mdnorconsole.mdenumerates the surfacesConsoleShellmounts, so neither documentsImpersonationBannerorAiUsageIndicatoreither. There is therefore no docs-site home for this one to join, and inventing a section had no precedent to follow; AGENTS.md Add automated testing infrastructure and CI/CD workflows #2 is satisfied through the package README instead. Noted, not filed — I could name no pull request or person who would land on it.Related, and untouched by this branch: cloud#2377 remains open. Nothing here acts on it.
🤖 Generated with Claude Code
https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
Generated by Claude Code