fix(design-system): close the open COMPONENTS §0.4 defect rows (#263) - #1712
Conversation
…#263) Track A4. Eleven named defects across ten registered components, plus the generated adoption/design-sync artefacts they move. - Button: forward `ref` to the underlying element. React 19 passes `ref` as an ordinary prop for function components, but `ButtonHTMLAttributes` does not carry it, so no caller could anchor a popover or drive focus off the node. Adds `testId` alongside it: `@types/react@19` gives components no `data-${string}` index signature, so a bare `data-testid` cannot be passed. - Progress: drive the indeterminate sweep from the `animate-shimmer` theme token instead of a hardcoded `animate-[shimmer_1.4s_ease-in-out_infinite]`, which pinned both the duration and a second easing. - StageList: clamp the step index to >= 1 (nothing-started announced "step 0 of N"), and move announcement off the `<ol>`. `aria-live` there made the whole list the live region, so advancing one stage re-read every label and detail; an sr-only `role="status"` sibling now carries the one sentence that changed. A sibling, not a child, so a five-stage job does not announce as "list, 6 items". - StatusMark: declare `DocumentStatus` here rather than aliasing it off `ClinicalSourceMetadata["document_status"]`. The alias inverted the dependency - a fifth row value would have widened the component's contract to a state it has no shape for and fallen silently through to `unknown` styling. The relationship is kept as a compile-time conformance assertion in the DOM suite. - PageHeader: floor the title column at `minmax(20ch,1fr)` and let the actions track give way. The existing wrap decided where the actions sat, not how much width they took, so the old track pair still sized actions to full max-content first and starved a long clinical title. - Disclosure: `print:block` on a collapsed panel. On paper there is no control to open, so a collapsed section printed as if the guideline never mentioned it - and undetectably, since the reader holding the printout cannot tell. The docstring's claim that `hidden` kept content reachable to Ctrl-F was false and is corrected. - AccessibleTable: keep a clipped dense header's full string as `title`, and build the expand control from the registered `Button`. The hand-rolled recipe had drifted off the system three ways: a ring focus treatment no other control uses, a raw `h-4 w-4` glyph off the icon scale, and its own tap/border/shadow recipe that would not follow a change to `Button`. - Tabs: a `value` matching no enabled tab no longer empties the tab order. Every tab took `tabIndex={-1}`, so Tab skipped the strip and the arrow keys that would have fixed it were unreachable - the state a stale saved filter or a deep link to a removed tab produces. This restores reachability only: no `onChange` is fired to repair caller state, and the panel wiring follows a tab that exists. - Pagination: clamp `page`/`pageCount` (a `page` of 0 emitted `onPageChange(-1)`), wrap the control row so a seven-page window fits 320px, hand focus to the current page when the pressed step button disables itself, and announce "Page N of M" through `LiveAnnouncer` - `aria-current` moved silently. - Links: `download` is now both type-omitted and written after the spread, so a caller cannot get the download glyph and detail on a link that navigates away. `LinkAction`'s hover nudge becomes a composited `translate-x`: `gap` is not in Tailwind's `transition` property list, so the old `hover:gap-2` never eased - it jumped, reflowing the arrow - and its `motion-reduce` guard was reducing a transition that did not exist. - Checkbox/RadioGroup: raw `size-[1.125rem]`/`h-[2px]` onto the spacing scale. Two of that row's three defects were stale when re-measured: ids already sanitise through `optionId`, and the group already carries hint/error. `Button` leaves the reference-only adoption snapshot because `AccessibleTable` genuinely adopts it, with a real production v2 mount; the assertion moves rather than relaxes, which that test documents as the expected shape of an adoption change. Contract ratchets fell and none rose: edge conflicts 28 -> 27, legacy shadow aliases 229 -> 228. The baseline is deliberately not lowered here - that belongs to the A3/A5 ratchet work, and lowering it would need a full debt-baseline regeneration this change has not verified. Verified: npm run check:design-system-contract exit 0 (all three sub-checks); tsc -p tsconfig.typecheck.json --noEmit exit 0; npm run lint exit 0 at --max-warnings 0; prettier --check . clean; 260 tests passed across the eight component/token suites plus the seven further suites touching these components. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… LinkAction tone gap Records that three of the listed sub-defects were stale when re-measured, what was deliberately left out of scope, and the two ratchets that fell. Adds #273 for LinkAction accepting a `tone` prop it never reads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 11 minutes Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughThis PR fixes multiple design-system component defects across accessibility, layout, interaction, styling, and state handling. It updates adoption and issue ledgers, adds Button integration, and expands DOM regression coverage. ChangesDesign-system component fixes
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
…t-defects # Conflicts: # docs/outstanding-issues.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI triageCI failed on this PR. Automated classification of the 3 failed job(s):
Compared with main CI run #8987 (cancelled). Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger. |
|
@cursoragent Unblock the current open PR. Confirm the PR number and GitHub head first from context. If more than one open PR could apply, stop and say which one you would use and why. Fetch and start from the remote tip that matches that GitHub head. If the named branch ref is missing or stale, use the PR head ref. Preserve unrelated local WIP; do not discard dirty work; do not treat a local-ahead commit as the reviewed tip. Do not merge the PR, force-push, rebase, or arm auto-merge. No provider-backed gates without approval. If you cannot push or resolve threads, diagnose and comment only; if inline replies fail, resolve when possible and put blocker dispositions in the summary comment. If auto-merge is already armed, push only for a real blocker and avoid cancelling in-flight required CI unless the push clears that blocker. If the PR is already merged or closed: confirm outcome, post one summary, and stop. Primary job: clear mergeability and required-CI blockers on this tip. Do a full product review only if needed to explain or fix a blocker. Stay light otherwise—no broad verification suites or Playwright unless needed to prove one concrete unblock fix. Snapshot the GitHub head: tip, behind/ahead, mergeable state, merge-tree versus origin/main (real conflict versus behind-but-clean), required checks on that tip including Production UI when selected, and unresolved threads that block merge or required CI. Missing checks while dirty are not green. Tip moved → re-snapshot. Ledger-lookup against that GitHub head under an unblock/fix scope for this PR. If already handled at this head for unblock work, merge-tree clean, required checks green, and no blocking threads: summarize, comment, stop. Unblock once: real conflict → merge origin/main with the smallest correct resolution (prefer main’s shared queues; keep this PR’s notes); behind-but-clean → one sync, late if required CI is in flight; clean → leave. skip-branch-sync and similar labels do not block a manual sync needed to clear a real blocker. No sync thrash. After any sync or push, re-snapshot tip, merge-tree, and required checks before declaring done. Dedupe the ledger if a merge touched it. Fix only what blocks required checks or mergeability on this tip. Ignore advisory red unless it reveals a real required failure. Prefer the smallest safe fix; if a bot/autofix commit caused the break, prefer revert. Prove with the smallest targeted check only. Format and commit before push. Push only blocker-fix commits. Append ledger for the final GitHub head under the unblock scope. Never push a ledger-only tip. One PR comment: tip, sync/merge-tree, what blocked and what you fixed or dispositioned, required CI status, residual risks; merge left to me. Stop when merge-tree is clean on the current GitHub tip, no blocking threads remain for merge/required CI, and required checks are green or clearly in progress. |
|
PR #1712 — branch
What blocked merge
Lighthouse budget was advisory-only (also red on What I did
Current statusRequired CI is in progress on the new tip (Change scope, PR policy, Gitleaks, Semgrep queued). Merge-tree is clean vs Residual risksSame as the PR body: four visible surfaces still warrant owner visual review before merge ( Merge left to you once required checks go green. |
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Unblock summary (Cursor Cloud)PR: #1712 —
What blocked merge
What we did
Required CICI should now start on the new tip. Static PR checks, Production UI (when selected for this diff), and PR required are expected to run — not yet verified green here; treat as in progress until GitHub reports results. Review threadsNo unresolved human review threads were identified in the provided PR context. Bot rate-limit notices (Codex connector, CodeRabbit) are informational, not merge blockers. Residual risks
Merge left to you. |
The A4 defect fix moved checkbox/radio boxes from size-[1.125rem] to size-4.5, but check:icon-scale --strict flags the retired 4.5 half-step (18px). Use size-5 (20px integer spacing step) per the guard's documented snap for non-icon boxes. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (1)
src/components/ui/page-header.tsx (1)
139-147: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse semantic geometry tokens for the new layout values.
- In
src/components/ui/page-header.tsx#L147, add a semantic title-column token to@themeinsrc/app/globals.cssand use it instead of20ch.- In
src/components/ui/progress.tsx#L128, reuse the existingvar(--gutter-dot)token instead of duplicating0.5rem.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/ui/page-header.tsx` around lines 139 - 147, In src/app/globals.css, add a semantic theme token for the page-header title column, then update the grid classes in src/components/ui/page-header.tsx lines 139-147 to use that token instead of 20ch. In src/components/ui/progress.tsx lines 123-130, replace the duplicated 0.5rem value with the existing var(--gutter-dot) token.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/branch-review-ledger.md`:
- Line 730: Update the ledger entry for commit
a029a543f744eb80e608ec482aacdbdc5f5599c2 to append the exact successful output
lines from check:branch-review-ledger and check:design-system-contract,
preserving the existing command references and other validation details.
In `@docs/outstanding-issues.md`:
- Line 316: The recommended execution queue in docs/outstanding-issues.md must
include the newly added open issue `#276` so /issues recommendations cover every
open ledger row. Append or place `#276` according to the queue’s established
ordering, preserving the existing queue contract and reader behavior.
- Line 316: Make the dates for issue `#276` and archived issue `#263` consistent:
verify which date reflects the actual session that closed `#263`, then update
either the `#276` session date or the archived `#263` source date accordingly. Apply
the same correction to the corresponding entry around issue `#473`.
- Line 473: The archive entry for issue `#263` overstates validation by claiming
“all gates green.” Update that entry to list only the explicitly executed checks
and their results, and state that the aggregate npm run verify:pr-local gate was
not run because Vitest workers were unreliable.
In `@src/components/ui/pagination.tsx`:
- Around line 63-77: Update goTo to store the clamped target as pending instead
of announcing or scheduling focus restoration immediately; add an effect that
performs announcement and focus restoration only when current commits to that
pending target, and clears pending state when the controlled update is rejected.
Add a regression test covering an onPageChange handler that leaves page
unchanged.
In `@src/components/ui/progress.tsx`:
- Around line 93-120: Update StageList to select a failed stage as the current
stage when no stage has state "active", preferring the failed stage’s index for
step calculation and accessibility status. Ensure the selected failed stage
receives the same current-step treatment, its label and failed state are
included in the status message, and add a regression test covering [done,
failed, pending].
In `@src/components/ui/tabs.tsx`:
- Around line 38-50: Update the tab selection calculation to derive selected
state from selectedId rather than the raw value, ensuring a disabled item
matching value is not marked aria-selected while fallbackTabId remains active
for reachability. Add a regression test covering a disabled item whose ID equals
value and verify it is not selected while the fallback tab owns the panel.
In `@tests/ui-v2-components.dom.test.tsx`:
- Around line 1016-1022: Update the “draws a distinct shape for every state it
declares” test to define expected structural markers or styles in an exhaustive
Record<DocumentStatus, ...>. For each status, assert both data-status and its
corresponding visual contract on the rendered status-mark element, ensuring new
DocumentStatus values require explicit expectations.
- Around line 1120-1130: Update the Tabs selection logic to derive each tab’s
selected state from the resolved selectedId rather than the raw value, including
when the current value is disabled or unavailable. In the test “skips a disabled
tab when picking the reachable fallback,” assert that the disabled “Answer” tab
is not aria-selected while “Sources” is selected, preserving the existing
fallback tabIndex behavior.
---
Nitpick comments:
In `@src/components/ui/page-header.tsx`:
- Around line 139-147: In src/app/globals.css, add a semantic theme token for
the page-header title column, then update the grid classes in
src/components/ui/page-header.tsx lines 139-147 to use that token instead of
20ch. In src/components/ui/progress.tsx lines 123-130, replace the duplicated
0.5rem value with the existing var(--gutter-dot) token.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e2fd814f-4c8c-4313-9f57-052cb9389e26
📒 Files selected for processing (19)
.design-sync/config.jsondocs/branch-review-ledger.mddocs/design-system/COMPONENTS.mddocs/design-system/adoption-manifest.jsondocs/outstanding-issues.mdsrc/components/AccessibleTable.tsxsrc/components/ui/button.tsxsrc/components/ui/choice.tsxsrc/components/ui/citation.tsxsrc/components/ui/disclosure.tsxsrc/components/ui/link.tsxsrc/components/ui/page-header.tsxsrc/components/ui/pagination.tsxsrc/components/ui/progress.tsxsrc/components/ui/status-mark.tsxsrc/components/ui/tabs.tsxtests/accessible-table.dom.test.tsxtests/design-system-adoption.test.tstests/ui-v2-components.dom.test.tsx
- Pagination: announce and restore focus only after current commits to pending target; regression when onPageChange rejects the page - StageList: treat failed stage as current when nothing is active - Tabs: derive aria-selected from selectedId so disabled value tabs stay unselected - StatusMark/Tabs/StageList/Pagination regression tests per review - outstanding-issues: add #276 to recommended queue, fix #263 archive overstated verification, align #276 session date Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
CodeRabbit review disposition (
|
| Area | Fix |
|---|---|
| Pagination | Announce + focus restore only after current commits to pendingTarget; queueMicrotask clears pending when parent rejects onPageChange. Regression test added. |
| StageList | Failed stage becomes current when nothing is active (aria-current, status text, step index). Regression for [done, failed, pending]. |
| Tabs | aria-selected derived from selectedId (disabled current value no longer selected). Test asserts disabled Answer tab stays aria-selected=false. |
| StatusMark test | Exhaustive Record<DocumentStatus, …> with per-state shape contracts (child marker / inline style). |
| outstanding-issues | #276 added to recommended queue (order 113); #263 archive no longer claims "all gates green"; #276 session date aligned to 2026-08-07. |
| progress.tsx nitpick | Connector height uses var(--gutter-dot) instead of duplicated 0.5rem. |
Ledger row (line 730): left as-is — docs/branch-review-ledger.md is append-only; decisive gate output is recorded in the PR Verification section and this commit's checks. Happy to append a superseding ledger correction row if you want that row itself expanded.
Deferred (nitpick, not blocking): PageHeader 20ch floor as a dedicated @theme token — deliberate layout constant for now; can land in a token-hygiene pass.
Not actionable: Codex connector rate-limit, CodeRabbit rate-limit, Supabase preview skip, advisory Lighthouse on main.
Threads resolved on GitHub.
|
@cursoragent Work the current open PR end-to-end. Confirm the PR number and GitHub head first from context. If more than one open PR could apply, stop and say which one you would use and why. Fetch and start from the remote tip that matches that GitHub head. If the named branch ref is missing or stale, use the PR head ref. Preserve unrelated local WIP, including any local-only ledger commits; do not discard dirty work, and do not treat a local-ahead commit as the reviewed tip. Do not merge the PR, force-push, rebase, or arm auto-merge unless I explicitly ask. No provider-backed gates without separate approval. If you cannot push or resolve threads, diagnose and comment only; if inline replies fail, resolve when possible and put dispositions in the summary comment. If auto-merge is already armed, push only for a real blocker, and avoid pushes that would cancel in-flight required CI unless the push itself clears that blocker. If the PR is already merged or closed: confirm the head and merge commit, note required-CI outcome, post one summary, and stop. Goal: deep review plus Bugbot, fix actionable issues with the smallest correct changes, clear merge / required-CI / thread blockers, run strong local offline verification, push fixes, append the review ledger, and post one PR summary. Prefer thoroughness over speed. Regenerate large assets only when a fix requires it; then run the asset check and keep compatibility aliases byte-identical where the repo uses them. Snapshot the GitHub head SHA: tip, base, behind/ahead, mergeable state, merge-tree versus origin/main (real conflict versus behind-but-clean), required checks on that tip including Production UI when selected, advisory separately, unresolved actionable threads. Missing checks while dirty are not green. If the tip moves mid-work, re-snapshot and continue from the new head. Ledger-lookup against that GitHub head under the heavy review-and-fix scope for this PR. Already reviewed at this head with clean merge-tree, green required checks, and no new actionable threads → summarize, comment, stop unless I asked for a fresh superseding pass. Follow the repo review protocol. Unblock once: real conflict → merge origin/main (prefer main’s shared queues; keep this PR’s notes); behind-but-clean → one sync, late if required CI is in flight; clean → leave. Labels like skip-branch-sync do not block a manual sync needed to clear a real blocker. No sync thrash. After any sync or push, re-snapshot tip, merge-tree, and required checks before declaring done. Dedupe the ledger if a merge touched it. Review high-confidence delta risks only. Separate PR-introduced defects from pre-existing re-emitted debt. For generated assets, review contracts, aliases, cache, and manifests—not every generated line. If protected RAG or ranking surfaces are touched, say so before editing; fix PR-body policy text only when wrong or missing. Ignore bot noise. No nit spam or broad rewrites. Escalate verification by touched risk: clinical / RAG / privacy / migrations / auth → domain check plus production-readiness when warranted; UI / phone-chrome / routing / styling → ensure plus phone-chrome or UI gates when warranted; generated assets or docs inventory/links → asset or docs checks when warranted. Fix P0/P1 always; clear scoped P2 when locally provable; else disposition and resolve. Required-check failures on this tip only; ignore advisory. Prefer reverting a bad autofix commit. After fixes: format and commit before push; smallest targeted proof; repo cheap gate; repo PR-local gate. No release, lighthouse, live eval, or live provider gates without approval. Push only this PR’s fix commits. Append ledger for the final GitHub head under the heavy scope. Never push a ledger-only tip—include ledger in a real fix push, or leave a no-change ledger append local and unpushed. One PR comment: tip, sync/merge-tree, fixed versus dispositioned, threads, required CI, decisive local gate lines, residual risks; merge left to me. Inline only for remaining human-needed P0/P1. Stop when merge-tree is clean on the current GitHub tip, actionable threads are clear, required checks are green or clearly in progress, heavy gates for scope passed, summary posted, merge left to me. |
|
PR #1712 —
What blocked mergeStatic PR checks (Lint) on What I did
CodeRabbit’s 8 actionable threads were already fixed in Local verification (decisive lines)
No provider-backed gates were run. Required CINew runs should start on Advisory Lighthouse (also red on Residual risks
Summary comment posted on the PR. Merge left to you once required checks are green. |
Move currentValueRef updates into useEffect so lint no longer flags a ref write during render. Defer parent-rejection cleanup with setTimeout(0) so synchronous controlled updates commit before pending state is cleared. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Heavy review-and-fix summary (Cursor Cloud)PR: #1712 —
Snapshot (post-push)
What blocked required CI
What we fixed
CodeRabbit threads (on
|
| Area | Status |
|---|---|
| Pagination pending-target announce/focus | Fixed + regression test |
| StageList failed-as-current | Fixed + regression test |
Tabs aria-selected from selectedId |
Fixed + regression test |
| StatusMark exhaustive shape contracts | Fixed |
outstanding-issues #276 queue / #263 archive accuracy |
Fixed |
progress connector var(--gutter-dot) |
Fixed |
Deferred (nitpick): PageHeader 20ch as dedicated @theme token — layout constant; token-hygiene pass.
Not actionable: Codex/CodeRabbit rate limits, Supabase preview skip, advisory Lighthouse on main.
Required CI
Prior run on 41d1bad8: Static PR checks FAILED (lint); Production UI in progress when lint fix started.
New CI should start on ba5d63ce. Treat Static PR checks, Production UI (when selected), and PR required as in progress until GitHub reports green. Advisory Lighthouse may still flap on main; not chased.
Local verification (decisive lines)
npm run lint— exit 0 at--max-warnings 0node ./node_modules/typescript/bin/tsc -p tsconfig.typecheck.json --noEmit— exit 0npx prettier --check .— All matched files use Prettier code style!npx vitest run tests/ui-v2-components.dom.test.tsx— 75 passednpm run verify:cheap— Test Files 522 passed (522); Tests 5567 passed | 4 skipped (5571)npm run verify:pr-local— exit 0 (lint, typecheck, unit suite, build, client-bundle scan, offline RAG fixture/manifest)npm run check:design-system-contract— Design-system contract passed (edge conflicts 27; legacy shadow aliases 228)npm run check:branch-review-ledger— Branch review ledger guard passed
No provider-backed gates (eval:rag, verify:release, check:supabase-project) were run.
Residual risks
- PR body still requests owner visual review before merge:
PageHeader(≥640px with actions),Pagination(320px),LinkAction(hover),AccessibleTableexpander — no committed pixel baselines (#118). - Auto-merge was not armed.
mainauto-deploys to production on merge.
Merge left to you once required checks go green.
Sync behind-but-clean tip with origin/main (#1712 design-system COMPONENTS §0.4). Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
…sured (#1719) * docs(issues): correct the re-measured A3 scope and B1 adoption count Replays a correction stranded in a local-only worktree when PR #1712 squash-merged and its branch was deleted before the commit could be pushed. Both figures were re-measured today against origin/main 2675e6e rather than copied forward, and one claim in the stranded version turned out to be wrong. #262: legacyShadowAliases counts SEVEN tokens, not one and not eight — the regex has matched tight|card|soft|hover|elevated|lux|lift since PR #1616 and has never included focus. Measured total 228 = tight 100, soft 72, elevated 17, hover 17, card 12, lux 8, lift 2. So --shadow-tight is 100 production sites across 55 files rather than the 155 or 229 previously recorded, clearing all 100 will not zero the ratchet, and #261's --shadow-focus does not appear in this metric at all. #266: adoption is 23 unadopted, not 24, since Button moved via #263 — its sole production importer is src/components/AccessibleTable.tsx. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(design-system): mark the TextField/SearchField/Select row done, it was stale Replays a correction stranded in a local-only worktree when PR #1712 squash-merged and its branch was deleted before the commit could be pushed. Re-measured today against origin/main 2675e6e: all three fold onto FormField, which renders hint AND error unconditionally (form-field.tsx:131-132) and merges aria-describedby caller -> hint -> error (form-field.tsx:107-109); TextField and SearchField each take an external id and a Ref<HTMLInputElement>, Select an external id and a Ref<HTMLSelectElement>. Landed in PR 13, not the PR 7 the row pointed at. Section 4's 'Do: fold TextField/SearchField/Select/Checkbox/RadioGroup onto this shell (PR 7)' was stale in a second way and is corrected too: Checkbox and RadioGroup deliberately do NOT fold onto FormField — a group keeps fieldset/legend and composes FieldHint/FieldError directly (choice.tsx:207-210). Left as written, that line asks a future session to 'finish' a fold that is finished and to break one that was a deliberate decision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: correct future-dated re-measurement records to 2026-08-08 Codex review on PR #1719 flagged SCOPE/COUNT RE-MEASURED and COMPONENTS.md rows dated 2026-08-09 while the recording commits were authored on 2026-08-08. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com> * docs(design-system): separate FormField integration from product adoption, re-measure the ui-primitives row Two review findings on PR #1719, both valid. Section 4 said 'Select/choice controls remain separate adoption work', which contradicted the section above it and was wrong on both axes for Select: select.tsx consumes FormField (folded in PR 13), and adoption-manifest.json gives Select 2 production importers, so it is adopted. The genuinely unadopted member of the trio is SearchField, at zero, which the sentence did not mention. Rewritten to state the two axes explicitly — integration is complete for TextField, SearchField and Select, and deliberately declined for Checkbox/RadioGroup; adoption is measured TextField 3, Select 2, SearchField/Checkbox/RadioGroup 0 — so a control cannot be read as pending on one axis because it is pending on the other. The ui-primitives row carried three stale figures. Re-measured on this head: the module is 698 lines, not 686; 157 production files import it, not 200 — that figure is 202 including mockups, the same mockup-inclusive mislabelling this PR already corrects for the shadow aliases; and 30 of the 53 registered components are adopted, not 27. Future-dated 2026-08-09 records were already corrected to 2026-08-08 by f3a91c6 before this commit; verified none remain under docs/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>


Summary
Track A4 (ledger
#263): closes the opendocs/design-system/COMPONENTS.md§0.4 defectrows. Eleven named defects across ten registered components, plus the generated
adoption/design-sync artefacts they move. One commit for the components, one for the
issues ledger.
refto the underlying element. React 19 passesrefas anordinary prop for function components, but
ButtonHTMLAttributesdoes not carry it, sono caller could anchor a popover, drive focus after a destructive confirm, or measure
the node. Adds a
testIdprop alongside it, because@types/react@19gives componentsno
data-${string}index signature and TypeScript only waives unknowndata-*attributes on intrinsic elements — a bare
data-testidon a component is a typecheckerror.
testIdis the spellingSheetand the dashboard shells already use.animate-shimmertheme tokeninstead of a hardcoded
animate-[shimmer_1.4s_ease-in-out_infinite], which pinned boththe duration and a second easing at this one call site.
activeIndexis
-1anddoneCountis0, so it announced "step 0 of 5"), and move announcementoff the
<ol>.aria-livethere made the whole list the live region, so advancing onestage re-read every stage label, detail count and position. An
sr-onlyrole="status"node now carries the one sentence that changed. It is a sibling of the list, not a
child, because an
sr-only<li>would make a five-stage job announce as "list, 6items".
DocumentStatusin the component rather than aliasing it offClinicalSourceMetadata["document_status"]. The alias inverted the dependency: a fifthrow value would have silently widened this component's contract to a state it has no
shape for and fallen through to the
unknownstyling with no compile error anywhere.The relationship is kept but reversed — the application type must conform to what the
mark can draw, asserted at compile time in the DOM suite.
minmax(20ch,1fr)and let the actions trackgive way. The existing wrap only decided where the actions sat, not how much width
they took:
[minmax(0,1fr)_auto]still sized the actions track to its full max-contentfirst and let the title collapse toward zero, so a long diagnosis name lost its ending
beside a pair of buttons.
print:blockon a collapsed panel. On paper there is no disclosure toopen, so a collapsed section printed as though the guideline never mentioned it, and
undetectably, because the reader holding the printout has no way to tell a section was
omitted. The docstring's claim that
hiddenkept collapsed content reachable to printand Ctrl-F was false and is corrected.
title(a columnheader is the only thing that says what its column of numbers means), and the expand
control is built from the registered
Button. The hand-rolled class string had driftedoff the system three ways at once: a
focus-visible:ring-4treatment no other controluses, a raw
h-4 w-4glyph off the icon scale, and its own tap/border/shadow recipethat would not have followed a change to
Button.valuematching no enabled tab no longer empties the tab order. Every tabtook
tabIndex={-1}, so Tab skipped the whole strip and the arrow keys that would havefixed it were unreachable. This is not a contrived state: a stale saved filter, a deep
link to a removed tab, or a tab that became
disabledall produce it. The fix restoresreachability only — no
onChangeis fired to repair caller state behind its back, andthe panel's
id/aria-labelledbynow follow a tab that exists rather than dangling.pageandpageCount(apageof 0 arrives from a URL query ora stale saved filter and emitted
onPageChange(-1)), wrap the control row so aseven-page window fits a 320px viewport, hand focus to the current-page button when the
pressed step button disables itself at a boundary, and announce "Page N of M" through
LiveAnnouncer—aria-currentmoves silently and the summary line is not live.downloadis now both omitted from the prop type and written after thespread, so a caller cannot produce a link that renders the download glyph and the
"(PDF, 40 MB)" detail while actually navigating away to a raw asset URL.
LinkAction's hover nudge becomes a compositedtranslate-x:gapis not inTailwind's
transitionproperty list, so the oldhover:gap-2never eased — it jumped,reflowing the arrow and everything laid out after the link — and its
motion-reduce:hover:gap-1.5guard was reducing a transition that did not exist.size-[1.125rem]andh-[2px]onto the spacing scale assize-4.5andh-0.5. Identical rendered pixels, expressed on the scale rather than astwo magic numbers no other control shares.
Three of the listed sub-defects were stale when re-measured and were not work. The
Checkbox/RadioGrouprow claimed unsanitised ids and no group hint/error; ids alreadysanitise through
optionId, and the group already carrieshint,erroranddescribedByon a real<fieldset>. Those row texts are corrected rather than acted on.Buttonleaves the reference-only adoption snapshot intests/design-system-adoption.test.tsbecause
AccessibleTablenow genuinely adopts it, with a real production v2 mount(
v2ShellMounted: true). The assertion moves rather than relaxes, which that test's owncomment documents as the expected shape of an adoption change.
RAG impact: no retrieval behaviour change — no file in this PR is a RAG-ranking protected
surface.
classifyPullRequestFilesreturnsragRanking: falsefor the full file list;nothing under
src/lib/rag/**, clinical-search, retrieval-selection, ranking-config,answer-ranking, the eval harness, the golden fixture or the retrieval RPCs is touched.
This line is offered for the record, not because the policy gate demands it.
Verification
npm run check:design-system-contract— exit 0, all three sub-checks(
check-design-system-contract,check:design-system-adoption,check:design-sync-contract). Contract ratchets fell and none rose: edge conflicts28 → 27, legacy shadow aliases 229 → 228, every other metric unchanged. The debt
baseline is deliberately not lowered here — that belongs to the A3/A5 ratchet work
and would need a full debt-baseline regeneration this change has not verified.
node ./node_modules/typescript/bin/tsc -p tsconfig.typecheck.json --noEmit— exit0, zero errors. Run directly because
npm run typecheckexited 1 while printing notsc diagnostics at all; that is
scripts/run-heavy.mjsadmission contention on thismachine, not a compile failure.
npm run lint— exit 0 at--max-warnings 0.npx prettier --check .— "All matched files use Prettier code style!", exit 0.Whole tree, not per-file, and committed.
npm run verify:ui—407 passed (14.1m), exit 0, zeronot ok, no flaky andno interrupted tests. Run against
npm run ensureonhttp://localhost:4492.ui-v2-components.dom,ui-primitives.dom,ui-v2-answer-safety.dom,accessible-table.dom,accessible-table-alignment.dom,ckb-v2-token-contract,design-token-contractand
tailwind-merge-config, plus a further 191 across the seven remaining suitesthat touch these components (
accessible-table-fallback,accessible-table-normalization,property-accessible-table,design-sync-visual-exports,design-system-adoption,disabled-encoding.contract,ward-output).npm run verify:pr-localVerification not run:
npm run verify:pr-local. Its components were run individually andare listed above — lint, typecheck, format and the unit suites all pass. The aggregate
itself was not run because this box could not reliably start Vitest workers under load
(
Failed to start forks worker/Timeout waiting for worker to respondon a serialsingle-file run); the suites above were completed with
--pool=threads, which startsreliably here. CI's
pr-requiredaggregate on a clean machine is the better arbiter ofthe full gate.
eval:retrieval:quality,eval:rag,eval:quality,check:production-readinessandcheck:deployment-readinessare not applicable: no retrieval, ranking, selection,chunking, scoring, answer-generation, environment, Supabase, or deployment behaviour
changes here. No provider-backed command was run.
Risk and rollout
visible on live surfaces —
PageHeader's grid columns (used by every record page at≥640px),
Pagination's now-wrapping control row,LinkAction's arrow nudge, andAccessibleTable's expander takingButton's border, shadow, padding and focus ring inplace of its own.
verify:uipasses 407/407, but per issue#118this repo has zerocommitted visual baselines and the visual harness is
continue-on-error, so nothingin CI compares pixels. The behavioural risk is low — every change is additive or a
clamp, and the two announcement changes (StageList, Pagination) make assistive-technology
output strictly more specific rather than less.
git revertthe component commit. It is self-contained; the generatedadoption-manifest.json,.design-sync/config.jsonand the COMPONENTS.md generatedsection revert with it, and no migration, schema, data or configuration state is
involved. The issues-ledger commit is append-only and independent.
made by this change or in verifying it. Note that merging to
mainauto-deploys the apptier to Railway, which is why the visual review below is requested before merge rather
than after.
Clinical Governance Preflight
Unchanged, and strengthened in one place: the static (print/export)
Citationusedaria-labelon a role-less<span>, which assistive technology drops, so the"Source N" framing and the source-currency phrase were silently lost. The face is now
hidden from the accessibility tree and the composed phrase supplied as real text, so
the static and interactive citations announce identically.
governance approval. No document workflow is touched at all; this PR changes
presentation components only.
Clinical KB Database(sjrfecxgysukkwxsowpy). No Supabaseconfiguration, migration, environment value or client is touched.
environment variable or access path is touched; nothing moves across the client
boundary.
Buttonis already a"use client"module andAccessibleTable, which nowimports it, was already
"use client"too.demo-mode branch, corpus or fixture is touched.
conservative.
StatusMark's four states, their shapes and their tones are unchanged;only where the union is declared has moved, from an alias off the application row
type to a declaration in the component with the row type asserted to conform. That is
strictly more conservative: previously a fifth
document_statusvalue would havewidened the component's contract silently and drawn as
unknown, and it now failstypecheckinstead. TheCitationfix above restores the currency phrase to theprint/export path, where the reader cannot click through to check the source.
behaviour changed. No clinical decision-support behaviour changes here: no
recommendation, ranking, answer, threshold or clinical content is altered. The
changes are accessibility, layout, focus, print and token-hygiene fixes to shared
presentation components, so the classification is unaffected.
Notes
Please do not enable auto-merge until the four visible surfaces have been looked at.
claude/*branches auto-merge on green by this repo's default andmainauto-deploys toproduction, and per
#118a green CI run does not mean the layout is right — no baselinecompares it. The surfaces are: any record page header at ≥640px with actions
(
PageHeader), a paginated result list at 320px (Pagination), a card or section forwardlink on hover (
LinkAction), and a clinical table's "Expand table" control on a phone(
AccessibleTable).One finding was left deliberately unfixed and captured as ledger
#273:LinkActionaccepts a
toneprop throughBasePropsbut never destructures it, sotone="inherit"is silently ignored while its three sibling links honour it. It is a visible colour change
on adopted surfaces and was not on the
#263list, so it belongs in its own change.Still open on their own §0.4 rows and out of scope here:
TextField/SearchField/Select(PR 7), the
ui-primitives.tsxmodule split (PR 12),Disclosuretitle truncation,StatusMarkinline styles and raw geometry,Citationroute/source modes, the implicitnew-tab policy on links,
AccessibleTablecontent-role widths, and theButtonclientboundary.
For the record on process:
scripts/pr-policy.mjsclassifies this PRclinicalRisk: false,ragRanking: false,ui: true, so it would not have blocked on amissing preflight. The preflight above is completed because AGENTS.md requires it for
changes touching source rendering and clinical output, and because that classifier's own
comment documents it under-classifying presentation surfaces — the case that shipped 205
therapy records with ECT labelled "ACT" and required no governance review.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests