diff --git a/.design-sync/config.json b/.design-sync/config.json index aedede06b5..e012a0c488 100644 --- a/.design-sync/config.json +++ b/.design-sync/config.json @@ -20,7 +20,7 @@ ], "dtsPropsFor": { "AccessibleTable": "actionsHeader?: string; caption: string; clinicalOnly?: boolean; columnAlign?: AccessibleTableColumnAlign[]; columns?: string[] | null; compact?: boolean; densePreview?: boolean; dialogTitle?: string | null; expandOnMobile?: boolean; hidePreviewCaption?: boolean; hidePreviewRowCount?: boolean; lowConfidenceFallback?: ReactNode; markdown?: string | null; normalizedTable?: NormalizedAccessibleTable | null; numericColumns?: number[]; previewRows?: number; rowActions?: ReactNode[]; rows?: string[][] | null;", - "AnswerCard": "actions?: AnswerCardAction[]; children: ReactNode; className?: string; onOpenSource?: ((sourceId: string, locator?: string) => void) | ((sourceId: string, locator?: string) => void); provenance?: AnswerFooterProps; query?: string; state: { kind: \"ready\"; sourceCount: number; } | DegradedAnswerState; verification: VerificationNoticeProps;", + "AnswerCard": "actions?: AnswerCardAction[]; children: ReactNode; className?: string; onOpenSource?: ((sourceId: string, locator?: string) => void) | ((sourceId: string, locator?: string) => void); provenance?: AnswerFooterProps; query?: string; state: { kind: \"ready\"; sourceCount: number; } | DegradedAnswerState; support: AnswerSupportStrength; verification: VerificationNoticeProps;", "AnswerFooter": "className?: string; generatedAt?: string | null; metadata?: SourceMetadataInput; provenance?: string | SourceMetadataInput; publisher?: string | null; reviewDate?: string | null; version?: string | null;", "AsyncButton": "about?: string; accessKey?: string; \"aria-activedescendant\"?: string; \"aria-atomic\"?: (boolean | \"true\" | \"false\"); \"aria-autocomplete\"?: \"none\" | \"list\" | \"inline\" | \"both\"; \"aria-braillelabel\"?: string; \"aria-brailleroledescription\"?: string; \"aria-busy\"?: (boolean | \"true\" | \"false\"); \"aria-checked\"?: boolean | \"true\" | \"false\" | \"mixed\"; \"aria-colcount\"?: number; \"aria-colindex\"?: number; \"aria-colindextext\"?: string; \"aria-colspan\"?: number; \"aria-controls\"?: string; \"aria-current\"?: boolean | \"true\" | \"false\" | \"page\" | \"step\" | \"location\" | \"date\" | \"time\"; \"aria-describedby\"?: string; \"aria-description\"?: string; \"aria-details\"?: string; \"aria-disabled\"?: (boolean | \"true\" | \"false\"); \"aria-dropeffect\"?: \"none\" | \"link\" | \"copy\" | \"execute\" | \"move\" | \"popup\"; \"aria-errormessage\"?: string; \"aria-expanded\"?: (boolean | \"true\" | \"false\"); \"aria-flowto\"?: string; \"aria-grabbed\"?: (boolean | \"true\" | \"false\"); \"aria-haspopup\"?: boolean | \"true\" | \"false\" | \"dialog\" | \"grid\" | \"listbox\" | \"menu\" | \"tree\"; \"aria-hidden\"?: (boolean | \"true\" | \"false\"); \"aria-invalid\"?: boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\"; \"aria-keyshortcuts\"?: string; \"aria-label\"?: string; \"aria-labelledby\"?: string; \"aria-level\"?: number; \"aria-live\"?: \"off\" | \"assertive\" | \"polite\"; \"aria-modal\"?: (boolean | \"true\" | \"false\"); \"aria-multiline\"?: (boolean | \"true\" | \"false\"); \"aria-multiselectable\"?: (boolean | \"true\" | \"false\"); \"aria-orientation\"?: \"horizontal\" | \"vertical\"; \"aria-owns\"?: string; \"aria-placeholder\"?: string; \"aria-posinset\"?: number; \"aria-pressed\"?: boolean | \"true\" | \"false\" | \"mixed\"; \"aria-readonly\"?: (boolean | \"true\" | \"false\"); \"aria-relevant\"?: \"text\" | \"additions\" | \"additions removals\" | \"additions text\" | \"all\" | \"removals\" | \"removals additions\" | \"removals text\" | \"text additions\" | \"text removals\"; \"aria-required\"?: (boolean | \"true\" | \"false\"); \"aria-roledescription\"?: string; \"aria-rowcount\"?: number; \"aria-rowindex\"?: number; \"aria-rowindextext\"?: string; \"aria-rowspan\"?: number; \"aria-selected\"?: (boolean | \"true\" | \"false\"); \"aria-setsize\"?: number; \"aria-sort\"?: \"none\" | \"ascending\" | \"descending\" | \"other\"; \"aria-valuemax\"?: number; \"aria-valuemin\"?: number; \"aria-valuenow\"?: number; \"aria-valuetext\"?: string; autoCapitalize?: \"off\" | \"none\" | \"on\" | \"sentences\" | \"words\" | \"characters\" | (string & {}); autoCorrect?: string; autoFocus?: boolean; autoSave?: string; busy: boolean; busyLabel: string; children: ReactNode; className?: string; color?: string; content?: string; contentEditable?: (boolean | \"true\" | \"false\") | \"inherit\" | \"plaintext-only\"; contextMenu?: string; dangerouslySetInnerHTML?: { __html: string | TrustedHTML; }; datatype?: string; defaultChecked?: boolean; defaultValue?: string | number | readonly string[]; dir?: string; disabled?: boolean; draggable?: (boolean | \"true\" | \"false\"); enterKeyHint?: \"enter\" | \"done\" | \"go\" | \"next\" | \"previous\" | \"search\" | \"send\"; exportparts?: string; form?: string; formAction?: string | ((formData: FormData) => void | Promise); formEncType?: string; formMethod?: string; formNoValidate?: boolean; formTarget?: string; hidden?: boolean; id?: string; idleIcon?: ReactNode; inert?: boolean; inlist?: any; inputMode?: \"none\" | \"search\" | \"text\" | \"tel\" | \"url\" | \"email\" | \"numeric\" | \"decimal\"; is?: string; itemID?: string; itemProp?: string; itemRef?: string; itemScope?: boolean; itemType?: string; lang?: string; name?: string; nonce?: string; onAbort?: import(\"react\").ReactEventHandler; onAbortCapture?: import(\"react\").ReactEventHandler; onAnimationEnd?: import(\"react\").AnimationEventHandler; onAnimationEndCapture?: import(\"react\").AnimationEventHandler; onAnimationIteration?: import(\"react\").AnimationEventHandler; onAnimationIterationCapture?: import(\"react\").AnimationEventHandler; onAnimationStart?: import(\"react\").AnimationEventHandler; onAnimationStartCapture?: import(\"react\").AnimationEventHandler; onAuxClick?: import(\"react\").MouseEventHandler; onAuxClickCapture?: import(\"react\").MouseEventHandler; onBeforeInput?: import(\"react\").InputEventHandler; onBeforeInputCapture?: import(\"react\").InputEventHandler; onBeforeToggle?: import(\"react\").ToggleEventHandler; onBlur?: import(\"react\").FocusEventHandler; onBlurCapture?: import(\"react\").FocusEventHandler; onCanPlay?: import(\"react\").ReactEventHandler; onCanPlayCapture?: import(\"react\").ReactEventHandler; onCanPlayThrough?: import(\"react\").ReactEventHandler; onCanPlayThroughCapture?: import(\"react\").ReactEventHandler; onChange?: import(\"react\").ChangeEventHandler; onChangeCapture?: import(\"react\").ChangeEventHandler; onClick?: import(\"react\").MouseEventHandler; onClickCapture?: import(\"react\").MouseEventHandler; onCompositionEnd?: import(\"react\").CompositionEventHandler; onCompositionEndCapture?: import(\"react\").CompositionEventHandler; onCompositionStart?: import(\"react\").CompositionEventHandler; onCompositionStartCapture?: import(\"react\").CompositionEventHandler; onCompositionUpdate?: import(\"react\").CompositionEventHandler; onCompositionUpdateCapture?: import(\"react\").CompositionEventHandler; onContextMenu?: import(\"react\").MouseEventHandler; onContextMenuCapture?: import(\"react\").MouseEventHandler; onCopy?: import(\"react\").ClipboardEventHandler; onCopyCapture?: import(\"react\").ClipboardEventHandler; onCut?: import(\"react\").ClipboardEventHandler; onCutCapture?: import(\"react\").ClipboardEventHandler; onDoubleClick?: import(\"react\").MouseEventHandler; onDoubleClickCapture?: import(\"react\").MouseEventHandler; onDrag?: import(\"react\").DragEventHandler; onDragCapture?: import(\"react\").DragEventHandler; onDragEnd?: import(\"react\").DragEventHandler; onDragEndCapture?: import(\"react\").DragEventHandler; onDragEnter?: import(\"react\").DragEventHandler; onDragEnterCapture?: import(\"react\").DragEventHandler; onDragExit?: import(\"react\").DragEventHandler; onDragExitCapture?: import(\"react\").DragEventHandler; onDragLeave?: import(\"react\").DragEventHandler; onDragLeaveCapture?: import(\"react\").DragEventHandler; onDragOver?: import(\"react\").DragEventHandler; onDragOverCapture?: import(\"react\").DragEventHandler; onDragStart?: import(\"react\").DragEventHandler; onDragStartCapture?: import(\"react\").DragEventHandler; onDrop?: import(\"react\").DragEventHandler; onDropCapture?: import(\"react\").DragEventHandler; onDurationChange?: import(\"react\").ReactEventHandler; onDurationChangeCapture?: import(\"react\").ReactEventHandler; onEmptied?: import(\"react\").ReactEventHandler; onEmptiedCapture?: import(\"react\").ReactEventHandler; onEncrypted?: import(\"react\").ReactEventHandler; onEncryptedCapture?: import(\"react\").ReactEventHandler; onEnded?: import(\"react\").ReactEventHandler; onEndedCapture?: import(\"react\").ReactEventHandler; onError?: import(\"react\").ReactEventHandler; onErrorCapture?: import(\"react\").ReactEventHandler; onFocus?: import(\"react\").FocusEventHandler; onFocusCapture?: import(\"react\").FocusEventHandler; onGotPointerCapture?: import(\"react\").PointerEventHandler; onGotPointerCaptureCapture?: import(\"react\").PointerEventHandler; onInput?: import(\"react\").InputEventHandler; onInputCapture?: import(\"react\").InputEventHandler; onInvalid?: import(\"react\").ReactEventHandler; onInvalidCapture?: import(\"react\").ReactEventHandler; onKeyDown?: import(\"react\").KeyboardEventHandler; onKeyDownCapture?: import(\"react\").KeyboardEventHandler; onKeyPress?: import(\"react\").KeyboardEventHandler; onKeyPressCapture?: import(\"react\").KeyboardEventHandler; onKeyUp?: import(\"react\").KeyboardEventHandler; onKeyUpCapture?: import(\"react\").KeyboardEventHandler; onLoad?: import(\"react\").ReactEventHandler; onLoadCapture?: import(\"react\").ReactEventHandler; onLoadedData?: import(\"react\").ReactEventHandler; onLoadedDataCapture?: import(\"react\").ReactEventHandler; onLoadedMetadata?: import(\"react\").ReactEventHandler; onLoadedMetadataCapture?: import(\"react\").ReactEventHandler; onLoadStart?: import(\"react\").ReactEventHandler; onLoadStartCapture?: import(\"react\").ReactEventHandler; onLostPointerCapture?: import(\"react\").PointerEventHandler; onLostPointerCaptureCapture?: import(\"react\").PointerEventHandler; onMouseDown?: import(\"react\").MouseEventHandler; onMouseDownCapture?: import(\"react\").MouseEventHandler; onMouseEnter?: import(\"react\").MouseEventHandler; onMouseLeave?: import(\"react\").MouseEventHandler; onMouseMove?: import(\"react\").MouseEventHandler; onMouseMoveCapture?: import(\"react\").MouseEventHandler; onMouseOut?: import(\"react\").MouseEventHandler; onMouseOutCapture?: import(\"react\").MouseEventHandler; onMouseOver?: import(\"react\").MouseEventHandler; onMouseOverCapture?: import(\"react\").MouseEventHandler; onMouseUp?: import(\"react\").MouseEventHandler; onMouseUpCapture?: import(\"react\").MouseEventHandler; onPaste?: import(\"react\").ClipboardEventHandler; onPasteCapture?: import(\"react\").ClipboardEventHandler; onPause?: import(\"react\").ReactEventHandler; onPauseCapture?: import(\"react\").ReactEventHandler; onPlay?: import(\"react\").ReactEventHandler; onPlayCapture?: import(\"react\").ReactEventHandler; onPlaying?: import(\"react\").ReactEventHandler; onPlayingCapture?: import(\"react\").ReactEventHandler; onPointerCancel?: import(\"react\").PointerEventHandler; onPointerCancelCapture?: import(\"react\").PointerEventHandler; onPointerDown?: import(\"react\").PointerEventHandler; onPointerDownCapture?: import(\"react\").PointerEventHandler; onPointerEnter?: import(\"react\").PointerEventHandler; onPointerLeave?: import(\"react\").PointerEventHandler; onPointerMove?: import(\"react\").PointerEventHandler; onPointerMoveCapture?: import(\"react\").PointerEventHandler; onPointerOut?: import(\"react\").PointerEventHandler; onPointerOutCapture?: import(\"react\").PointerEventHandler; onPointerOver?: import(\"react\").PointerEventHandler; onPointerOverCapture?: import(\"react\").PointerEventHandler; onPointerUp?: import(\"react\").PointerEventHandler; onPointerUpCapture?: import(\"react\").PointerEventHandler; onProgress?: import(\"react\").ReactEventHandler; onProgressCapture?: import(\"react\").ReactEventHandler; onRateChange?: import(\"react\").ReactEventHandler; onRateChangeCapture?: import(\"react\").ReactEventHandler; onReset?: import(\"react\").ReactEventHandler; onResetCapture?: import(\"react\").ReactEventHandler; onScroll?: import(\"react\").UIEventHandler; onScrollCapture?: import(\"react\").UIEventHandler; onScrollEnd?: import(\"react\").UIEventHandler; onScrollEndCapture?: import(\"react\").UIEventHandler; onSeeked?: import(\"react\").ReactEventHandler; onSeekedCapture?: import(\"react\").ReactEventHandler; onSeeking?: import(\"react\").ReactEventHandler; onSeekingCapture?: import(\"react\").ReactEventHandler; onSelect?: import(\"react\").ReactEventHandler; onSelectCapture?: import(\"react\").ReactEventHandler; onStalled?: import(\"react\").ReactEventHandler; onStalledCapture?: import(\"react\").ReactEventHandler; onSubmit?: import(\"react\").SubmitEventHandler; onSubmitCapture?: import(\"react\").SubmitEventHandler; onSuspend?: import(\"react\").ReactEventHandler; onSuspendCapture?: import(\"react\").ReactEventHandler; onTimeUpdate?: import(\"react\").ReactEventHandler; onTimeUpdateCapture?: import(\"react\").ReactEventHandler; onToggle?: import(\"react\").ToggleEventHandler; onTouchCancel?: import(\"react\").TouchEventHandler; onTouchCancelCapture?: import(\"react\").TouchEventHandler; onTouchEnd?: import(\"react\").TouchEventHandler; onTouchEndCapture?: import(\"react\").TouchEventHandler; onTouchMove?: import(\"react\").TouchEventHandler; onTouchMoveCapture?: import(\"react\").TouchEventHandler; onTouchStart?: import(\"react\").TouchEventHandler; onTouchStartCapture?: import(\"react\").TouchEventHandler; onTransitionCancel?: import(\"react\").TransitionEventHandler; onTransitionCancelCapture?: import(\"react\").TransitionEventHandler; onTransitionEnd?: import(\"react\").TransitionEventHandler; onTransitionEndCapture?: import(\"react\").TransitionEventHandler; onTransitionRun?: import(\"react\").TransitionEventHandler; onTransitionRunCapture?: import(\"react\").TransitionEventHandler; onTransitionStart?: import(\"react\").TransitionEventHandler; onTransitionStartCapture?: import(\"react\").TransitionEventHandler; onVolumeChange?: import(\"react\").ReactEventHandler; onVolumeChangeCapture?: import(\"react\").ReactEventHandler; onWaiting?: import(\"react\").ReactEventHandler; onWaitingCapture?: import(\"react\").ReactEventHandler; onWheel?: import(\"react\").WheelEventHandler; onWheelCapture?: import(\"react\").WheelEventHandler; part?: string; popover?: \"\" | \"auto\" | \"manual\" | \"hint\"; popoverTarget?: string; popoverTargetAction?: \"toggle\" | \"show\" | \"hide\"; prefix?: string; property?: string; radioGroup?: string; rel?: string; resource?: string; results?: number; rev?: string; role?: import(\"react\").AriaRole; security?: string; slot?: string; spellCheck?: (boolean | \"true\" | \"false\"); style?: import(\"react\").CSSProperties; suppressContentEditableWarning?: boolean; suppressHydrationWarning?: boolean; tabIndex?: number; title?: string; translate?: \"yes\" | \"no\"; type?: \"submit\" | \"reset\" | \"button\"; typeof?: string; unselectable?: \"off\" | \"on\"; value?: string | number | readonly string[]; vocab?: string;", "Breadcrumb": "className?: string; items: Crumb[];", diff --git a/.design-sync/previews/AnswerCard.tsx b/.design-sync/previews/AnswerCard.tsx index c1a274e65d..229a1d5917 100644 --- a/.design-sync/previews/AnswerCard.tsx +++ b/.design-sync/previews/AnswerCard.tsx @@ -9,6 +9,7 @@ export const WithFooter = () => ( ( export const PlainAnswer = () => (
- +

No local guideline covers this question. The linked sources are the closest available.

@@ -37,6 +38,7 @@ export const MissingReviewDate = () => (

Review status is unknown for this source, so the footer says so rather than staying blank.

@@ -61,6 +63,7 @@ export const StaleEvidence = () => ( ], }} verification={{ state: "stale_evidence", sourceCount: 2 }} + support="supported" onOpenSource={openSource} >

This answer uses a source whose scheduled review is overdue.

@@ -78,6 +81,7 @@ export const PartialRetrieval = () => ( missing: [{ sourceId: "local-monitoring-addendum", title: "Local monitoring addendum", locator: "p. 4" }], }} verification={{ state: "partial_retrieval", sourceCount: 2 }} + support="limited" onOpenSource={openSource} >

This answer may omit local guidance because one named source was unavailable.

@@ -90,6 +94,7 @@ export const Ungrounded = () => (

The cited passages did not verify every clinical number in this answer.

@@ -102,6 +107,7 @@ export const SourceOnly = () => (

This answer was assembled from source passages without model synthesis.

diff --git a/docs/branch-review-records/017879a1e01ebb393ee9cfcacfe95f6f72f36926ac3b954e40cce1cc53c02b27.record.md b/docs/branch-review-records/017879a1e01ebb393ee9cfcacfe95f6f72f36926ac3b954e40cce1cc53c02b27.record.md new file mode 100644 index 0000000000..b8d98d6675 --- /dev/null +++ b/docs/branch-review-records/017879a1e01ebb393ee9cfcacfe95f6f72f36926ac3b954e40cce1cc53c02b27.record.md @@ -0,0 +1 @@ +| 2026-08-21 | claude/frontend-design-6sl1ft | eaae8bde3066749f105233c1492d6f044671b91b | full-frontend design review remediation: tokens, ward a11y, answer support label, gate gaps | applied — 20 review findings resolved or dispositioned; 3 architectural items deliberately deferred | verify:pr-local exit 0 (19/19 gates; 7599 tests passed, 4 skipped, 0 errors; build 1871 pages); verify:ui delegated to CI Production UI (playwright revision drift #255) | diff --git a/docs/branch-review-records/e59c5e7a53f6377bb610ffaeec065030de7aaccb89378c8c0d6318d233cfe41e.record.md b/docs/branch-review-records/e59c5e7a53f6377bb610ffaeec065030de7aaccb89378c8c0d6318d233cfe41e.record.md new file mode 100644 index 0000000000..9fabd08d74 --- /dev/null +++ b/docs/branch-review-records/e59c5e7a53f6377bb610ffaeec065030de7aaccb89378c8c0d6318d233cfe41e.record.md @@ -0,0 +1 @@ +| 2026-08-21 | claude/frontend-design-6sl1ft | 8b26a5c4ce836db00e031e2ad3ee3980e8a9d1a9 | design-review remediation + loose ends + nested-mockup CI scope routing | applied — 20 review findings resolved or dispositioned, 5 loose ends closed, 1 CI scope gap fixed; 3 architectural items recorded as decisions in TOKENS.md §9 / COMPONENTS.md §0.4 | verify:pr-local exit 0 (28/28 gates after CI-scope widening; 7599 tests passed, 4 skipped, 0 errors; build ok); full suite green with no gh CLI; verify:ui delegated to CI Production UI (playwright revision drift #255, and heavy jobs are draft-gated) | diff --git a/docs/design-system/COMPONENTS.md b/docs/design-system/COMPONENTS.md index ca39f2a88d..a5d89bb0ab 100644 --- a/docs/design-system/COMPONENTS.md +++ b/docs/design-system/COMPONENTS.md @@ -61,32 +61,32 @@ print primitives (`PrintHeader`, `PrintFooter`, `CitationFootnote`, `PrintOnly`, ### 0.4 Open-defect ledger (existing components → closing PR) -| Component | Open defects (compressed) | Closes in | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -| Button | danger hover/active tokens, the 48px comment and `ref` forwarding (plus a `testId` passthrough — React 19 types give components no `data-*` index signature) are resolved; the client boundary remains | follow-on | -| AsyncButton | `type` applied after spread (default `button`; explicit `submit` preserved). Prefer `Button` busy API for new sites. | **done** (PR-A) | -| IconButton | disabled encoding uses `controlDisabled` (opacity retired in PR-A) | **done** (PR-A) | -| ToggleSwitch | operable branch requires `aria-label`; opacity disabled retired; knob now travels on `transform` (`translate-x-4`), tokenised duration, reduced-motion opt-out | **done** (motion) | -| Chip | final `appearance`/size API, removable label contract, tap target and full-value title | **done** | -| TextField/SearchField/Select | all three closed, and the row was stale when re-measured 2026-08-08: each folds onto `FormField`, which renders `hint` AND `error` unconditionally and merges `aria-describedby` caller → hint → error; each takes an external `id` and a `ref`. Landed in PR 13, not the PR 7 the row pointed at | **done** | -| Checkbox/RadioGroup | all four closed. Two rows were stale when re-measured on 2026-08-08: ids already sanitise through `optionId`, and the group already carries `hint`/`error`/`describedBy`. Raw dimensions moved to `size-5`/`h-0.5` (`size-4.5` retired by `check:icon-scale`) | **done** | -| Citation/CitationList | required interactive handler, stable-id list keys and static-span labelling (`aria-label` on a role-less span was dropped, losing the currency phrase on print) are resolved; route/source modes remain | follow-on | -| DoseLine | must compose `Quantity` · structured dose model · overdue text + non-colour mark + open action | **done** (PR 6) | -| StatusMark | app-type coupling resolved — `DocumentStatus` is declared here and `@/lib/types` conforms to it (asserted at compile time in the DOM suite), not the reverse; inline styles/raw geometry remain | PR 12 | -| AnswerCard | required verification/state, structured actions and five-state vocabulary landed | **done** (PR 6) | -| AnswerFooter | machine ISO values composed through `DateDisplay` + `MissingValue` landed | **done** (PR 6) | -| PageHeader/Breadcrumb | all resolved. The wrap alone was not enough — the actions track still took full max-content first, so the title column now has a `minmax(20ch,1fr)` floor and the actions track is the one that gives way | **done** | -| Tabs | both resolved. An invalid `value` no longer empties the tab order: the first enabled tab becomes reachable and owns the panel wiring. Reachability only — no `onChange` is fired to repair caller state | **done** | -| Pagination | all resolved. Props clamp (`page=0` no longer emits `onPageChange(-1)`), the control row wraps at 320px, and reaching a boundary hands focus to the current page and announces "Page N of M" through `LiveAnnouncer` | **done** | -| Links | `tone` leak, spread-overridable `download` (now type-omitted AND written after the spread) and the `gap` "animation" (`gap` is not in Tailwind's `transition` list, so it jumped; now a composited `translate-x`) are resolved; new-tab policy still implicit, and `LinkAction` silently ignores `tone` | PR 9 | -| Tooltip | composed handlers/description, string content, OverlayRoot portal, collision and delay contract | **done** (PR 10) | -| Toast | independent tone/priority/persistence, OverlayRoot portal, pause, dedupe and queue contract | **done** (PR 10) | -| Sheet/ConfirmDialog | required names/action labels, portal default, tokened layers/duration and wrapping titles | **done** (PR 10) | -| Disclosure | heading level and print resolved — a collapsed panel is `print:block`, so a section no longer prints as if the guideline never mentioned it; truncation remains. The old docstring's Ctrl-F/print claim was false | PR 11 | -| Progress/StageList | all four resolved: `scaleX`, the theme `animate-shimmer` in place of a hardcoded `1.4s`, a step index clamped to ≥1, and an sr-only `role="status"` sibling in place of `aria-live` on the whole `
    ` | **done** | -| EmptyState | static live-off default with explicit polite/assertive opt-in | **done** (PR 8) | -| AccessibleTable | semantic caption, `MissingValue` cells, dense headers (clipped header keeps its full string as `title`) and the expander (now the registered `Button`, off the local ring-focus recipe) landed; content-role widths remain | PR 6/PR 12 remainder | -| ui-primitives.tsx | 698-line module mixing recipes/actions/feedback/clinical — split. Re-measured 2026-08-12: **157** production files import it (202 including mockups), against the 54 registered components' **31** with product imports; this module, not the registry, is what the product actually runs on | PR 12 | +| Component | Open defects (compressed) | Closes in | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | +| Button | danger hover/active tokens, the 48px comment and `ref` forwarding (plus a `testId` passthrough — React 19 types give components no `data-*` index signature) are resolved; the client boundary remains | follow-on | +| AsyncButton | `type` applied after spread (default `button`; explicit `submit` preserved). Prefer `Button` busy API for new sites. | **done** (PR-A) | +| IconButton | disabled encoding uses `controlDisabled` (opacity retired in PR-A) | **done** (PR-A) | +| ToggleSwitch | operable branch requires `aria-label`; opacity disabled retired; knob now travels on `transform` (`translate-x-4`), tokenised duration, reduced-motion opt-out | **done** (motion) | +| Chip | final `appearance`/size API, removable label contract, tap target and full-value title | **done** | +| TextField/SearchField/Select | all three closed, and the row was stale when re-measured 2026-08-08: each folds onto `FormField`, which renders `hint` AND `error` unconditionally and merges `aria-describedby` caller → hint → error; each takes an external `id` and a `ref`. Landed in PR 13, not the PR 7 the row pointed at | **done** | +| Checkbox/RadioGroup | all four closed. Two rows were stale when re-measured on 2026-08-08: ids already sanitise through `optionId`, and the group already carries `hint`/`error`/`describedBy`. Raw dimensions moved to `size-5`/`h-0.5` (`size-4.5` retired by `check:icon-scale`) | **done** | +| Citation/CitationList | required interactive handler, stable-id list keys and static-span labelling (`aria-label` on a role-less span was dropped, losing the currency phrase on print) are resolved; route/source modes remain | follow-on | +| DoseLine | must compose `Quantity` · structured dose model · overdue text + non-colour mark + open action | **done** (PR 6) | +| StatusMark | app-type coupling resolved — `DocumentStatus` is declared here and `@/lib/types` conforms to it (asserted at compile time in the DOM suite), not the reverse; inline styles/raw geometry remain | PR 12 | +| AnswerCard | required verification/state, structured actions and five-state vocabulary landed. `support` joined them as a third required prop (2026-08-21) — authority-gated `medium` trust previously rendered identically to `high` | **done** (PR 6) | +| AnswerFooter | machine ISO values composed through `DateDisplay` + `MissingValue` landed | **done** (PR 6) | +| PageHeader/Breadcrumb | all resolved. The wrap alone was not enough — the actions track still took full max-content first, so the title column now has a `minmax(20ch,1fr)` floor and the actions track is the one that gives way | **done** | +| Tabs | both resolved. An invalid `value` no longer empties the tab order: the first enabled tab becomes reachable and owns the panel wiring. Reachability only — no `onChange` is fired to repair caller state | **done** | +| Pagination | all resolved. Props clamp (`page=0` no longer emits `onPageChange(-1)`), the control row wraps at 320px, and reaching a boundary hands focus to the current page and announces "Page N of M" through `LiveAnnouncer` | **done** | +| Links | `tone` leak, spread-overridable `download` (now type-omitted AND written after the spread) and the `gap` "animation" (`gap` is not in Tailwind's `transition` list, so it jumped; now a composited `translate-x`) are resolved; new-tab policy still implicit, and `LinkAction` silently ignores `tone` | PR 9 | +| Tooltip | composed handlers/description, string content, OverlayRoot portal, collision and delay contract | **done** (PR 10) | +| Toast | independent tone/priority/persistence, OverlayRoot portal, pause, dedupe and queue contract | **done** (PR 10) | +| Sheet/ConfirmDialog | required names/action labels, portal default, tokened layers/duration and wrapping titles | **done** (PR 10) | +| Disclosure | heading level and print resolved — a collapsed panel is `print:block`, so a section no longer prints as if the guideline never mentioned it; truncation remains. The old docstring's Ctrl-F/print claim was false | PR 11 | +| Progress/StageList | all four resolved: `scaleX`, the theme `animate-shimmer` in place of a hardcoded `1.4s`, a step index clamped to ≥1, and an sr-only `role="status"` sibling in place of `aria-live` on the whole `
      ` | **done** | +| EmptyState | static live-off default with explicit polite/assertive opt-in | **done** (PR 8) | +| AccessibleTable | semantic caption, `MissingValue` cells, dense headers (clipped header keeps its full string as `title`) and the expander (now the registered `Button`, off the local ring-focus recipe) landed; content-role widths remain | PR 6/PR 12 remainder | +| ui-primitives.tsx | 698-line module mixing recipes/actions/feedback/clinical — split. Re-measured 2026-08-12: **157** production files import it (202 including mockups), against the 54 registered components' **31** with product imports; this module, not the registry, is what the product actually runs on. Per-primitive breakdown added 2026-08-21: `Button` **12** importers against 157, with **112** production files still holding a raw `