Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .design-sync/config.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions .design-sync/entry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ export {
type DisclosureGroupProps,
type DisclosureProps,
} from "@/components/ui/disclosure";
export {
ErrorState,
errorStateCopy,
shouldEmitErrorStateDiagnostic,
type ErrorStateProps,
type ErrorStateReason,
} from "@/components/ui/error-state";
export {
ErrorSummary,
FieldError,
Expand Down
16 changes: 16 additions & 0 deletions .design-sync/previews/ErrorState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ErrorState } from "prompt-for-codex-medical-knowledge-base";

// A failed request has no count to report, so none of these render a number.
// "0 matches" on a failed services search asserts there are no crisis services
// when the search never ran — see SPEC §10 and COMPONENTS §0.3.
export const Reasons = () => (
<div className="flex flex-col gap-3">
<ErrorState reason="request_failed" subject="Services" onRetry={() => {}} />
<ErrorState reason="unauthorized" subject="Favourites" />
<ErrorState reason="timeout" subject="DSM diagnoses" onRetry={() => {}} />
</div>
);

// `inline` drops the panel chrome for a surface that already owns a border,
// exactly as the search band's fault panel sits inside the results ribbon.
export const Inline = () => <ErrorState reason="request_failed" subject="Referral services" density="inline" />;
2 changes: 1 addition & 1 deletion docs/design-system/ADOPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ multiply-owned routes fail the check. `src/app/api/**` and `src/app/mockups/**`
product exclusions; the only route-only disposition is the documented legacy document-source
redirect. Shared shell/component roots carry their own explicit `shared-shell` disposition.

Registered public components: 53
Registered public components: 54
Declared product roots: 59
Roots with a literal `.ckb-v2` opt-in: 1
Roots inheriting `.ckb-v2` from the global `<html>`: 58
Expand Down
5 changes: 3 additions & 2 deletions docs/design-system/COMPONENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -945,8 +945,8 @@ tests to run.

## Generated maturity snapshot

Registered public components: 53
Components with a valid design-sync preview: 53
Registered public components: 54
Components with a valid design-sync preview: 54
Components with product imports: 31

This generated snapshot is a local source-derived inventory. It does not assert remote design-project publication.
Expand All @@ -970,6 +970,7 @@ This generated snapshot is a local source-derived inventory. It does not assert
| `DoseLine` | answer | yes | yes | no | yes | no | 0 |
| `DownloadLink` | controls | yes | yes | no | yes | no | 0 |
| `EmptyState` | feedback | yes | yes | inherited-global-root | yes | no | 12 |
| `ErrorState` | feedback | yes | yes | no | yes | no | 0 |
| `ErrorSummary` | feedback | yes | yes | no | yes | no | 0 |
| `ExternalTextLink` | controls | yes | yes | no | yes | no | 0 |
| `FieldError` | feedback | yes | yes | no | yes | no | 0 |
Expand Down
80 changes: 40 additions & 40 deletions docs/design-system/GATES.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/design-system/adoption-contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@
"DoseLine": "answer",
"DownloadLink": "controls",
"EmptyState": "feedback",
"ErrorState": "feedback",
"ErrorSummary": "feedback",
"ExternalTextLink": "controls",
"FieldError": "feedback",
Expand Down
35 changes: 32 additions & 3 deletions docs/design-system/adoption-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@
"src/components/DocumentManagementActions.tsx",
"src/components/clinical-dashboard/account-setup-dialog.tsx",
"src/components/clinical-dashboard/auth-panel.tsx",
"src/components/clinical-dashboard/search-results-header-band.tsx"
"src/components/clinical-dashboard/search-results-header-band.tsx",
"src/components/ui/error-state.tsx"
],
"productImportFiles": [
"src/components/DocumentManagementActions.tsx",
Expand Down Expand Up @@ -688,6 +689,34 @@
"proofDeclared": true,
"baselineCommitted": false
},
{
"name": "ErrorState",
"family": "feedback",
"source": "src/components/ui/error-state.tsx",
"sourceExported": true,
"entryExported": true,
"directImportFiles": [],
"productImportFiles": [],
"designSync": {
"listedInSourceMap": true,
"listedInDtsProps": true,
"preview": ".design-sync/previews/ErrorState.tsx",
"previewValid": true
},
"testFiles": ["tests/design-sync-visual-exports.test.ts", "tests/error-state.dom.test.tsx"],
"baseline": {
"targetLayer": "v2",
"liveLayer": "v2",
"remoteStatus": "unverified",
"visualBaselineStatus": "not-committed"
},
"built": true,
"locallyRegistered": true,
"v2ShellMounted": false,
"v2MountMode": "none",
"proofDeclared": true,
"baselineCommitted": false
},
{
"name": "ErrorSummary",
"family": "feedback",
Expand Down Expand Up @@ -3314,8 +3343,8 @@
"declaredV2SurfaceCount": 14
},
"summary": {
"registeredComponentCount": 53,
"previewCount": 53,
"registeredComponentCount": 54,
"previewCount": 54,
"productImportedComponentCount": 31,
"rootCount": 59,
"productionRouteCount": 51,
Expand Down
6 changes: 5 additions & 1 deletion docs/outstanding-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ removed after current-main verification; it is not missing recommended work.
| 114 | `#289` | A2 | High — auth/identity | Next auth module touch | 1–2 hours | Export a named helper (e.g. `authorizationIdentity(headers)`) from the auth module and use it at every property-access call site; consider a lint rule or branded type so `.Authorization` stops type-checking at all. **Stop:** do not change `authorizationHeadersForAccessToken` to emit uppercase — lowercase is the correct Fetch/Headers convention and callers that pass the object wholesale to `fetch` depend on it. |


<!-- issues:next-id=298 -->
<!-- issues:next-id=302 -->
## Open items

> **Merged-main canary update (2026-07-23, run `30018289898`):** the new structured report correctly recorded evaluated tree `c24f2e8f2d30d0c59fc1eba025d3dcd63478137e`, run/attempt identity and `cross-region-runner` latency context. Golden retrieval remained 36/36 with document/content recall 1.0 and no failed cases. The 44-case answer gate had grounded-supported and unsupported-correct rates of 1.0, but failed because `neuroleptic-side-effect-escalation` again returned one citation where two are required (citation-failure rate 0.0227). `admission-discharge-comparison` again omitted the specific AKG admission document after `comparison_source_extractive_fallback`; `admission-discharge-coverage-paraphrase` was advisory-only at 24,870 ms. Answer cost was reported as `$0.234736`. Do not retry immediately: retain this as the first structured datapoint, compare it with the scheduled 2026-07-26 report, and keep retrieval/ranking unchanged.
Expand Down Expand Up @@ -330,6 +330,10 @@ removed after current-main verification; it is not missing recommended work.
| #294 | P3 | rec | OffscreenCanvas for the PDF raster is unjustified until the page-flip cost is read from CI | **Outcome:** the worker-raster question is settled by a number rather than left as a standing 'optional' item in the redesign plan. **Detail:** docs/plans/document-viewer-redesign-plan.md conditions OffscreenCanvas on 'measured main-thread paint cost'. Phase 3 (Task 5) did not implement it, deliberately: virtualization now keeps the reader's page and one neighbour already rastered, so the cold-render-per-flip cost that motivated a worker raster is largely gone before any threading work starts, and moving pdf.js rendering off the main thread would put the canvas the clinical source is drawn into behind a transfer boundary — a real risk on the one surface where a blank page is a clinical failure. **No number exists yet and none could be produced locally:** pdfjs-dist@6 needs Map.prototype.getOrInsertComputed, which this container's Chromium 141 lacks and Node 24.13.0 also lacks, so neither a browser nor a headless harness here can raster a page (see #279). **Next:** read the measurement the gate already captures. tests/ui-document-canvas.spec.ts attaches page-flip-raster-cost.json (flipToPaintedMs, longTaskCount, longTaskTotalMs, longestTaskMs, canvasBackingPixels) and logs a '[viewer-canvas] page flip painted in Nms' line, on every Production UI run and on any host with the pinned Chromium 151 build: npm ci --include=dev && npx playwright install chromium && npm run ensure && npm run test:e2e -- tests/ui-document-canvas.spec.ts --project=chromium. Close this as not-worth-doing and strike the row from the plan's Phase 3 table only when a Production UI (or equivalent Chromium 151) run records decisive log lines for all three: longestTaskMs comfortably under ~50ms, plus explicit flipToPaintedMs and longTaskTotalMs budgets agreed for that host class and met on the same run. Do not close on longestTaskMs alone. **Stop:** do not implement OffscreenCanvas on principle because the plan lists it — the plan conditions it on the measurement, and the measurement is now cheap to obtain. | session 2026-08-09 document viewer Phase 3, Task 5; docs/plans/document-viewer-phase3-handover.md | 2026-08-09 |
| #296 | P3 | issue | tests/pr-handoff-stop.test.ts fails whenever the unit suite runs as root | The case 'emits handoff context only when the marker file exists' chmods the fixture git dir to 0o555 to force the marker write to fail, then asserts the hook failed open with no marker. Root ignores permission bits, so the write succeeds and the assertion flips: 'expected true to be false' at tests/pr-handoff-stop.test.ts:180. Confirmed environmental and pre-existing, not diff-induced — reproduced on a clean checkout of af85cbc with every working change stashed (1 failed \| 10 passed), and 'id -u' returns 0 in the remote container. Cost is that 'npm run test' and therefore 'npm run verify:pr-local' cannot reach a clean exit in any root container, so a real regression later in the run is masked by a known-red file and the gate has to be interpreted by hand every time. CI is unaffected because its runner is non-root, which is why this has not surfaced there. Next action: make the test skip or change technique when 'process.getuid?.() === 0' — either skip with an explicit reason, or force the write failure a way root cannot bypass (point the marker path at a directory that does not exist, or at a path whose parent is a file), which is portable and keeps the assertion meaningful for every user. Stop: do not delete the case or relax it to 'marker may or may not exist' — failing open without telling the model that tools are denied is the actual contract it guards. | session 2026-08-09; verify:pr-local run on af85cbc | 2026-08-09 |
| #297 | P3 | task | Retire the --text-2xl-compact type step, which no production surface selects | Measured 2026-08-09 against origin/main 7aaf9349c while adding the step-selection gate (#262 part 2). --text-2xl-compact is declared at src/app/globals.css:112 and selected by ZERO production surfaces: the only other mentions anywhere are src/lib/tailwind-merge.ts:20 (a docstring), its entry in CLINICAL_TWMERGE_THEME.text at src/lib/tailwind-merge.ts:50, tests/tailwind-merge-config.test.ts:49 (which asserts the @theme block and the twMerge config agree), and a row in docs/design-system/TOKENS.md. No var(--text-2xl-compact) consumer exists either. For contrast, the next-rarest step, text-hero, does have one real consumer (src/components/mode-home-template.tsx:87). The new gate in scripts/check-design-system-contract.mjs fails on any declared-but-unconsumed step; this one is carried in UNUSED_TYPE_STEP_EXEMPTIONS with outstanding-issues.md #297 named as its tracking task, and the gate additionally fails if the exemption ever goes stale (step undeclared, or step gains a consumer). Next action: delete --text-2xl-compact from the globals.css @theme block, drop "2xl-compact" from CLINICAL_TWMERGE_THEME.text and from tests/tailwind-merge-config.test.ts, update the TOKENS.md row, then delete the exemption entry - the gate will hold the line afterwards. Deleting a step with zero consumers renders identically, but it edits @theme, so give it its own revertible PR rather than bundling it with a gate change. Gate: npm run check:design-system-contract plus npm run test. | session 2026-08-09 - M3 design-token debt (#262 parts 2 and 3) | 2026-08-09 |
| #298 | P2 | task | ErrorState is built and registered but nothing enforces it - the '0 matches after a failed request' row is still planned | GATES.md still reads: Render "0 matches" after a failed request \| ErrorState adoption + check \| planned. The component now exists (src/components/ui/error-state.tsx) and is registered across all gate-11/12 surfaces, but grep over scripts/ and eslint-rules/ returns ZERO references to ErrorState, so nothing prevents a new surface rendering a count under a failed status. Building the component closed the 'documented gate with nothing behind it' half; the enforcement half is untouched, and the row was deliberately NOT flipped to implemented because that is exactly the drift GATES.md exists to stop. Next action: add a metric to check:design-system-contract that fails when a count-bearing node renders under an error/failed status - the analyzer already resolves class roots and JSX children (childrenAreNumeralOnly, used by statusColouredNumerals), so it needs no new npm script. Do this BEFORE adoption: a check with no adoption still stops the next regression, adoption with no check does not. Stop: do not flip the GATES.md row until a check actually runs in verify:cheap. | session 2026-08-09 M4 - ErrorState build | 2026-08-09 |
| #299 | P3 | task | Adopt ErrorState at the three surfaces that genuinely hand-roll the failed-request guard | Three surfaces hand-roll the guard and their comments state the rule outright: src/components/clinical-dashboard/search-results-header-band.tsx:210 ('no number may reach the DOM'), src/components/services/services-navigator-page.tsx:634 ('a blocked registry must not reach the band as 0 matches'), src/components/clinical-dashboard/favourites-command-library-page.tsx:1182. They are CORRECT today, just not shared, so this is convergence rather than a bug fix. The band's fault panel is the richest existing implementation (role=alert, warning tokens, AsyncButton retry with busy state, faultAction slot) and ErrorState was modelled on it, so the shapes already line up. Live-look change: own PR, Chromium pass. Per the M4 brief it sits DOWNSTREAM of design decisions the owner has not made, so doing it before the site-wide redesign risks redoing it. Do NOT bundle with the enforcement check. Stop: only these three - see the sibling row for three sites that were miscarried as guards. | session 2026-08-09 M4 - ErrorState build | 2026-08-09 |
| #300 | P2 | issue | Three sites carried into M4 as hand-rolled '0 matches' guards are not guards - do not convert them | Re-measured 2026-08-09. The M4 handover listed six surfaces hand-rolling the failed-request guard; three do not survive measurement and converting them to ErrorState would be WRONG. (1) src/components/clinical-dashboard/differentials-home.tsx:716,729 renders '0 matches' and 'No matches' when sourcesChecked is TRUE - sourcesChecked: boolean means the source search RAN, so that is a legitimate zero after a search that SUCCEEDED and must keep reporting its count. (2) specifiers-home-page.tsx is not under clinical-dashboard/ at all - the real path is src/components/specifiers/specifiers-home-page.tsx and its line 211 is a comment about not showing a stale zero ABOVE real catalogue results, a different problem. (3) document-search-results.tsx:1508 gates on recordStatus for LOADING (recordSearchStillRunning), not for a failed count; its genuine fault handling is recordBandOwnsFault, which delegates to the band. Only search-results-header-band, services-navigator-page and favourites-command-library-page are real. Next action: none - this row exists so the next reader does not convert the wrong three. Stop: do not 'fix' differentials-home to suppress its count. | session 2026-08-09 M4 - ErrorState build | 2026-08-09 |
| #301 | P3 | issue | Two sessions built #262 part 3 in parallel because the GATES.md row understated what had shipped | On 2026-08-09 two branches implemented the same raw-value ratchet independently. PR #1780 landed rawPaddingLiterals/rawRadiusLiterals/rawLineHeightLiterals; a concurrent session built arbitraryPadding/arbitraryGap/arbitraryRadius/arbitraryLeading against the same four files and discovered the collision only when syncing before PR. The duplicate was dropped and only the uncovered gap family was rebuilt on #1780's predicate (rawGapLiterals, 34 sites). Root cause is the same failure this document keeps producing: the §3 row read 'Contract ratchet \| implemented-partial (colour/shadow/tap literals only)' and named none of the metrics #1780 had just shipped, so the row still advertised the work as unstarted. Identical to the 2026-08-09 finding that four of #264's six prohibitions were already gated while their rows read 'planned'. Both rows are corrected now. Next action: when a gate lands, update its §3 row IN THE SAME COMMIT - a row that understates shipped work is not a stale doc, it is a duplicate-work generator. Consider asserting in a test that every metric key in design-system-contract-baseline.json appears somewhere in GATES.md. Stop: do not rely on the ledger alone to prevent this - both sessions had ledger access. | session 2026-08-09 M4; PR #1780 collision | 2026-08-09 |


## Resolved / archive
Expand Down
5 changes: 4 additions & 1 deletion scripts/check-design-system-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const metrics = {
arbitraryTracking: 0,
rawPaddingLiterals: 0,
rawRadiusLiterals: 0,
rawGapLiterals: 0,
rawLineHeightLiterals: 0,
layoutTransitionExceptions: 0,
textSoftConsumers: 0,
Expand Down Expand Up @@ -196,6 +197,7 @@ for (const file of files) {
recordDebt("arbitraryTracking", file.relativePath, classAnalysis.arbitraryTracking.length);
recordDebt("rawPaddingLiterals", file.relativePath, classAnalysis.rawPaddingLiterals.length);
recordDebt("rawRadiusLiterals", file.relativePath, classAnalysis.rawRadiusLiterals.length);
recordDebt("rawGapLiterals", file.relativePath, classAnalysis.rawGapLiterals.length);
recordDebt("rawLineHeightLiterals", file.relativePath, classAnalysis.rawLineHeightLiterals.length);
for (const step of classAnalysis.typeStepUsages) typeStepUsage.add(step);
for (const step of findTypeStepCssUsagesInSource(source, file.relativePath)) typeStepCssUsage.add(step);
Expand All @@ -211,6 +213,7 @@ for (const file of files) {
recordDebt("legacyShadowAliases", file.relativePath, cssAnalysis.legacyShadowAliases.length);
recordDebt("rawPaddingLiterals", file.relativePath, cssAnalysis.rawPaddingLiterals.length);
recordDebt("rawRadiusLiterals", file.relativePath, cssAnalysis.rawRadiusLiterals.length);
recordDebt("rawGapLiterals", file.relativePath, cssAnalysis.rawGapLiterals.length);
recordDebt("rawLineHeightLiterals", file.relativePath, cssAnalysis.rawLineHeightLiterals.length);
imageInversionFindings.push(...cssAnalysis.imageInversions);
layoutTransitionFindings.push(...cssAnalysis.layoutTransitions);
Expand Down Expand Up @@ -476,7 +479,7 @@ console.log(
`Status-colour boundary: colour-only status indicators ${metrics.colourOnlyStatusIndicators}; status-coloured numerals ${metrics.statusColouredNumerals}; image inversions ${imageInversionFindings.length}.`,
);
console.log(
`Scale ratchets: raw padding literals ${metrics.rawPaddingLiterals}; raw radius literals ${metrics.rawRadiusLiterals}; raw line-height literals ${metrics.rawLineHeightLiterals}.`,
`Scale ratchets: raw padding literals ${metrics.rawPaddingLiterals}; raw radius literals ${metrics.rawRadiusLiterals}; raw gap literals ${metrics.rawGapLiterals}; raw line-height literals ${metrics.rawLineHeightLiterals}.`,
);
console.log(`Text-role ratchet: --text-soft consumers ${metrics.textSoftConsumers}.`);
console.log(`Raw-color exemptions: ${RAW_COLOR_EXEMPTIONS.map(({ category }) => category).join(", ")}.`);
Loading
Loading