diff --git a/.design-sync/conventions.md b/.design-sync/conventions.md index 6105591cf2..aacdffc1b4 100644 --- a/.design-sync/conventions.md +++ b/.design-sync/conventions.md @@ -31,10 +31,9 @@ arbitrary-value form — never hardcoded colours: `--info-solid`. For a filled non-danger status use `-bg` + `-text`. - Elevation: the `--e0` … `--e4` ladder — `shadow-[var(--e2)]`, `hover:shadow-[var(--e3)]`. `--e0` flush · `--e1` resting hairline · `--e2` cards/popovers · `--e3` hover/lifted chrome · - `--e4` modals/sheets/drawers. The surviving role names are aliases onto tiers: - `--shadow-card`/`--shadow-soft`→`--e2`, `--shadow-hover`→`--e3`, - `--shadow-elevated`/`--shadow-lux`→`--e4`. `--shadow-tight` is retired — reach for `--e1`. - `--shadow-inset` stays bespoke. + `--e4` modals/sheets/drawers. The role names are aliases onto tiers: + `--shadow-tight`→`--e1`, `--shadow-card`/`--shadow-soft`→`--e2`, `--shadow-hover`→`--e3`, + `--shadow-elevated`/`--shadow-lux`→`--e4`. `--shadow-inset` stays bespoke. Never hand-roll a `shadow-[0_…]` literal. - Focus ring: `focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]`. Outline only — never add a companion `focus:ring-*` / `box-shadow`. The shared base rule is one diff --git a/AGENTS.md b/AGENTS.md index 483df4b4c6..88ec727f80 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,9 +2,7 @@ # This is NOT the Next.js you know -This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. - -This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. diff --git a/Dockerfile b/Dockerfile index 623e2d3422..72c491885f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ # NEVER baked into the image — inject them at run time from the host's # secret store. -FROM node:26-bookworm-slim@sha256:cd565714d4da3e84bfd341e31448f81d47c6362198f152345297c9c1154e6341 AS node-base +FROM node:24-bookworm-slim@sha256:235600a8101ab264e117b1768e925532262668dc9b581ef1dd7d96ced463b8e7 AS node-base FROM node-base AS deps WORKDIR /app @@ -28,10 +28,11 @@ WORKDIR /app COPY package.json package-lock.json .npmrc ./ COPY scripts/check-node-engine.cjs scripts/check-node-engine.cjs COPY scripts/install-git-hooks.mjs scripts/install-git-hooks.mjs +COPY scripts/check-installed-lock-parity.mjs scripts/check-installed-lock-parity.mjs # Registry blips (ECONNRESET) have failed CI app-image builds mid-install; retry # the whole `npm ci` rather than relying only on per-request fetch retries. RUN for attempt in 1 2 3; do \ - NPM_CONFIG_ENGINE_STRICT=false npm ci --ignore-scripts --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 && break; \ + npm ci --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 && break; \ if [ "$attempt" -eq 3 ]; then exit 1; fi; \ sleep $((attempt * 10)); \ done @@ -67,8 +68,9 @@ WORKDIR /app COPY package.json package-lock.json .npmrc ./ COPY scripts/check-node-engine.cjs scripts/check-node-engine.cjs COPY scripts/install-git-hooks.mjs scripts/install-git-hooks.mjs +COPY scripts/check-installed-lock-parity.mjs scripts/check-installed-lock-parity.mjs RUN for attempt in 1 2 3; do \ - NPM_CONFIG_ENGINE_STRICT=false npm ci --omit=dev --ignore-scripts --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 && break; \ + npm ci --omit=dev --ignore-scripts --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 && break; \ if [ "$attempt" -eq 3 ]; then exit 1; fi; \ sleep $((attempt * 10)); \ done diff --git a/Dockerfile.worker b/Dockerfile.worker index 0352e999be..4e5163c762 100644 --- a/Dockerfile.worker +++ b/Dockerfile.worker @@ -19,16 +19,17 @@ # `server-only` marker to the standalone stub at build time (the job # run-tsx.mjs previously did at runtime) and keeps npm packages external, # so the bundle resolves them from the runner's production node_modules. -FROM node:26-bookworm-slim@sha256:cd565714d4da3e84bfd341e31448f81d47c6362198f152345297c9c1154e6341 AS node-base +FROM node:24-bookworm-slim@sha256:235600a8101ab264e117b1768e925532262668dc9b581ef1dd7d96ced463b8e7 AS node-base FROM node-base AS build WORKDIR /app COPY package.json package-lock.json .npmrc ./ COPY scripts/check-node-engine.cjs scripts/check-node-engine.cjs COPY scripts/install-git-hooks.mjs scripts/install-git-hooks.mjs +COPY scripts/check-installed-lock-parity.mjs scripts/check-installed-lock-parity.mjs # Same install-retry contract as the app Dockerfile (registry ECONNRESET flakes). RUN for attempt in 1 2 3; do \ - NPM_CONFIG_ENGINE_STRICT=false npm ci --ignore-scripts --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 && break; \ + npm ci --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 && break; \ if [ "$attempt" -eq 3 ]; then exit 1; fi; \ sleep $((attempt * 10)); \ done @@ -42,8 +43,9 @@ WORKDIR /app COPY package.json package-lock.json .npmrc ./ COPY scripts/check-node-engine.cjs scripts/check-node-engine.cjs COPY scripts/install-git-hooks.mjs scripts/install-git-hooks.mjs +COPY scripts/check-installed-lock-parity.mjs scripts/check-installed-lock-parity.mjs RUN for attempt in 1 2 3; do \ - NPM_CONFIG_ENGINE_STRICT=false npm ci --omit=dev --ignore-scripts --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 && break; \ + npm ci --omit=dev --ignore-scripts --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000 && break; \ if [ "$attempt" -eq 3 ]; then exit 1; fi; \ sleep $((attempt * 10)); \ done diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 7c0fdec906..b1c6f2b5ce 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -880,9 +880,13 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-10 | cursor/smarter-meds-search-9c1b (PR #1785) | 5cb0e11e077a3aaf5b8e4ea37b26ac72b0328997 | PR #1785 unblock/fix | before: Production UI (3) failed on service-detail scroll endpoint (remaining 67px) at 38b3bd0c; GitHub DIRTY behind-but-clean vs #1791. after: merged origin/main + re-scroll toPass fix in ui-tools service-detail test; threads untouched; do not merge | CI Production UI (3) logs; git merge-tree clean; prettier ui-tools; product fix in same tip commit as this row | | 2026-08-10 | PR #1800 / codex/enhance-search-function-with-fuzzy-matching | 93da84b063c9c3f956da7ef79710d2cd00159735 | PR #1800 babysit | Synced origin/main (merge-tree clean; GitHub DIRTY was staleness). Fixed CodeRabbit SSRI/SNRI fuzzy cross-match (floor 5 chars) in follow-on tip commit. Clinical Governance Preflight required for clinicalRisk body. Codex P2 field-aware/per-token fuzzy deferred. RAG surfaces untouched. | focused catalog-search+consumers 49 pass; pr-policy body local ok; merge-tree clean | | 2026-08-10 | PR #1803 / claude/codex-m4b-shadow-tight-migration-53a8kn | b778a56e9c3fa7642a783dde85e1130559d71e24 | shadow-tight token migration onto the e1 elevation tier and alias retirement (#262 part 1) | Migrated all 150 var(--shadow-tight) occurrences across 71 files to var(--e1) (90 gated production sites across 48 files, 60 mockup); deleted all three alias declarations (:root, .dark, forced-colors); pinned legacyShadowAliases 220 to 127 with exact per-path counts, closing 3 aliases of re-accumulated slack; added a whole-stylesheet absence assertion (mutation-verified); updated GATES.md section 3 plus a new section 6, TOKENS.md section 6, design-system.md, both redesign direction docs, .design-sync/conventions.md and ledger #262. Verified in Chromium that the ckb-v2 tier override is picked up by the alias substitution, so the change is value-preserving; that check is recorded as a prerequisite for the remaining six aliases. | npm run verify:cheap (30 static gates plus lint plus typecheck green; design-system contract passed, legacy shadow aliases 127; unit suite 553/554 files, 6024 tests passed, 1 pre-existing root-permission failure in tests/pr-handoff-stop.test.ts reproduced on untouched base a16dd26); npm run format:check whole tree; targeted Chromium computed-style measurement. verify:ui not run, Playwright browser revision drift #255, delegated to CI Production UI. No provider-backed gates. | -| 2026-08-11 | codex/answer-loading-ui-20260811 | 6758f8156f9d1b3e893981dfd7a1f6563aa90da0 | answer creation loading UI | No high-confidence findings | UI 3 passed; unit 8 passed; lint, typecheck, build, design-system and offline RAG passed; full suite 6022 passed with 16 unchanged baseline failures | | 2026-08-11 | claude/codex-m4c-retire-shadow-nliak3 | 448a0d084c4cd2cda6153dd7f03dcb67c43a8df0 | DS Track A2 (#261): retire --shadow-focus; composer focus onto sanctioned outline; contract guard; baseline ratchet; design-system docs + ledger | Approved — PR #1807. Token deleted in both themes; .chat-composer-shell-delta:focus-within uses outline 2px var(--focus) at offset 2px and no longer overrides box-shadow. Reach premise corrected: 0 of 37 production routes render the class (only /mockups/calculators-search). legacyShadowAliases 127->125, globals.css pin 3->1. | check:design-system-contract PASS; design-token-contract.test.ts PASS + mutation-verified both ways; verify:pr-local PASS except pre-existing tests/pr-handoff-stop.test.ts failure baselined on untouched base e8b61d8; build PASS; check:rag:fixtures PASS (36 cases); Chromium look both themes on the mockup route (inspection only, rev 1194 vs pinned 1234 #255); verify:ui/verify:phone-chrome NOT run — delegated to CI | +| 2026-08-11 | claude/spacing-icon-design-review-rxwh28 | f8701a524f0eb22decc64ce1f626bdafe91751af | mode-home hero spacing rhythm + icon scale (PR #1815) | shipped: group copy reserve banded to measured wrap points, continuous hero medallion, phone composer glyph 1.1rem->icon-lg, surface glyphs onto size-icon-*, privacy link bottom-only negative margin (fixes 8px tap overhang on the APP-5 sentence), composer phone reserve 7.625->6.625rem | test 6043 passed/1 pre-existing root-perm failure; lint; typecheck; build; check:icon-scale; check:type-scale; check:design-system-contract; check:rag:fixtures; check:bundle-budget; prettier --check .; verify:ui NOT run (Playwright r1234 vs image r1194, #255 - delegate to CI Production UI) | +| 2026-08-11 | codex/answer-loading-ui-20260811 | 6758f8156f9d1b3e893981dfd7a1f6563aa90da0 | answer creation loading UI | No high-confidence findings | UI 3 passed; unit 8 passed; lint, typecheck, build, design-system and offline RAG passed; full suite 6022 passed with 16 unchanged baseline failures | +| 2026-08-11 | 1815 | be7461ef1f66357999995acefbeecaf95268e481 | unblock | local-build-pass | MergeTreeClean,UnitCoverage,StaticPRChecks,ContainerImages | | 2026-08-11 | codex/answer-ecg-animation-20260811 | 12279a8309c225aa957ef1e65afc37545a0ce04c | answer ECG progress variants | No high-confidence findings; physical Safari/PWA remains residual acceptance | design contract, typecheck, focused unit 8/8, trace token 33/33, Chromium 4/4, offline RAG 574/574; full suite baseline/platform failures | +| 2026-08-11 | claude/spacing-icon-design-review-rxwh28 | 455bc198c077860fb1f830670a5fa9c1de08da52 | pr-1815 heavy review-and-fix | remote already merged main (shadow-tight Switch kept); cherry-picked privacy -mb-4 reclaim + calculators dock cancel; removed duplicate UniversalSearchAlsoMatches; rail-aware section-sheet focus restore; dispositioned CodeRabbit docs/ledger/gates nits and outdated Sentry skeleton gap | verify:cheap PASS prior tip; verify:pr-local PASS prior tip; vitest privacy+in-page-nav 28 passed on cherry-pick; merge-tree clean vs origin/main | +| 2026-08-11 | claude/spacing-icon-design-review-rxwh28 | 5b96281ee7da817d5ce7f1102004ebe6f861b920 | pr-1815 heavy review-and-fix | remote already merged main (shadow-tight Switch kept); cherry-picked privacy -mb-4 reclaim + calculators dock cancel; removed duplicate UniversalSearchAlsoMatches; rail-aware section-sheet focus restore; dispositioned CodeRabbit docs/ledger/gates nits and outdated Sentry skeleton gap | verify:cheap PASS prior tip; verify:pr-local PASS prior tip; vitest privacy+in-page-nav 28 passed on cherry-pick; merge-tree clean vs origin/main | | 2026-08-11 | work | 6dcd695076d630d16aae594577763e8004361893 | Codex Cloud setup and local parity | P2 fixed: cache-friendly locked Cloud npm install; parity limitations documented | check:codex-cloud; codex-cloud-setup 24/24; full suite 6059 pass, 7 unrelated timeout/state failures | | 2026-08-11 | 1822 | 4fab267f52b72992745e1d2e6975fb4847af447a | review-and-fix | clean | Build pass; Static PR checks pass; Change scope pass; PR mergeability pass; PR policy pass; Safety and config checks pass; Semgrep pass; Semgrep ingestion gate pass; Gitleaks pass; GitGuardian pass; Unit coverage pending; Production UI (1) pass; Production UI (2) pass; Production UI critical pending; Production UI (3) pending; Lighthouse budget pass; PR required pending | | 2026-08-11 | 1822 | 4fab267f52b72992745e1d2e6975fb4847af447a | review-and-fix (supersedes 2026-08-11) | clean | Build pass; Static PR checks pass; Change scope pass; PR mergeability pass; PR policy pass; Safety and config checks pass; Semgrep pass; Semgrep ingestion gate pass; Gitleaks pass; GitGuardian pass; Unit coverage pass; Production UI (1) pass; Production UI (2) pass; Production UI (3) pass; Production UI critical pass; Lighthouse budget pass; PR required pass | @@ -890,3 +894,5 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-11 | 1821 | d76e90547dbdb104256b66a508c15c74302002fc | review-and-fix | dispositioned | PR policy:success; PR mergeability:success; Gitleaks:success; Semgrep:success; Semgrep ingestion gate:success; Safety and config checks:success; Build:success; Production UI critical:success; Production UI (1):success; Production UI (2):success; Production UI (3):failure test not reproduced outside this PR; PR required:failure | | 2026-08-11 | work | 45fd05c8c3947835c0368666ff576c7a38b33ee4 | mobile evidence sheet UX, accessibility, and feedback logic | fixed unexplained claim marker, excess panel reserve, unclear purpose and feedback copy; no remaining high-confidence defects | focused DOM 7/7; Chromium evidence journey 1/1; offline RAG 23 suites/574 tests | | 2026-08-11 | 1820 | 897ff11a4cdb13ae1c01f5eb149007847028f5aa | review-and-fix | fixed | Semgrep:IN_PROGRESS, Gitleaks:IN_PROGRESS, Semgrep ingestion gate:IN_PROGRESS, Static PR checks:QUEUED, Safety and config checks:QUEUED, Unit coverage:QUEUED, Build:QUEUED, Production UI critical:QUEUED, Lighthouse budget:QUEUED | +| 2026-08-12 | PR #1815 / claude/spacing-icon-design-review-rxwh28 | 9f266210f02081be54d407c70a85f52fed436128 | babysit | no remaining actionable findings; one pre-existing thread resolved as no-change (Dockerfile.worker follow-up needed) | required checks: Gitleaks PR policy PR required (all pass); targeted vitest passed: tests/document-frame-contract.test.ts + tests/in-page-nav-header.dom.test.tsx | +| 2026-08-12 | 1815 | 27ce96e1755055ceee2eeae02d6efdf11259fcde | babysit | fixed | Unit coverage: targeted vitest passed: tests/shared-home-empty-state.dom.test.tsx (17 passed). PR required still blocked on pre-existing check failure at old remote head before sync. | diff --git a/docs/design-system.md b/docs/design-system.md index 1a715728e2..5d5489c7fe 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -150,11 +150,9 @@ Icon **glyphs** use the parallel `--spacing-icon-*` scale in `@theme`: instead of bleeding. `--e0` flush · `--e1` resting hairline · `--e2` cards/popovers · `--e3` hover/lifted chrome · `--e4` modals/sheets/drawers. Dark lifts with a top highlight rather than more black. -- The surviving role names are **aliases onto tiers**, not independent values: +- The role names are **aliases onto tiers**, not independent values: `--shadow-tight` → `--e1`; `--shadow-card` / `--shadow-soft` → `--e2`; `--shadow-hover` → `--e3`; `--shadow-elevated` / - `--shadow-lux` → `--e4`. `--shadow-tight` is retired — the resting hairline is `--e1` at the - call site. `--shadow-focus` is retired too — focus is an `outline: 2px solid var(--focus)`, - never a companion shadow ring. `--shadow-inset`, `--shadow-rail-active` and + `--shadow-lux` → `--e4`. `--shadow-inset`, `--shadow-rail-active`, `--shadow-focus` and `--glow-primary/soft` stay bespoke. All are removed under forced-colors, ladder included. - No literal `box-shadow` values in components — reach for a tier (`shadow-[var(--e2)]`, `hover:shadow-[var(--e3)]`) or a role alias. @@ -222,7 +220,7 @@ image"}` — never a possibly-empty variable alone. | `Number(query.page ?? 1)` | `parseInt` + `Number.isFinite` + `>= 1` clamp | | `alt={caption}` | `alt={caption?.trim() \|\| "Clinical document image"}` | | new `z-[73]` for a popover | an existing ladder rung, or `Sheet` | -| `shadow-[0_5px_12px_rgba(0,122,120,0.16)]` | `shadow-[var(--e1)]` | +| `shadow-[0_5px_12px_rgba(0,122,120,0.16)]` | `shadow-[var(--shadow-tight)]` | ## 9. Verification gates — Definition of Done for UI PRs diff --git a/docs/design-system/GATES.md b/docs/design-system/GATES.md index 527887d8d9..e485e7190b 100644 --- a/docs/design-system/GATES.md +++ b/docs/design-system/GATES.md @@ -74,7 +74,7 @@ because it contributed nothing. | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | Contrast ≥4.5:1 for every text/background pair, both themes, live **and** v2; `--decoration-soft` asserted below 4.5 and never on a text node | **implemented-blocking** | v2 pairs + `--text-placeholder` + `--decoration-soft` tier + recipe AST (`decoration-on-text.contract.test.ts`); live full matrix remains follow-on. | | 2 | Tap targets ≥48px interactive (token + declared carriers today; fixed-height `h-10` controls and full interactive enumeration not yet blocked); static pills never carry `min-h-tap`; no production target reduced | **implemented-partial** | Enforced today: `--spacing-tap` is 48px and pinned from both sides (`ckb-v2-token-contract` asserts the `@theme` knob is ≥48 and that `--tap-min` is its alias), `ui-style-contract` measures the rendered floor for declared `min-height` carriers in Chromium, and the legacy-class / literal ratchets still run. **Still open for the `h-10` case, and 9 Aug 2026 measured why.** An enumeration of _rendered interactive_ elements was written, shown to find genuine defects, and then **reverted rather than landed**: it is not deterministic on this route. Six runs against one production build returned 6, 5, 4, 3, 3 and 9 distinct sub-floor shapes, largely disjoint — one run saw the answer-suggestion chips and a sort band, another the settled results list. `waitForLoadState("networkidle")` plus deduplication to distinct shapes did not fix it, and two consecutive agreeing runs turned out to be coincidence. This spec runs in the required `Production UI` job, so an intermittent version of it would block every merge in the repo; that is a worse outcome than the gap it closes. Path to blocking: give the audit a deterministic surface — a static route or a fixed seeded state — before re-attempting it. **What the enumeration did establish, in every one of the six runs, is a live defect:** controls that carry `min-h-tap` compute `min-height: 0px` and render at 16–36px, six distinct shapes in total. The declared-carrier audit cannot report them by construction — it only measures elements already computing at or above the floor, so a floor overridden downward is skipped rather than flagged. Tracked as `#293`. **Correction, same date — "`test:e2e:style-contract` is not part of `verify:cheap`" was true and badly misleading, and it sent one session looking for a wiring bug that does not exist.** The npm script is only a convenience alias for running this one spec; the spec matches `productionSpecPattern` in `playwright.config.ts` and is listed explicitly in `scripts/playwright-pr-shards.mjs`, so it already runs in the `Production UI` job that `pr-required` demands on any UI-scoped PR. It must **not** be added to `verify:cheap:internal`: `check:gate-manifest` requires every gate in that chain to also run in `static-pr`, which has no browser and no server. Per-surface geometry stays in the held visual harness. | -| 3 | Focus outline present, `--focus`, no companion ring | **planned** | Corrected 6 Aug 2026: the previous evidence ("`--focus` is referenced nowhere in the DS export", finding N3) was false — **[verified: grep]** 4 declarations (2 theme, 2 forced-colours) against **273** `var(--focus)` consumers, 260 of them in `.tsx`. The token is adopted; the check is what is missing. Path to blocking: assert a visible focus outline on every interactive role and reject a `ring-*` companion on the same node. A row that understates shipped work costs the document its authority as surely as one that overstates it, and nobody files a bug against pessimism. 11 Aug 2026 (`#261`): the one companion ring this rule knew about is gone — `--shadow-focus` is deleted and `.chat-composer-shell-delta:focus-within` uses the outline. That removes the known violation, not the need for the check; nothing yet stops the next one. | +| 3 | Focus outline present, `--focus`, no companion ring | **planned** | Corrected 6 Aug 2026: the previous evidence ("`--focus` is referenced nowhere in the DS export", finding N3) was false — **[verified: grep]** 4 declarations (2 theme, 2 forced-colours) against **273** `var(--focus)` consumers, 260 of them in `.tsx`. The token is adopted; the check is what is missing. Path to blocking: assert a visible focus outline on every interactive role and reject a `ring-*` companion on the same node. A row that understates shipped work costs the document its authority as surely as one that overstates it, and nobody files a bug against pessimism. | | 4 | Non-colour encoding on every status indicator | **implemented-partial** | Blocked today by `ui-v2-answer-safety.dom.test.tsx`: an overdue `DoseLine` row is asserted to carry all three channels (amber inset rule **plus** the words "Source review overdue" **plus** a `StatusMark` shape), `MissingValue` is asserted never to contract to a dash at any density, `FieldError` is asserted to pair its text with an icon, and `RetrievalStateBanner` is asserted to carry its state in the headline text rather than the tone alone. Off-vocabulary status still degrades to a phrase (`source-badges-off-vocab.dom.test.tsx`, Gate 6). **Not blocked today:** there is no repository-wide enumeration of status indicators, so a _new_ colour-only indicator elsewhere in `src/components/**` — the bare `statusDot*` recipes are the obvious candidates — would not fail anything. **Closed 9 Aug 2026 — the repository-wide enumeration now ships.** `colourOnlyStatusIndicators` (`check:design-system-contract`) flags a status hue on a box that says nothing: no children, no `aria-label`/`aria-labelledby`/`title` on it or any ancestor, no text sibling, and not a `StatusMark`. It also flags shared _swatch recipes_ — a status hue plus a tiny round box and no text utility — because the analyzer is per-file and cannot follow an imported `statusDotReady` to its call sites, so the recipe is where the defect is catchable. Ratcheted at **4** with per-path pins: the two bare `statusDot*` recipes GATES.md named, plus a calculator risk band and a therapy meter fill. A _new_ colour-only indicator anywhere in `src/**` now fails. Still partial: those 4 recorded sites, and `--decoration-soft` is deliberately out of scope (it carries no state). | | 5 | Tables: semantic caption, associated headers, `aria-controls` on the expander | **implemented-blocking** | `AccessibleTableProps.caption` is required; DOM and alignment tests prove the semantic `