diff --git a/.design-sync/config.json b/.design-sync/config.json index fb177362cc..8579ab25cd 100644 --- a/.design-sync/config.json +++ b/.design-sync/config.json @@ -57,7 +57,7 @@ "RetrievalStateBanner": "className?: string; onOpenSource: (sourceId: string, locator?: string) => void; state: DegradedAnswerState;", "SafeBoldText": "text: string;", "SearchField": "about?: string; accept?: string; accessKey?: string; alt?: 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 & {}); autoComplete?: import(\"react\").HTMLInputAutoCompleteAttribute; autoCorrect?: string; autoFocus?: boolean; autoSave?: string; capture?: boolean | \"user\" | \"environment\"; checked?: boolean; children?: import(\"react\").ReactNode; className?: string; clearLabel?: 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\"; error?: string; exportparts?: string; fieldClassName?: string; form?: string; formAction?: string | ((formData: FormData) => void | Promise); formEncType?: string; formMethod?: string; formNoValidate?: boolean; formTarget?: string; height?: string | number; hidden?: boolean; hideLabel?: boolean; hint?: string; id?: string; 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; label: string; lang?: string; list?: string; max?: string | number; maxLength?: number; min?: string | number; minLength?: number; multiple?: boolean; 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; onClear?: (() => void); 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; pattern?: string; placeholder?: string; popover?: \"\" | \"auto\" | \"manual\" | \"hint\"; popoverTarget?: string; popoverTargetAction?: \"toggle\" | \"show\" | \"hide\"; prefix?: string; property?: string; radioGroup?: string; readOnly?: boolean; ref?: Ref; rel?: string; required?: boolean; resource?: string; results?: number; rev?: string; role?: import(\"react\").AriaRole; security?: string; size?: number; slot?: string; spellCheck?: (boolean | \"true\" | \"false\"); src?: string; step?: string | number; style?: import(\"react\").CSSProperties; suppressContentEditableWarning?: boolean; suppressHydrationWarning?: boolean; tabIndex?: number; title?: string; translate?: \"yes\" | \"no\"; typeof?: string; unselectable?: \"off\" | \"on\"; value?: string | number | readonly string[]; vocab?: string; width?: string | number;", - "SegmentedControl": "ariaDescribedBy?: string; ariaLabelledBy?: string; className?: string; label?: string; layout?: \"fit\" | \"equal\"; onChange: (value: string) => void; options: readonly SegmentedControlOption[]; value: string;", + "SegmentedControl": "ariaControls?: string; ariaDescribedBy?: string; ariaLabelledBy?: string; className?: string; label?: string; layout?: \"fit\" | \"equal\"; onChange: (value: string) => void; options: readonly SegmentedControlOption[]; value: string;", "Select": "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 & {}); autoComplete?: string; autoCorrect?: string; autoFocus?: boolean; autoSave?: string; 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\"; error?: string; exportparts?: string; fieldClassName?: string; form?: string; hidden?: boolean; hideLabel?: boolean; hint?: string; id?: string; 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; label: string; lang?: string; multiple?: boolean; 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; options: SelectOption[]; part?: string; placeholder?: string; popover?: \"\" | \"auto\" | \"manual\" | \"hint\"; popoverTarget?: string; popoverTargetAction?: \"toggle\" | \"show\" | \"hide\"; prefix?: string; property?: string; radioGroup?: string; ref?: Ref; rel?: string; required?: boolean; resource?: string; results?: number; rev?: string; role?: import(\"react\").AriaRole; security?: string; size?: number; slot?: string; spellCheck?: (boolean | \"true\" | \"false\"); style?: import(\"react\").CSSProperties; suppressContentEditableWarning?: boolean; suppressHydrationWarning?: boolean; tabIndex?: number; title?: string; translate?: \"yes\" | \"no\"; typeof?: string; unselectable?: \"off\" | \"on\"; value?: string | number | readonly string[]; vocab?: string;", "Sheet": "ariaLabel?: string; bodyClassName?: string; children: ReactNode; closeButtonClassName?: string; closeLabel?: string; contentClassName?: string; contentStyle?: CSSProperties; description?: string; descriptionContent?: ReactNode; desktopBackdropClassName?: string; footer?: ReactNode; headerActions?: ReactNode; headerClassName?: string; headerLeading?: ReactNode; id?: string; initialFocusRef?: RefObject; labelledBy?: string; mobilePlacement?: \"bottom\" | \"top\" | \"fullscreen\"; mobileSize?: SheetMobileSize; onClose: () => void; open: boolean; placement?: \"default\" | \"left\"; portal?: boolean; resolveReturnFocusTarget?: (() => HTMLElement | null); returnFocusRef?: RefObject; testId?: string; title?: string; titleAccessory?: ReactNode; titleClassName?: string;", "Skeleton": "about?: string; accessKey?: string; animationDelay?: 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; 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; draggable?: (boolean | \"true\" | \"false\"); enterKeyHint?: \"enter\" | \"done\" | \"go\" | \"next\" | \"previous\" | \"search\" | \"send\"; exportparts?: string; hidden?: boolean; id?: string; 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; 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\"; typeof?: string; unselectable?: \"off\" | \"on\"; vocab?: string;", diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 5df0ad13fc..846e0c297f 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -883,7 +883,7 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 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 | 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 | codex/chat-services-flow-redesign-20260812 | 6f44b92defb91bcd77509bf10337b428be37619c | Services home, results, shortlist, comparison, and referral detail redesign | No findings; changed-area UI, phone contracts, focused unit, build, and RAG fixtures passed; PR-local Windows baseline limitations documented. | 78 focused tests passed post-merge; 185 changed-browser tests; 129 phone contracts; 7 phone-scroll tests; build and RAG fixtures passed | +| 2026-08-11 | claude/filter-popup-design-mockups-x6sbjv | 7b64f2559741a9f353adcf939745831e0daff7db | services filter sheet redesign mockups (3 directions, desktop+phone) | PR #1828 opened; design-scratch route only, no production behaviour change | verify:pr-local (1 pre-existing root-uid test failure, reproduced on origin/main 046feb3), build, check:rag:fixtures, check:bundle-budget both baselines within tolerance, 320px 0px overflow | | 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 | @@ -891,26 +891,27 @@ 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 | claude/filter-popup-design-mockups-x6sbjv | 5e41d164e30e8f5a74b255fbeafd34123385dbb9 | services filter: round-two options study (stop-the-bleed / recommended / presets-evicted) | Pushed to PR #1828; merged babysit fixes to round-one facet semantics; design-scratch only | verify:pr-local (1 pre-existing root-uid failure only), build, check:rag:fixtures, bundle-budget mockups 286.8 KiB within 25% tolerance, counts re-verified vs snapshot, 320px 0px overflow | +| 2026-08-11 | codex/chat-services-flow-redesign-20260812 | 6f44b92defb91bcd77509bf10337b428be37619c | Services home, results, shortlist, comparison, and referral detail redesign | No findings; changed-area UI, phone contracts, focused unit, build, and RAG fixtures passed; PR-local Windows baseline limitations documented. | 78 focused tests passed post-merge; 185 changed-browser tests; 129 phone contracts; 7 phone-scroll tests; build and RAG fixtures passed | | 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 | 1815 | be7461ef1f66357999995acefbeecaf95268e481 | unblock | local-build-pass | MergeTreeClean,UnitCoverage,StaticPRChecks,ContainerImages | | 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-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. | -| 2026-08-12 | codex/implement-verification-policy-changes-for-multiple-tasks | 8177129497eb95105cdd5bba80dbf72a9f88b066 | pr-review | resolved actionable Codex review findings; updated operational-risk patterns; removed outdated metadata from PR body and title | verify:cheap, pr-policy self-test | -| 2026-08-11 | claude/filter-popup-design-mockups-x6sbjv | 7b64f2559741a9f353adcf939745831e0daff7db | services filter sheet redesign mockups (3 directions, desktop+phone) | PR #1828 opened; design-scratch route only, no production behaviour change | verify:pr-local (1 pre-existing root-uid test failure, reproduced on origin/main 046feb3), build, check:rag:fixtures, check:bundle-budget both baselines within tolerance, 320px 0px overflow | -| 2026-08-12 | claude/filter-popup-design-mockups-x6sbjv | 5e41d164e30e8f5a74b255fbeafd34123385dbb9 | services filter: round-two options study (stop-the-bleed / recommended / presets-evicted) | Pushed to PR #1828; merged babysit fixes to round-one facet semantics; design-scratch only | verify:pr-local (1 pre-existing root-uid failure only), build, check:rag:fixtures, bundle-budget mockups 286.8 KiB within 25% tolerance, counts re-verified vs snapshot, 320px 0px overflow | -| 2026-08-12 | PR #1595 / claude/ds-v2-adopt | 590eb6cfb229c5ae0f7a5025352fa871d8321521 | Supersedes 2026-08-03 PR-J clinical-governance review at f9f73c707d9b6b6226fc04d172fef8e426513055; accepted delta through merged PR head | SUPERSEDES the earlier PR-J clinical-governance row for merge evidence. The final delta added the answer-state projection, the two scoped review fixes, and the clinically approved #228 attribution wording. The user accepted that delta without a second clinical-governance review; this record preserves that explicit limitation rather than implying the earlier review covered the final tree. | Final PR head 590eb6cfb229c5ae0f7a5025352fa871d8321521; squashed to main as f4448f8c1 (historical mapping recorded in #232); no new provider or clinical review performed | | 2026-08-12 | claude/rag-canary-test-review-seprbt | bcf357a96fde74d39fc4726ffabb5079a744ef28 | eval-canary review: workflow, compare tooling, snapshot builder, alias tiering, rag-behaviour docs | PR #1843 opened; no retrieval behaviour change; snapshot refresh handed off as /issues #304 | verify:pr-local (green except env-only #296), eval:rag:offline 574/574, focused suites 40/40 | | 2026-08-12 | codex/pr-workflow-safety-230-296 | bc0a491fdf4146775629f9b2b03e2a2cc61bd7cb | pr-1830 unblock | unblocked: merged origin/main (outstanding-issues conflict), PR body RAG impact + governance, resolved Copilot thread; merge-tree clean; required CI in progress | check:outstanding-issues pass; evaluatePullRequestPolicy ok; merge-tree clean; PR policy/mergeability/Change scope in progress | +| 2026-08-12 | claude/segmented-control-count | d5ff3160242986d2fac80ed9a915390b5c69b6e1 | SegmentedControl option hint slot (filter rollout prerequisite) | PR #1848 opened; additive, no call site passes hint yet; fixed a concatenated accessible name (All62 -> All (62)) | lint/typecheck/test green, 80 in ui-v2-components, 56 design-sync with no regen needed, clean build, bundle-budget within tolerance | | 2026-08-12 | claude/design-issues-triage-wnr7k9 | a6bfc6f2707975d9b9c649843e083965c80afb9c | Tier 1 design-issue re-verification: archive #171/#172/#174/#181/#273/#274/#302, correct #293 | docs-only; six rows verified delivered on main, min-h-tap finding refuted as deliberate sm: step-down | verify:pr-local (10/10 green); check:outstanding-issues 138 open/163 archived | | 2026-08-12 | claude/design-issues-triage-wnr7k9 | 970d39bc7023823d3283c660df090659a2f07aec | Full outstanding-issues ledger sweep: 47 rows individually verified against merged main | 19 archived (delivered or duplicate), 10 re-scoped with re-measured evidence, 1 refuted (#293), 4 machine-local rows annotated do-not-close-from-cloud; 98 rows bucketed by blocker, not individually verified | verify:pr-local 10/10 green; check:outstanding-issues 126 open/175 archived, no ids deleted from base | +| 2026-08-12 | claude/filter-contract-global | a0add717c2521c7fdeba4da5b094377014383c3e | global filter contract: lens/facet kinds + docs/filter-contract.md (no rendered change) | PR #1847 opened; additive only, zero call sites touched; fixed an accessible-name leak caught by the new DOM tests | verify:pr-local fully green (no failures), 4 new DOM tests, git diff over all 7 mode files empty | | 2026-08-12 | claude/design-issues-triage-wnr7k9 | 2553b64b2342b0ef2ebef0afde152e76c1252496 | Ledger sweep round 2: open-PR cross-check plus clinical/answer-surface verification | 24 rows flagged IN FLIGHT against 7 open PRs (none had said so); 3 answer-surface rows archived (#166 #208 #216); #250 wave plan re-scoped; main-merge conflict resolved preserving both sides, 23 branch changes re-applied via the writer | verify:pr-local 10/10 green; check:outstanding-issues 121 open/180 archived, no ids deleted from base | | 2026-08-12 | claude/design-issues-triage-wnr7k9 | 586012639565e4d3306b44361ebc5a3bdb3024ad | Land PR #1838 ledger sweep; close #147 mobile CLS by measurement | Merge resolved as union (main renumbered #302/#303 to #306/#307 — not lost, correcting an earlier claim); #306/#307 archived as already-delivered. #147 archived on two identical offline Lighthouse runs: mobile CLS 0.035/0.000/0.013/0.081/0.000, all under 0.1, cause fixed by PR #1616 not this session. #118 updated (browser drift 141-vs-151, wider than recorded); new #308 for desktop /documents/search CLS 0.119 | verify:pr-local 10/10 green; check:outstanding-issues 121 open/185 archived; verify:lighthouse x2 (gate ungraded on browser drift, measurements valid) | +| 2026-08-12 | 1848 | c65b9d91b760862c2ff9d5001974670219c5da02 | full PR diff and unresolved review feedback | P2 hint contrast and live-count width fixes applied | focused Vitest passed (81); focused ESLint passed; design-system contract passed | +| 2026-08-12 | PR #1595 / claude/ds-v2-adopt | 590eb6cfb229c5ae0f7a5025352fa871d8321521 | Supersedes 2026-08-03 PR-J clinical-governance review at f9f73c707d9b6b6226fc04d172fef8e426513055; accepted delta through merged PR head | SUPERSEDES the earlier PR-J clinical-governance row for merge evidence. The final delta added the answer-state projection, the two scoped review fixes, and the clinically approved #228 attribution wording. The user accepted that delta without a second clinical-governance review; this record preserves that explicit limitation rather than implying the earlier review covered the final tree. | Final PR head 590eb6cfb229c5ae0f7a5025352fa871d8321521; squashed to main as f4448f8c1 (historical mapping recorded in #232); no new provider or clinical review performed | | 2026-08-12 | PR-1835 | fe46e5ade8018d21cf15d149711579b1b43c7fb8 | full PR diff and unresolved review feedback | P1/P2 findings fixed during fresh open-PR sweep; rename-only historical review comments dispositioned no-change | focused drift/docs/ledger/migration checks pass; verify:pr-local static+lint+typecheck pass, unrelated Windows unit baseline failures | -| 2026-08-12 | claude/filter-contract-global | a0add717c2521c7fdeba4da5b094377014383c3e | global filter contract: lens/facet kinds + docs/filter-contract.md (no rendered change) | PR #1847 opened; additive only, zero call sites touched; fixed an accessible-name leak caught by the new DOM tests | verify:pr-local fully green (no failures), 4 new DOM tests, git diff over all 7 mode files empty | +| 2026-08-12 | claude/filter-lens-modes | 5cf8871ef122ec9e3d9b25ea65c4643d5a2cf2ba | filter contract PR A: lens adoption across differentials, medication, applications, specifiers | PR #1857 opened; 4 bespoke aria-pressed rails converged onto SegmentedControl with one shared option array per mode; specifiers footerNote fixed (counted results+catalogueMatches while filters govern only results); ResultFilterSheet counted-option accessible name fixed (All8 -> All (8)) on both group kinds; SegmentedControl gained group-level ariaControls so the launcher keeps #launcher-results-panel; dead SpecifierFamilyFilterChips removed; scope segment deliberately deferred to services per filter-contract.md s4 | verify:pr-local all steps green except build, which failed on the /issues #210 dev-types corruption and passed on a clean rebuild; unit suite 6100 passed/4 skipped with one 30s timeout (not an assertion failure) in design-sync-contract under parallel load, passing in isolation 7/7; bundle-budget production 1297.7 KiB and mockups 285.1 KiB both within tolerance on a verified-fresh build; browser proof at 1440/800/390/320px on all four modes, 0px overflow, 48px targets | +| 2026-08-12 | codex/implement-verification-policy-changes-for-multiple-tasks | 8177129497eb95105cdd5bba80dbf72a9f88b066 | pr-review | resolved actionable Codex review findings; updated operational-risk patterns; removed outdated metadata from PR body and title | verify:cheap, pr-policy self-test | | 2026-08-12 | PR-1837 | 401aadc1d24a99067eb0472dc4419d85502e0caa | full PR diff and unresolved review feedback | No P0-P2 findings after current review fixes; existing dispositions verified | focused workflow policy and ledger guards pass after current-main merge | | 2026-08-12 | codex/implement-verification-policy-changes-for-multiple-tasks | 2fed97c601948b959120ff73e88be54af1dc84ba | pr-review | clean | check:pr-policy, verify:pr-local --dry-run | | 2026-08-12 | PR-1836 | eef2e275b7d5944206f1a27e23fdd78d95b383d8 | full PR diff and unresolved review feedback | No P0-P2 findings; prior review feedback already fixed and dispositioned | focused ledger/docs/workflow guards pass after current-main merge | -| 2026-08-12 | claude/segmented-control-count | d5ff3160242986d2fac80ed9a915390b5c69b6e1 | SegmentedControl option hint slot (filter rollout prerequisite) | PR #1848 opened; additive, no call site passes hint yet; fixed a concatenated accessible name (All62 -> All (62)) | lint/typecheck/test green, 80 in ui-v2-components, 56 design-sync with no regen needed, clean build, bundle-budget within tolerance | -| 2026-08-12 | 1848 | c65b9d91b760862c2ff9d5001974670219c5da02 | full PR diff and unresolved review feedback | P2 hint contrast and live-count width fixes applied | focused Vitest passed (81); focused ESLint passed; design-system contract passed | diff --git a/docs/design-system/COMPONENTS.md b/docs/design-system/COMPONENTS.md index 585db62f37..2561da50d4 100644 --- a/docs/design-system/COMPONENTS.md +++ b/docs/design-system/COMPONENTS.md @@ -999,7 +999,7 @@ This generated snapshot is a local source-derived inventory. It does not assert | `RetrievalStateBanner` | answer | yes | yes | inherited-global-root | yes | no | 1 | | `SafeBoldText` | layout | yes | yes | inherited-global-root | yes | no | 8 | | `SearchField` | controls | yes | yes | no | yes | no | 0 | -| `SegmentedControl` | controls | yes | yes | inherited-global-root | yes | no | 3 | +| `SegmentedControl` | controls | yes | yes | inherited-global-root | yes | no | 7 | | `Select` | controls | yes | yes | inherited-global-root | yes | no | 2 | | `Sheet` | layout | yes | yes | inherited-global-root | yes | no | 25 | | `Skeleton` | feedback | yes | yes | inherited-global-root | yes | no | 6 | diff --git a/docs/design-system/adoption-manifest.json b/docs/design-system/adoption-manifest.json index 23b3d5b8e0..e299498df4 100644 --- a/docs/design-system/adoption-manifest.json +++ b/docs/design-system/adoption-manifest.json @@ -1383,14 +1383,22 @@ "sourceExported": true, "entryExported": true, "directImportFiles": [ + "src/components/applications-launcher-page.tsx", + "src/components/clinical-dashboard/differentials-home.tsx", + "src/components/clinical-dashboard/medication-prescribing-workspace.tsx", "src/components/clinical-dashboard/patient-profile-panel.tsx", "src/components/clinical-dashboard/settings-dialog.tsx", - "src/components/in-page-nav/in-page-nav-header.tsx" + "src/components/in-page-nav/in-page-nav-header.tsx", + "src/components/specifiers/specifiers-home-page.tsx" ], "productImportFiles": [ + "src/components/applications-launcher-page.tsx", + "src/components/clinical-dashboard/differentials-home.tsx", + "src/components/clinical-dashboard/medication-prescribing-workspace.tsx", "src/components/clinical-dashboard/patient-profile-panel.tsx", "src/components/clinical-dashboard/settings-dialog.tsx", - "src/components/in-page-nav/in-page-nav-header.tsx" + "src/components/in-page-nav/in-page-nav-header.tsx", + "src/components/specifiers/specifiers-home-page.tsx" ], "designSync": { "listedInSourceMap": true, @@ -1398,7 +1406,11 @@ "preview": ".design-sync/previews/SegmentedControl.tsx", "previewValid": true }, - "testFiles": ["tests/design-sync-visual-exports.test.ts", "tests/ui-v2-components.dom.test.tsx"], + "testFiles": [ + "tests/design-sync-visual-exports.test.ts", + "tests/search-results-header-band.dom.test.tsx", + "tests/ui-v2-components.dom.test.tsx" + ], "baseline": { "targetLayer": "v2", "liveLayer": "v2", diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index 3d8c5b7cff..d81215b3ba 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -198,7 +198,7 @@ removed after current-main verification; it is not missing recommended work. | #165 | P2 | task | Adopt a consolidated answer-home notice block — the studies exist, nothing adopts them | **Outcome:** the answer hero states its safety obligation, its scope, and its verification requirement as one block in one voice. **Detail:** `/mockups/warning-consolidation` (PR #1437) diagnoses today's three stacked notices — the APP-5 privacy warning at 11px muted, a bare `/privacy` link, and an accent-blue `ShieldCheck` capability claim at 14px semibold — and shows the hierarchy is inverted: the least important line is the loudest, and two shields with opposite meanings sit ~40px apart. Three consolidations are drawn at 1440px and 390px. Recommended: **02 Safety card** on the hero (obligation on a warning-tinted top row, everything descriptive in one grey voice below) and **01 Assurance bar** on the docked composer — the same content model at two densities, so one component with a `density` prop covers both. **This is a governance change, not just a design one:** `PrivacyInputNotice` is the single site-wide APP-5 line and renders on the answer, documents and calculators composers, so all three move together; `tests/privacy-ui.test.ts`, `tests/ui-accessibility.spec.ts` and the phone-chrome reserve coverage all assert against the current markup and must change in the same commit; and the PR will need a full `## Clinical Governance Preflight` (the mockup PR correctly did not). **Third study (before/after):** `/mockups/answer-home-proposal` draws the concrete D-direction proposal as a full hero before/after rather than an isolated notice. **Second study (words only):** `/mockups/warning-line` answers a narrower brief — no icon, border, tint or background, one line where width allows. Six variants A-F; line counts measured from the rendered DOM, not asserted. Only B (middot clauses), D (obligation + verify) and F (compressed obligation) hold one line at desktop width, and **none fit one line on a 390px phone while the pinned APP-5 sentence stays verbatim** — 46 characters of obligation plus the 27-character link exceeds the ~60 available at 11px. Recommended there: **D**, the only compliant variant that is both one line and keeps weight-only hierarchy, reached by dropping the scope claim (a capability statement already visible on the answer itself). F fits best but rewrites the pinned obligation to \|No patient-identifiable information.\| and so needs the same privacy sign-off as `#166` plus a matching `tests/privacy-ui.test.ts` update. **Status:** PR #1437 was closed unmerged on 2026-07-30 as a deliberate pause during an owner-authorized ordered merge sweep, to be reopened at its queued place; branch `claude/warning-consolidation-mockups-09jyj7` is preserved and merged onto current `main`; these follow-up rows have been renumbered on each sync because `main` kept claiming the next ids while the PR was paused; the superseded numbers are deliberately not listed, since they now belong to unrelated rows. **Next:** decide block (02 + 01) versus line (D) direction, get wording sign-off for `#166`, then implement behind one component and run `verify:phone-chrome` before `verify:ui`. | session 2026-07-30; PR #1437; `/mockups/warning-consolidation`; `/mockups/warning-line` | 2026-07-30 | | #168 | P2 | rec | Sequential issue ids force every concurrent append to conflict | **Outcome:** two sessions can append to this ledger at the same time without conflicting. **Detail:** ids are allocated read-modify-write against the `issues:next-id` marker inside the file being edited, so two branches both read N and both write N. Because duplicate ids are unacceptable, a union merge driver is unsafe — .gitattributes says so explicitly — which is why this file deliberately has no driver and every overlapping append conflicts by hand. Manual resolution is where rows get dropped: PR #1490 was closed during one and took the only record of four snapshots with it (#152), and ids were renumbered under in-flight work three times in one session (#154, #155). The new writer (`scripts/outstanding-issues.mjs`) removes the mechanical errors but explicitly not this one. **Next:** replace the counter with a collision-free id (ULID, timestamp+suffix, or a content hash), keeping a short display number derived at render time if `#151` reads better than 01JQ…; then a union driver becomes safe to reinstate and concurrent appends stop conflicting at all. A larger variant is one row per file under `docs/issues` with the table generated, which the repo already does for `site-map.md`. **Stop:** do not reinstate `merge=union` while ids are sequential — that combination was tried in PR #1416 and removed for duplicating rows and the marker. Renumbered from this PR's original `#159` because `main` already used `#159` for the duplicated test-file-list finding. | session 2026-07-31; .gitattributes; #154/#155; PR #1524 sync | 2026-07-31 | | #169 | P2 | issue | Local branches carry work that exists on no remote | **Outcome:** committed work is not lost when a machine or worktree is reclaimed. **Detail 2026-07-31:** six `claude/*` branches in this checkout have commits and no `origin/` counterpart. Verified real for `claude/clinical-kb-design-system-333a69` — 57 files / +4069, tip `feat(design-system): v2 token layer, 26 components, browser-crash fix` dated 2026-07-31 17:40, whose added `.design-sync/previews/*.tsx` files are absent from main. Others unverified: `design-sync-db0a54`, `fable-implementation-fc937c`, `frosty-mayer-2c6167`, `issues-133-evidence`. **How to check, because the obvious measure lies:** `git rev-list --count origin/main..` and a three-dot diff both report landed work as unmerged, since this repo squash-merges and the original commits never become ancestors — my own merged branch reported 1 commit and +476 by that measure. Test instead whether files the branch adds exist on main (`git ls-tree origin/main `). **Next:** per branch, push it for review or confirm it is superseded and delete it; do not bulk-delete on the commit count. Sibling of #152, which covers uncommitted work in worktrees rather than unpushed commits on branches. NOT VERIFIABLE FROM A CLOUD SESSION (checked 2026-08-12): this row describes machine-local git or process state. Claude Code on the web runs in a container cloned fresh at session start, so it reports 1 worktree, 0 upstream-less branches and no running dev servers no matter what is true on the machine that raised this. Do NOT close this row from a cloud session — that reading is an artifact of the container, not evidence. Verify from the originating machine. | session 2026-07-31; local branch audit | 2026-07-31 | -| #170 | P2 | task | Phone filter sheets exist for documents and therapy; shared-band adoption remains | **Outcome:** phone filtering opens the repo's own bottom sheet instead of competing for width in the utilities rail. **Current state (2026-08-04):** documents already mounts a Filter documents Sheet, and therapy-compass mounts TherapyFilterSheet; both also pass appliedFilters into SearchResultsHeaderBand. The remaining modes still keep filter controls inline (or page-owned chips/nav), and the shared band has not yet adopted a Filter trigger + Sheet that wraps mobileControls for every mode. **Detail:** SearchResultsHeaderBand still renders mobileControls inline below sm for pages that supply them that way. src/components/ui/sheet.tsx already provides the primitive — bottom sheet on mobile via sheet-up, centred dialog at sm+, safe-area aware, focus-trapped, Escape and backdrop dismiss — so shared-band adoption is the remaining work, not new UI. Design settled in the round-7 study: one Filter control at both widths, badge counting applied filters, sheet titled Filter and sort because a phone bar cannot fit a labelled Sort beside Filter at 390 px. **Next:** replace the inline mobileControls render with a Filter trigger + Sheet containing mobileControls ?? filterControls for the modes that still lack a page-owned sheet, without regressing documents/therapy sheets or removing Sort from sheetless Sort consumers (differentials, forms, services). **Stop:** at least four Playwright specs drive those inline controls directly (document-source-type-select, search-query-ribbon-mobile-control-pair); budget for updating them, and do not ship without verify:ui — PR #1523 showed Production UI critical does gate this surface properly. | Round-7 design study; PR #1523 notes; `src/components/ui/sheet.tsx` | 2026-07-31 | +| #170 | P2 | task | Phone filter sheets exist for documents and therapy; shared-band adoption remains | **Outcome:** phone filtering opens the repo's own bottom sheet instead of competing for width in the utilities rail, and both breakpoints agree on what each dimension means. **Current state (2026-08-12):** the shared contract landed (PR #1847, docs/filter-contract.md) discriminating ResultFilterGroup into lens (one-of-N) and facet (many-of-N); SegmentedControl gained an option hint slot (PR #1848); and the four lens modes now adopt it — differentials, medication, applications and specifiers each replaced a bespoke aria-pressed desktop rail with SegmentedControl and build ONE option array shared by rail and sheet. That closes the defect where the same dimension claimed one-of-N in the sheet (role=radiogroup) and many-of-N on the rail (aria-pressed). Specifiers' footerNote also fixed: it reported results.length + catalogueMatches.length while its groups narrow only results. **Remaining:** formulation (derive domains, convert to facet, evict the query-replacing pattern presets), services (facet index + selection state + URL + the scope segment), factsheets (evict category presets to the composer), therapy-compass (converge onto the shared trigger/sheet; its onClearAll still wipes the query, which the contract forbids), documents last (port find-a-filter + collapse up as the >20 tier). **Stop:** the scope segment is deliberately NOT retrofitted to the four lens modes yet — filter-contract.md section 4 gates it on a catalogue meaningfully larger than the result set, and it lands with services where that is true. Do not ship a mode conversion without browser proof at 390px and 320px; these change rendered output, unlike #1847. | Round-7 design study; PR #1523 notes; `src/components/ui/sheet.tsx` | 2026-07-31 | | #175 | P2 | task | Therapy modality is now null on all 205 records and needs curation or removal | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: public/therapy-compass-data/therapies-index.json holds 205 records and 0 carry a modality value, exactly as described. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** the Therapy detail and recommend screens either show a curated modality or stop carrying the field at all. **Detail:** the source catalogue derived `modality` from each record's own tag list — all 205 records had one, every value was also present in that record's `tags`, and the whole catalogue collapsed to CBT/ACT/DBT. It mislabelled the treatments it could not describe: ECT and rTMS as "ACT", Psychoanalysis and Psychodynamic Psychotherapy as "CBT", MBT and TFP as "DBT". Pre-existing on main, surfaced by the PR #1489 review. The generator emits it only when the source curates a value that is not already a tag, which today means null for 205/205 on the index projections *and* the full catalogue the detail/recommend screens load (`catalogue: "full"`), so the two chips (`detail-screen.tsx:49`, `recommend-screen.tsx:115`) never render and `select.ts:117` contributes no same-modality point. Removal was provably search-neutral: `src/lib/therapies.ts` scores with boolean `haystack.includes(token)`, not term frequency, and every modality value was already contributed by `tags.join(" ")` in the same haystack. **Next:** one of two — curate real modality values in `public/therapy-compass-data/therapies.json` (clinical work, needs the psychiatrist), or drop the field from `types.ts`, `src/lib/therapies.ts`, the two chips and `select.ts`. **Stop:** do not reinstate the tag-derived value to make the chips reappear; a guess rendered as curated fact is the defect. `tests/therapy-compass-pathways.test.ts` pins the echo invariant on both the index and the full catalogue asset. Renumbered from this PR's original `#169` because `main` claimed `#169`–`#174` while the branch was open. | PR #1489 review remediation; PR #1532; session 2026-07-31 | 2026-07-31 | | #177 | P3 | rec | Therapy catalogue aliases duplicate 2.53 MB of bytes instead of pointing at the hashed file | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: public/therapy-compass-data/ holds THREE copies of the catalogue (therapies.json plus two hashed assets), which is the grace-generation case this row predicted. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** the unversioned catalogue aliases stop costing a second copy of every payload in the repo and the image. **Detail:** `public/therapy-compass-data/` ships each catalogue twice — `therapies.json` (2,529,429 bytes) alongside `therapies..json` with identical content, and the same for the index and home projections. Git stores one blob for each identical pair, so history is unaffected, but the working tree and every Docker image carry both. PR #1489's remediation adds a one-deploy grace generation, so a stem whose content changed now holds three copies until the next regeneration. **Next:** serve the alias as a Next rewrite to the current hashed filename instead of writing a duplicate file — the alias would keep its `max-age=0, must-revalidate` header (the hashed target keeps `immutable`, so the rewrite must not inherit it) and the generator would stop copying bytes. Check that a rewrite still satisfies the alias-equality assertions in `check:therapy-data-index` and `tests/therapy-compass-mode-wiring.test.ts`, which currently compare file bytes. **Stop:** do not drop the aliases outright — they are the fallback `useTherapyData` uses when a pre-deploy bundle names a hash that no longer exists. Renumbered from this PR's original `#171`. | PR #1489 review remediation; PR #1532; session 2026-07-31 | 2026-07-31 | | #178 | P2 | rec | pr-policy does not flag operational risk bundled with clinical or UI risk | IN FLIGHT 2026-08-12 in PR #1837 (harden verification & PR policy guards). Checked against the open-PR list during the full ledger sweep. Do NOT start this row while that PR is open — duplicating a queued conversion is the exact failure #292 records, and it has happened twice. Re-verify this row against main after that PR merges, and close it there rather than here. **Outcome:** a PR that mixes operational-risk paths with clinical or UI risk is called out before it merges, because squash-merging that mix destroys per-item revert. **Detail:** `classifyPullRequestFiles` already computes `operationalRisk`, `clinicalRisk`, `ragRanking` and `ui` independently, but nothing reacts to the combination. AGENTS.md's "PR bundling" section forbids bundling anything once `operationalRisk` is true; the classifier is where that could be enforced. PR #1489 is the worked example: 33 files spanning `.github/workflows/ci.yml`, both Dockerfiles, a rewrite of the bundle-budget gate, a phone-chrome scroll change and a therapy data restructure, merged as one squash (945148251). Reverting any single item now means hand-reverting hunks out of the squash commit, because the branch commits are unreachable. The remediation PR for that review repeats the pattern on a smaller scale (clinical data plus a one-line ci.yml timeout), which is why this is a recommendation rather than a hard gate — the right severity is probably a warning that names the mixed classes, not a merge block. **Next:** emit an advisory line from `evaluatePullRequestPolicy` when `operationalRisk` coincides with `clinicalRisk` or `ui`, listing which paths drove each; decide separately whether it ever blocks. Cover it in the `--self-test` block. **Stop:** do not make it a hard failure in the same change that introduces it — land the signal first and see how often it fires. Renumbered from this PR's original `#172`. | PR #1489 review remediation; PR #1532; session 2026-07-31 | 2026-07-31 | diff --git a/src/components/applications-launcher-page.tsx b/src/components/applications-launcher-page.tsx index 8866d552d1..ef5c727899 100644 --- a/src/components/applications-launcher-page.tsx +++ b/src/components/applications-launcher-page.tsx @@ -34,6 +34,7 @@ import { } from "@/components/clinical-dashboard/result-filter-control"; import { useSearchCommand } from "@/components/clinical-dashboard/search-command-context"; import { useFavouritesAccess } from "@/components/clinical-dashboard/use-favourites-access"; +import { SegmentedControl } from "@/components/ui/segmented-control"; import { cn, EmptyState } from "@/components/ui-primitives"; import { Chip, type ChipStatusTone } from "@/components/ui/chip"; import { Sheet } from "@/components/ui/sheet"; @@ -390,31 +391,20 @@ function FilterTabs({ const [filterOpen, setFilterOpen] = useState(false); // `more` is the desktop "All" tab's overflow state, not a category of its own. const resolvedFilter: LauncherFilter = activeFilter === "more" ? "all" : activeFilter; + // One array for the desktop rail and the phone sheet. The categories partition + // the tool list, so this is a lens at both breakpoints — the rail used to say + // many-of-N with `aria-pressed` while the sheet said one-of-N. + const launcherFilterOptions = desktopFilters.map((filter) => ({ value: filter.id, label: filter.label })); return ( <> -
- {desktopFilters.map((filter) => { - const active = filter.id === activeFilter || (filter.id === "all" && activeFilter === "more"); - return ( - - ); - })} +
+
{/* The phone half of the same control. This launcher renders no results band, so the trigger sits inline here rather than in a ribbon slot — @@ -440,7 +430,7 @@ function FilterTabs({ id: "category", label: "Category", value: resolvedFilter, - options: desktopFilters.map((filter) => ({ value: filter.id, label: filter.label })), + options: launcherFilterOptions, onChange: (value) => { setFilterOpen(false); onFilterChange(value); diff --git a/src/components/clinical-dashboard/differentials-home.tsx b/src/components/clinical-dashboard/differentials-home.tsx index 0f07fb8b07..d7a23e44c3 100644 --- a/src/components/clinical-dashboard/differentials-home.tsx +++ b/src/components/clinical-dashboard/differentials-home.tsx @@ -28,8 +28,10 @@ import { SearchResultsHeaderBand } from "@/components/clinical-dashboard/search- import { ResultFilterSheet, ResultFilterTrigger, + type ResultFilterOption, resultFilterGroup, } from "@/components/clinical-dashboard/result-filter-control"; +import { SegmentedControl } from "@/components/ui/segmented-control"; import { UniversalSearchAlsoMatches } from "@/components/clinical-dashboard/universal-search-also-matches"; import { useDifferentialSearch } from "@/components/clinical-dashboard/use-differential-catalog"; import { useResultSort } from "@/components/use-result-sort"; @@ -251,73 +253,6 @@ function StatusBadge({ status, className }: { status: DifferentialRecord["status type KindFilter = "all" | "presentation" | "diagnosis"; -const resultTypeTabFocusRing = - "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]"; - -function ResultTypeTabs({ - activeFilter, - onFilterChange, - allCount, - presentationCount, - diagnosisCount, -}: { - activeFilter: KindFilter; - onFilterChange: (filter: KindFilter) => void; - allCount: number; - presentationCount: number; - diagnosisCount: number; -}) { - const tabs = [ - { id: "all" as const, label: "All", count: allCount }, - { id: "presentation" as const, label: "Presentations", count: presentationCount }, - { id: "diagnosis" as const, label: "Diagnoses", count: diagnosisCount }, - ]; - - // Single-select filters over one results list — modeled as a toggle group - // (role="group" + aria-pressed), not ARIA tabs (which would need tabpanels, - // aria-controls, and roving tabindex for content that does not exist here). - return ( -
- {tabs.map((tab) => { - const active = activeFilter === tab.id; - return ( - - ); - })} -
- ); -} - function MatchBadge({ label }: { label: string }) { // Match quality is a relevance signal, not a safety one — keep it in the // accent family so red stays reserved for the emergent status badges. @@ -870,6 +805,18 @@ function SearchResultsView({ const presentationCount = results.filter((result) => result.kind === "presentation").length; const diagnosisCount = results.length - presentationCount; + // One array feeds both breakpoints. The counts used to be declared twice — + // as `count` on the desktop rail and again as `hint` in the sheet — which is + // how the two could drift, and how the same dimension came to claim one-of-N + // in the sheet (`role="radiogroup"`) and many-of-N on the rail (`aria-pressed`). + const kindFilterOptions = useMemo>>( + () => [ + { value: "all", label: "All", hint: String(results.length) }, + { value: "presentation", label: "Presentations", hint: String(presentationCount) }, + { value: "diagnosis", label: "Diagnoses", hint: String(diagnosisCount) }, + ], + [diagnosisCount, presentationCount, results.length], + ); const visibleResults = useMemo( () => sortResultItems( @@ -1009,12 +956,11 @@ function SearchResultsView({ /> } filterControls={ - } /> @@ -1031,11 +977,7 @@ function SearchResultsView({ id: "result-type", label: "Show", value: kindFilter, - options: [ - { value: "all", label: "All", hint: String(results.length) }, - { value: "presentation", label: "Presentations", hint: String(presentationCount) }, - { value: "diagnosis", label: "Diagnoses", hint: String(diagnosisCount) }, - ], + options: kindFilterOptions, onChange: setKindFilter, }), ]} diff --git a/src/components/clinical-dashboard/medication-prescribing-workspace.tsx b/src/components/clinical-dashboard/medication-prescribing-workspace.tsx index d465a5e6e0..eb6e8d0c27 100644 --- a/src/components/clinical-dashboard/medication-prescribing-workspace.tsx +++ b/src/components/clinical-dashboard/medication-prescribing-workspace.tsx @@ -42,6 +42,7 @@ import { import { medicationIdentityBadges, type MedicationRecord } from "@/lib/medications"; import { SEMANTIC_TONE_META } from "@/lib/semantic-tone"; import { isDeployedClinicalKb } from "@/lib/deployed-app"; +import { SegmentedControl, type SegmentedControlOption } from "@/components/ui/segmented-control"; import { cn, EmptyState, pageContainer } from "@/components/ui-primitives"; type MedicationPrescribingWorkspaceProps = { @@ -262,50 +263,6 @@ function resultMatchesFilter(result: MedicationResult, filter: MedicationResultF ); } -function FilterStrip({ - activeFilter, - counts, - onFilterChange, -}: { - activeFilter: MedicationResultFilter; - counts: Record; - onFilterChange: (filter: MedicationResultFilter) => void; -}) { - return ( -
- {medicationResultFilters.map((filter) => { - const active = activeFilter === filter.id; - const Icon = filter.icon; - return ( - - ); - })} -
- ); -} - function ResultToneIcon({ result, accent }: { result: MedicationResult; accent?: string }) { const tone = result.tone === "teal" ? "teal" : result.tone === "blue" ? "blue" : "slate"; if (accent) { @@ -455,6 +412,18 @@ function MedicationResults({ }; }, [activeFilter, catalog.data, profile, profileEmpty]); const resultCount = rows.length; + // One array feeds the desktop rail and the phone sheet, so the counts cannot + // drift between them and the dimension states one-of-N at both breakpoints. + const filterOptions = useMemo>>( + () => + medicationResultFilters.map((filter) => ({ + value: filter.id, + label: filter.label, + icon: filter.icon, + hint: String(counts[filter.id]), + })), + [counts], + ); // The match-quality badge only earns its slot when it differentiates: hide it on // "Exact clinical fit" rows when every visible row says the same thing. const showMatchBadge = useMemo(() => new Set(rows.map((row) => row.result.match)).size > 1, [rows]); @@ -485,7 +454,17 @@ function MedicationResults({ onToggle={() => setFilterOpen((current) => !current)} /> } - filterControls={} + filterControls={ + + } /> {/* Phone-only by construction: the trigger that opens it lives in the ribbon's `mobileControls` slot, which the band hides from `sm` up. */} @@ -500,11 +479,7 @@ function MedicationResults({ id: "result-filter", label: "Show", value: activeFilter, - options: medicationResultFilters.map((filter) => ({ - value: filter.id, - label: filter.label, - hint: String(counts[filter.id]), - })), + options: filterOptions, onChange: setActiveFilter, }), ]} diff --git a/src/components/clinical-dashboard/result-filter-control.tsx b/src/components/clinical-dashboard/result-filter-control.tsx index bd47b194fd..edca5f8472 100644 --- a/src/components/clinical-dashboard/result-filter-control.tsx +++ b/src/components/clinical-dashboard/result-filter-control.tsx @@ -334,6 +334,13 @@ function FilterRadioGroup({ group, panelId }: { group: ResultFilterLensGroup; pa type="button" role="radio" aria-checked={selected} + // Without this the label and hint spans concatenate to "All8" in + // the accessible name — the name computation normalises the + // inter-element whitespace away, so a text-node separator cannot + // fix it. Same defect and same fix as SegmentedControl, which is + // what lets one shared option array announce identically on the + // desktop rail and in this sheet. + aria-label={option.hint ? `${option.label} (${option.hint})` : undefined} // `aria-disabled` rather than `disabled`: a real disabled // button leaves the tab order, so a keyboard or screen-reader // user loses the option entirely and never learns why. Kept @@ -435,6 +442,9 @@ function FilterFacetGroup({ group, panelId }: { group: ResultFilterFacetGroup; p aria-pressed={selected} aria-disabled={deadEnd || undefined} aria-describedby={deadEnd ? deadEndDescId : undefined} + // Same concatenation defect as the lens chips above: a facet + // count would otherwise be announced as "Crisis12". + aria-label={option.hint ? `${option.label} (${option.hint})` : undefined} onClick={() => { if (deadEnd) return; group.onToggle(option.value); diff --git a/src/components/specifiers/specifier-ui.tsx b/src/components/specifiers/specifier-ui.tsx index aaee82be9e..9610552f8b 100644 --- a/src/components/specifiers/specifier-ui.tsx +++ b/src/components/specifiers/specifier-ui.tsx @@ -4,8 +4,7 @@ import { ArrowRight, CheckCircle2, ChevronsUpDown, Info, Minus, ShieldAlert, Tag import { InformationPageBreadcrumbs, InformationPageShell } from "@/components/information-page-shell"; import { cn, eyebrowText } from "@/components/ui-primitives"; -import type { SpecifierFamily, SpecifierRecord } from "@/lib/specifiers"; -import { specifierFamilies } from "@/lib/specifiers"; +import type { SpecifierRecord } from "@/lib/specifiers"; import type { SpecifierSourceStatus } from "@/lib/specifiers-search-index"; export const specifierCard = @@ -53,45 +52,6 @@ export function SpecifierWordingPathway() { ); } -const familyChipBase = - "inline-flex min-h-tap shrink-0 items-center rounded-lg border px-3 text-xs font-semibold transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] sm:text-sm"; -const familyChipActive = - "border-[color:var(--clinical-accent)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)]"; -const familyChipIdle = - "border-[color:var(--border)] bg-[color:var(--surface-raised)] text-[color:var(--text-muted)] hover:border-[color:var(--border-strong)] hover:text-[color:var(--text-heading)]"; - -export function SpecifierFamilyFilterChips({ - value, - onChange, -}: { - value: "all" | SpecifierFamily; - onChange: (value: "all" | SpecifierFamily) => void; -}) { - return ( -
- {specifierFamilies.map((option) => { - const active = value === option.id; - return ( - - ); - })} -
- ); -} - export function SpecifierDiagnosisFilter({ value, onChange, diff --git a/src/components/specifiers/specifiers-home-page.tsx b/src/components/specifiers/specifiers-home-page.tsx index b38a056eb4..0d2637a840 100644 --- a/src/components/specifiers/specifiers-home-page.tsx +++ b/src/components/specifiers/specifiers-home-page.tsx @@ -16,12 +16,12 @@ import { CategoryTag, ReviewStatusBadge, SpecifierDiagnosisFilter, - SpecifierFamilyFilterChips, SpecifierMatchCard, SpecifierPageShell, SpecifierSafetyNote, specifierCard, } from "@/components/specifiers/specifier-ui"; +import { SegmentedControl } from "@/components/ui/segmented-control"; import { cn } from "@/components/ui-primitives"; import { appModeHomeHref } from "@/lib/app-modes"; import { modeHomeDesktopComposerSlotId } from "@/lib/mode-home-composer"; @@ -197,6 +197,14 @@ function SpecifierResults({ query }: { query: string }) { // shows "0 matches" with an empty-state banner above real results. const catalogueMatches = useMemo(() => searchSpecifierCatalog(query).slice(0, CATALOGUE_RESULT_LIMIT), [query]); const totalMatches = results.length + catalogueMatches.length; + // One array for the desktop rail and the phone sheet. The full label is safe + // at both: the band hides `filterControls` below `sm` whenever a page also + // supplies `mobileControls`, so the chips' phone-only short label could never + // render and the two breakpoints never showed different text. + const familyOptions = useMemo( + () => specifierFamilies.map((option) => ({ value: option.id, label: option.label })), + [], + ); return ( @@ -221,7 +229,13 @@ function SpecifierResults({ query }: { query: string }) { } filterControls={
- +
} @@ -241,7 +255,7 @@ function SpecifierResults({ query }: { query: string }) { id: "family", label: "Family", value: family, - options: specifierFamilies.map((option) => ({ value: option.id, label: option.shortLabel })), + options: familyOptions, onChange: setFamily, }), resultFilterGroup({ @@ -260,7 +274,11 @@ function SpecifierResults({ query }: { query: string }) { setDiagnosis(""); } } - footerNote={`${totalMatches} showing`} + // `results`, not `totalMatches`. Both groups narrow only the curated + // list; `catalogueMatches` keys on the query alone, so reporting the sum + // claimed the sheet scoped a total its filters could not move — the + // count sat still while a filter visibly changed the list. + footerNote={`${results.length} showing`} /> {totalMatches === 0 ? ( diff --git a/src/components/ui/segmented-control.tsx b/src/components/ui/segmented-control.tsx index 2454f58de1..706da0ed99 100644 --- a/src/components/ui/segmented-control.tsx +++ b/src/components/ui/segmented-control.tsx @@ -31,6 +31,14 @@ export type SegmentedControlProps = AccessibleName & { onChange: (value: T) => void; options: ReadonlyArray>; ariaDescribedBy?: string; + /** + * The region this rail filters. Set it when the control governs a panel + * elsewhere in the page — the launcher's category rail pointed each of its + * buttons at `#launcher-results-panel`, and converging on this primitive must + * not silently drop that association. One group-level reference, because the + * radiogroup is the control; the individual radios are its options. + */ + ariaControls?: string; layout?: "fit" | "equal"; className?: string; }; @@ -42,6 +50,7 @@ export function SegmentedControl({ label, ariaLabelledBy, ariaDescribedBy, + ariaControls, layout = "fit", className, }: SegmentedControlProps) { @@ -91,6 +100,7 @@ export function SegmentedControl({ aria-label={label} aria-labelledby={ariaLabelledBy} aria-describedby={ariaDescribedBy} + aria-controls={ariaControls} onKeyDown={onKeyDown} data-layout={layout} className={cn( diff --git a/tests/favourites-auth-gate.dom.test.tsx b/tests/favourites-auth-gate.dom.test.tsx index e5be94c0ee..0b34deab21 100644 --- a/tests/favourites-auth-gate.dom.test.tsx +++ b/tests/favourites-auth-gate.dom.test.tsx @@ -211,7 +211,8 @@ describe("favourites auth gate DOM", () => { authSession.status = "signed_out"; render(); - expect(screen.queryByRole("button", { name: "Saved" })).toBeNull(); + // The category rail is a lens: its options are radios, not buttons. + expect(screen.queryByRole("radio", { name: "Saved" })).toBeNull(); expect(screen.queryByTestId("tool-shortcut-favourites")).toBeNull(); expect(screen.queryByText("Saved workflows")).toBeNull(); expect(screen.getByTestId("tools-hub")).toBeVisible(); @@ -220,7 +221,7 @@ describe("favourites auth gate DOM", () => { it("keeps Tools Saved workflows available when Favourites access is granted", () => { render(); - expect(screen.getByRole("button", { name: "Saved" })).toBeVisible(); + expect(screen.getByRole("radio", { name: "Saved" })).toBeVisible(); expect(screen.getByTestId("tool-shortcut-favourites")).toBeVisible(); expect( toolCatalogRecordsForSession({ authenticated: true, demoMode: false }).some((t) => t.id === "favourites"), diff --git a/tests/medication-prescribing-workspace.dom.test.tsx b/tests/medication-prescribing-workspace.dom.test.tsx index 66b01ccb2a..0fbb69004c 100644 --- a/tests/medication-prescribing-workspace.dom.test.tsx +++ b/tests/medication-prescribing-workspace.dom.test.tsx @@ -107,8 +107,10 @@ function rowVisible(name: string): boolean { return screen.queryAllByText(name).length > 0; } +// The rail is a one-of-N lens, so its options are radios in a radiogroup, not +// pressed toggles. The accessible name carries the count as "Best (3)". function filterButton(label: string): HTMLElement { - return screen.getByRole("button", { name: new RegExp(`^${label}`, "i") }); + return screen.getByRole("radio", { name: new RegExp(`^${label}`, "i") }); } afterEach(() => { @@ -142,8 +144,8 @@ describe("MedicationPrescribingWorkspace — result filter strip", () => { it("defaults to the Best lens with every result shown", () => { renderWorkspace(); - expect(filterButton("Best")).toHaveAttribute("aria-pressed", "true"); - expect(filterButton("Safety")).toHaveAttribute("aria-pressed", "false"); + expect(filterButton("Best")).toHaveAttribute("aria-checked", "true"); + expect(filterButton("Safety")).toHaveAttribute("aria-checked", "false"); expect(rowVisible("Clozapine")).toBe(true); expect(rowVisible("Lithium")).toBe(true); expect(rowVisible("Sertraline")).toBe(true); @@ -153,8 +155,8 @@ describe("MedicationPrescribingWorkspace — result filter strip", () => { renderWorkspace(); fireEvent.click(filterButton("Indication")); - expect(filterButton("Indication")).toHaveAttribute("aria-pressed", "true"); - expect(filterButton("Best")).toHaveAttribute("aria-pressed", "false"); + expect(filterButton("Indication")).toHaveAttribute("aria-checked", "true"); + expect(filterButton("Best")).toHaveAttribute("aria-checked", "false"); expect(rowVisible("Clozapine")).toBe(true); expect(rowVisible("Lithium")).toBe(true); // Sertraline is a "Related match", so it leaves the Indication lens. @@ -165,7 +167,7 @@ describe("MedicationPrescribingWorkspace — result filter strip", () => { renderWorkspace(); fireEvent.click(filterButton("Monitor")); - expect(filterButton("Monitor")).toHaveAttribute("aria-pressed", "true"); + expect(filterButton("Monitor")).toHaveAttribute("aria-checked", "true"); expect(rowVisible("Lithium")).toBe(true); expect(rowVisible("Clozapine")).toBe(false); expect(rowVisible("Sertraline")).toBe(false); diff --git a/tests/search-results-header-band.dom.test.tsx b/tests/search-results-header-band.dom.test.tsx index 58109eb92d..8041753518 100644 --- a/tests/search-results-header-band.dom.test.tsx +++ b/tests/search-results-header-band.dom.test.tsx @@ -587,6 +587,48 @@ describe("ResultFilterSheet facet groups", () => { expect(onToggle).toHaveBeenCalledWith("Affect"); }); + // The label and hint spans concatenate in the accessible name, and the name + // computation normalises inter-element whitespace away, so a text-node + // separator does not fix it. Both group kinds carry counts, so both need the + // explicit name — and it must match SegmentedControl's, because a mode now + // hands one option array to the desktop rail and this sheet. + it("names a counted option 'label (hint)' rather than concatenating to 'All8'", () => { + render( + (), + options: [{ value: "Crisis", label: "Crisis", hint: "12" }], + onToggle: vi.fn(), + }), + ]} + />, + ); + + expect(screen.getByRole("radio", { name: "All (8)" })).toBeInTheDocument(); + expect(screen.getByRole("radio", { name: "Presentations (1)" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Crisis (12)" })).toBeInTheDocument(); + // A hintless option keeps its plain label — every pre-count call site. + expect(screen.queryByRole("radio", { name: "All8" })).not.toBeInTheDocument(); + }); + // Every toggle must be individually reachable. The lens groups' single tab // stop is correct there because arrowing replaces; here it would strand // options behind a key binding that must not commit anything. diff --git a/tests/ui-specifiers.spec.ts b/tests/ui-specifiers.spec.ts index f11d41de4a..1d11da3d65 100644 --- a/tests/ui-specifiers.spec.ts +++ b/tests/ui-specifiers.spec.ts @@ -79,7 +79,9 @@ test("searches clinical language without provenance fields and carries a result await expect(queryRibbon.getByRole("group", { name: "Filter specifier results" })).toBeVisible(); await expect(page.getByText(/Results ranked by text relevance/i)).toHaveCount(0); await expect(page.getByText("Top match", { exact: true })).toBeVisible(); - await expect(page.getByRole("group", { name: "Filter by specifier family" })).toBeVisible(); + // A lens, so the rail is a radiogroup rather than a toggle group — the sheet + // already said one-of-N and the desktop rail now agrees with it. + await expect(page.getByRole("radiogroup", { name: "Filter by specifier family" })).toBeVisible(); await expect(page.getByRole("combobox", { name: "Filter by diagnosis" })).toBeVisible(); await expect(page.getByText("Best fit", { exact: true })).toHaveCount(0); await expect(page.getByText(/clinical fit/i)).toHaveCount(0); diff --git a/tests/ui-v2-components.dom.test.tsx b/tests/ui-v2-components.dom.test.tsx index 1fa5c48afd..bb00fd9fa1 100644 --- a/tests/ui-v2-components.dom.test.tsx +++ b/tests/ui-v2-components.dom.test.tsx @@ -409,6 +409,41 @@ describe("SegmentedControl", () => { expect(screen.getByRole("radio", { name: "All" })).toBeInTheDocument(); }); + it("points the group, not each radio, at the region it filters", () => { + render( + undefined} + options={[ + { value: "all", label: "All" }, + { value: "assess", label: "Assess" }, + ]} + ariaControls="launcher-results-panel" + />, + ); + + expect(screen.getByRole("radiogroup")).toHaveAttribute("aria-controls", "launcher-results-panel"); + // The radios are options of the control, not separate controllers of the + // panel — the launcher's old rail put aria-controls on all six buttons. + for (const radio of screen.getAllByRole("radio")) { + expect(radio).not.toHaveAttribute("aria-controls"); + } + }); + + it("omits aria-controls entirely when the rail governs no separate region", () => { + render( + undefined} + options={[{ value: "all", label: "All" }]} + />, + ); + + expect(screen.getByRole("radiogroup")).not.toHaveAttribute("aria-controls"); + }); + it("keeps a controlled disabled value checked instead of remapping to the first enabled option", () => { render(