diff --git a/docs/design-system/ADOPTION.md b/docs/design-system/ADOPTION.md
index b734870ab..8219dd6f8 100644
--- a/docs/design-system/ADOPTION.md
+++ b/docs/design-system/ADOPTION.md
@@ -4,10 +4,10 @@
of truth is `adoption-contract.json`; the generated manifest and marked tables in this document
must match it exactly.
-- **Date:** 5 August 2026
-- **Current state:** 53 visual references are locally registered; all 47 production page routes
- are owned across 14 surface families, with 55 route/component roots scanned; every declared
- root remains on the compatibility shell.
+- **Date:** 12 August 2026
+- **Current state:** 54 visual references are locally registered; all 51 production page routes
+ are owned across 14 surface families, with 59 route/component roots scanned; every declared
+ root uses the v2 shell and has declared proof with no committed visual baseline.
- **Phase 1 blockers resolved first, in their own commits:** `#207` ungrounded `AnswerState`,
`#208` clipboard composition. See [SPEC.md](SPEC.md) §13 PR 6 clinical review, blockers 1–2.
- **Companions:** [SPEC.md](SPEC.md) · [COMPONENTS.md](COMPONENTS.md) ·
@@ -137,7 +137,7 @@ src/components/DocumentViewer.tsx
navigation, zoom, fit, rotation, the viewing aid and fullscreen, and `PdfCanvasViewer` renders
source pixels only. There is exactly one toolbar and one page readout in the viewer, and the
contract test `tests/document-frame-contract.test.ts` holds that. It is not yet design-sync
-registered among the 53 published visual exports. Do not invent a second frame or add inversion/filters; route document renders through
+registered among the 54 published visual exports. Do not invent a second frame or add inversion/filters; route document renders through
the existing viewer + frame. Keep every `role="alert"` semantic; route announcements through
the announcer policy rather than deleting roles, because many `role="status"` sites are
implicit polite live regions with no `aria-live` attribute and removing the role without an
@@ -212,6 +212,19 @@ over passages no model wrote. `composeAnswerClipboardText()` now takes an explic
tier flag, which both product callers pass. Pinned in
`tests/answer-clipboard-product-path.dom.test.tsx` and `tests/answer-clipboard-composition.test.ts`.
+**Single answer-copy payload contract (`#234`).** The live dashboard answer, prior thread turn,
+and inline answer result all route clipboard output through
+`src/components/clinical-dashboard/answer-copy-payload.ts`; a fourth copy surface must reuse this
+module rather than assemble answer text independently. `resolveAnswerSources` preserves the
+populated cited source set and falls back only when it is absent/empty;
+`answerStateForAnswer` creates the shared safety projection; `citedSourcesOnly` prevents uncited
+retrieval candidates from changing attribution; `singleDocumentClipboardMetadata` emits an audit
+line only for a single cited document; and `buildAnswerClipboardText` is the final composer that
+preserves render text while adding attribution, state caveats, and eligible provenance. Source-only
+attribution comes from the answer quality tier, not `AnswerState.kind`, because an extractive answer
+may also be ungrounded. `tests/answer-copy-payload.test.ts` and
+`tests/answer-clipboard-product-path.dom.test.tsx` pin both the helper and all three callers.
+
**Clinical-owner wording approval (2026-08-05).** The clinical owner formally approved the fixed
clinician-facing `responsive-compact` phone wording in
`src/components/ui/verification-notice.tsx` exactly as shipped on this branch. The approval covers
@@ -225,7 +238,7 @@ clamp, hidden instruction, retrieval change, visual baseline or patient/plain-la
## 3 · Registration disposition
The Phase 1 local registration gap is closed. The visual symbols below are now included in the
-53-component source-derived registry; support APIs remain entry-only by design:
+54-component source-derived registry; support APIs remain entry-only by design:
| Module | Symbols | First adopting surface |
| --------------------------------------------- | --------------------------------------------------------------- | ------------------------ |
@@ -316,6 +329,25 @@ chrome differs between them.
| provenance | the document-admin source metadata block · the answer support caution (pre-adoption baseline) · a differentials source-status chip · the source preview popover |
| answer | a ready grounded answer · an **ungrounded** answer showing the preserved caution (`#207`) · stale-evidence and source-only banners · a copied-clipboard sample (`#208`) · progress / empty / error / streaming |
+### 7.1 Adoption evidence recorded for this pass (`#221`, `#235`, `#238`, `#245`)
+
+This PR records executable evidence rather than committing image baselines. The generated adoption
+manifest remains `baseline: not-committed`, and no Playwright snapshot PNG is an adoption claim.
+
+| Surface | Current adopted evidence | Focused owner/check |
+| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
+| forms | `FormField` is mounted by `TextField`/`Select`; required labels carry a text marker and optional labels remain unmarked. | `tests/ui-v2-form-field.dom.test.tsx` |
+| headers/catalogues | Shared mode status notices now delegate to DS `EmptyState`; therapy loading/empty wrappers delegate to `LoadingPanel`/`EmptyState`; differential and favourites chip wrappers delegate to DS `Chip` while retaining their surface-specific density/tone mapping. | `tests/design-system-target-evidence.test.ts`, `tests/mode-home-status-notice.dom.test.tsx` |
+| overlays | `Sheet` portals to `OverlayRoot`'s modal host by default. Settings, the mobile Clinical Guide sidebar, and the three answer-review sheets use that default; none opts out with `portal={false}`. | `tests/sheet.dom.test.tsx`, `tests/design-system-target-evidence.test.ts` |
+| answer | The three product copy paths share the payload builder described in §2.6; answer-review overlays retain their existing content, dismissal, and focus-return props while using the portal default. | `tests/answer-copy-payload.test.ts`, `tests/answer-clipboard-product-path.dom.test.tsx` |
+| cross-mode links | `responsive-compact` deliberately mounts a phone chip rail and an `md+` card rail so SSR and hydration agree. CSS makes only one rail visible/in the accessibility tree; selectors and analytics must target the variant rail, while `cross-mode-links-rail` remains the phone-only contract. | `tests/design-system-target-evidence.test.ts`, focused `tests/ui-smoke.spec.ts` CrossModeLinks journeys |
+
+**Residual evidence requirement.** Static and DOM checks establish portal ownership and preserve
+focus-return inputs, but they do not prove every nested overlay's keyboard sequence in a real browser.
+Before changing focus order, ancestor-scoped overlay styling, or the portal default, capture a focused
+Chromium keyboard pass for settings, sidebar, and each answer-review sheet (open, Tab/Shift+Tab,
+Escape, and return focus). This is follow-up evidence, not a visual-baseline commitment.
+
## Generated adoption truth
diff --git a/docs/design-system/COMPONENTS.md b/docs/design-system/COMPONENTS.md
index 09125e620..585db62f3 100644
--- a/docs/design-system/COMPONENTS.md
+++ b/docs/design-system/COMPONENTS.md
@@ -4,7 +4,7 @@
specifications.** Prop shapes are normative contracts; token references are roles only —
values live in the token files (see [TOKENS.md](TOKENS.md)).
-- **Date:** 5 August 2026 · companions: [SPEC.md](SPEC.md) · [DECISIONS.md](DECISIONS.md) ·
+- **Date:** 12 August 2026 · companions: [SPEC.md](SPEC.md) · [DECISIONS.md](DECISIONS.md) ·
[GATES.md](GATES.md)
- **Publication vocabulary:** `registered` — exported by `.design-sync/entry.tsx`, mapped to
source, covered by a source-derived prop contract, preview, and direct static contract test ·
@@ -20,7 +20,7 @@ derived from source, `.design-sync/config.json`, previews, direct contract cover
production imports. It intentionally does not claim dark, forced-colours, 320px, print, remote
publication, or product adoption without separate evidence.
-### 0.1 Registered visual components (53 — local contract; remote status unverified)
+### 0.1 Registered visual components (54 — local contract; remote status unverified)
Every visual export has one source map entry, a source-derived public `*Props` contract (except
the two zero-prop roots), a reference preview, and a direct static publication test. The generated
@@ -37,11 +37,12 @@ table under **Generated maturity snapshot** is the canonical list and product-im
_Remaining from the original eight in this document:_ `DocumentFrame` is built locally
(`src/components/ui/document-frame.tsx`, shell-only in `DocumentViewer`) but is not yet among
-the 53 design-sync registered visual exports — registration and full controls remain follow-up.
+the 54 design-sync registered visual exports — registration and full controls remain follow-up.
`OverlayRoot`, `SegmentedControl`, and the PR 6–8 components are built and represented by the
-local publication contract. Registration proves a source/API/preview/test reference; it does
-not prove root-shell mounting, remote design-project publication, or product adoption.
+local publication contract. The generated snapshot records their current product-import counts;
+registration still proves only a source/API/preview/test reference and does not prove remote
+design-project publication or browser acceptance.
_P1 reusable (specified in outline only):_ `Menu`/`Popover` · `KeyValue` ·
`AppliedFilters`/`FilterSheet` · `ResponsiveActionGroup` · `ScrollableStrip`/
@@ -60,32 +61,32 @@ print primitives (`PrintHeader`, `PrintFooter`, `CitationFootnote`, `PrintOnly`,
### 0.4 Open-defect ledger (existing components → closing PR)
-| Component | Open defects (compressed) | Closes in |
-| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
-| Button | danger hover/active tokens, the 48px comment and `ref` forwarding (plus a `testId` passthrough — React 19 types give components no `data-*` index signature) are resolved; the client boundary remains | follow-on |
-| AsyncButton | `type` applied after spread (default `button`; explicit `submit` preserved). Prefer `Button` busy API for new sites. | **done** (PR-A) |
-| IconButton | disabled encoding uses `controlDisabled` (opacity retired in PR-A) | **done** (PR-A) |
-| ToggleSwitch | operable branch requires `aria-label`; opacity disabled retired; knob now travels on `transform` (`translate-x-4`), tokenised duration, reduced-motion opt-out | **done** (motion) |
-| Chip | final `appearance`/size API, removable label contract, tap target and full-value title | **done** |
-| TextField/SearchField/Select | all three closed, and the row was stale when re-measured 2026-08-08: each folds onto `FormField`, which renders `hint` AND `error` unconditionally and merges `aria-describedby` caller → hint → error; each takes an external `id` and a `ref`. Landed in PR 13, not the PR 7 the row pointed at | **done** |
-| Checkbox/RadioGroup | all four closed. Two rows were stale when re-measured on 2026-08-08: ids already sanitise through `optionId`, and the group already carries `hint`/`error`/`describedBy`. Raw dimensions moved to `size-5`/`h-0.5` (`size-4.5` retired by `check:icon-scale`) | **done** |
-| Citation/CitationList | required interactive handler, stable-id list keys and static-span labelling (`aria-label` on a role-less span was dropped, losing the currency phrase on print) are resolved; route/source modes remain | follow-on |
-| DoseLine | must compose `Quantity` · structured dose model · overdue text + non-colour mark + open action | **done** (PR 6) |
-| StatusMark | app-type coupling resolved — `DocumentStatus` is declared here and `@/lib/types` conforms to it (asserted at compile time in the DOM suite), not the reverse; inline styles/raw geometry remain | PR 12 |
-| AnswerCard | required verification/state, structured actions and five-state vocabulary landed | **done** (PR 6) |
-| AnswerFooter | machine ISO values composed through `DateDisplay` + `MissingValue` landed | **done** (PR 6) |
-| PageHeader/Breadcrumb | all resolved. The wrap alone was not enough — the actions track still took full max-content first, so the title column now has a `minmax(20ch,1fr)` floor and the actions track is the one that gives way | **done** |
-| Tabs | both resolved. An invalid `value` no longer empties the tab order: the first enabled tab becomes reachable and owns the panel wiring. Reachability only — no `onChange` is fired to repair caller state | **done** |
-| Pagination | all resolved. Props clamp (`page=0` no longer emits `onPageChange(-1)`), the control row wraps at 320px, and reaching a boundary hands focus to the current page and announces "Page N of M" through `LiveAnnouncer` | **done** |
-| Links | `tone` leak, spread-overridable `download` (now type-omitted AND written after the spread) and the `gap` "animation" (`gap` is not in Tailwind's `transition` list, so it jumped; now a composited `translate-x`) are resolved; new-tab policy still implicit, and `LinkAction` silently ignores `tone` | PR 9 |
-| Tooltip | composed handlers/description, string content, OverlayRoot portal, collision and delay contract | **done** (PR 10) |
-| Toast | independent tone/priority/persistence, OverlayRoot portal, pause, dedupe and queue contract | **done** (PR 10) |
-| Sheet/ConfirmDialog | required names/action labels, portal default, tokened layers/duration and wrapping titles | **done** (PR 10) |
-| Disclosure | heading level and print resolved — a collapsed panel is `print:block`, so a section no longer prints as if the guideline never mentioned it; truncation remains. The old docstring's Ctrl-F/print claim was false | PR 11 |
-| Progress/StageList | all four resolved: `scaleX`, the theme `animate-shimmer` in place of a hardcoded `1.4s`, a step index clamped to ≥1, and an sr-only `role="status"` sibling in place of `aria-live` on the whole `
` | **done** |
-| EmptyState | static live-off default with explicit polite/assertive opt-in | **done** (PR 8) |
-| AccessibleTable | semantic caption, `MissingValue` cells, dense headers (clipped header keeps its full string as `title`) and the expander (now the registered `Button`, off the local ring-focus recipe) landed; content-role widths remain | PR 6/PR 12 remainder |
-| ui-primitives.tsx | 698-line module mixing recipes/actions/feedback/clinical — split. Re-measured 2026-08-08: **157** production files import it (202 including mockups — the old "200 product files" was the mockup-inclusive figure mislabelled), against the 53 registered components' **30** adopted; this module, not the registry, is what the product actually runs on | PR 12 |
+| Component | Open defects (compressed) | Closes in |
+| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
+| Button | danger hover/active tokens, the 48px comment and `ref` forwarding (plus a `testId` passthrough — React 19 types give components no `data-*` index signature) are resolved; the client boundary remains | follow-on |
+| AsyncButton | `type` applied after spread (default `button`; explicit `submit` preserved). Prefer `Button` busy API for new sites. | **done** (PR-A) |
+| IconButton | disabled encoding uses `controlDisabled` (opacity retired in PR-A) | **done** (PR-A) |
+| ToggleSwitch | operable branch requires `aria-label`; opacity disabled retired; knob now travels on `transform` (`translate-x-4`), tokenised duration, reduced-motion opt-out | **done** (motion) |
+| Chip | final `appearance`/size API, removable label contract, tap target and full-value title | **done** |
+| TextField/SearchField/Select | all three closed, and the row was stale when re-measured 2026-08-08: each folds onto `FormField`, which renders `hint` AND `error` unconditionally and merges `aria-describedby` caller → hint → error; each takes an external `id` and a `ref`. Landed in PR 13, not the PR 7 the row pointed at | **done** |
+| Checkbox/RadioGroup | all four closed. Two rows were stale when re-measured on 2026-08-08: ids already sanitise through `optionId`, and the group already carries `hint`/`error`/`describedBy`. Raw dimensions moved to `size-5`/`h-0.5` (`size-4.5` retired by `check:icon-scale`) | **done** |
+| Citation/CitationList | required interactive handler, stable-id list keys and static-span labelling (`aria-label` on a role-less span was dropped, losing the currency phrase on print) are resolved; route/source modes remain | follow-on |
+| DoseLine | must compose `Quantity` · structured dose model · overdue text + non-colour mark + open action | **done** (PR 6) |
+| StatusMark | app-type coupling resolved — `DocumentStatus` is declared here and `@/lib/types` conforms to it (asserted at compile time in the DOM suite), not the reverse; inline styles/raw geometry remain | PR 12 |
+| AnswerCard | required verification/state, structured actions and five-state vocabulary landed | **done** (PR 6) |
+| AnswerFooter | machine ISO values composed through `DateDisplay` + `MissingValue` landed | **done** (PR 6) |
+| PageHeader/Breadcrumb | all resolved. The wrap alone was not enough — the actions track still took full max-content first, so the title column now has a `minmax(20ch,1fr)` floor and the actions track is the one that gives way | **done** |
+| Tabs | both resolved. An invalid `value` no longer empties the tab order: the first enabled tab becomes reachable and owns the panel wiring. Reachability only — no `onChange` is fired to repair caller state | **done** |
+| Pagination | all resolved. Props clamp (`page=0` no longer emits `onPageChange(-1)`), the control row wraps at 320px, and reaching a boundary hands focus to the current page and announces "Page N of M" through `LiveAnnouncer` | **done** |
+| Links | `tone` leak, spread-overridable `download` (now type-omitted AND written after the spread) and the `gap` "animation" (`gap` is not in Tailwind's `transition` list, so it jumped; now a composited `translate-x`) are resolved; new-tab policy still implicit, and `LinkAction` silently ignores `tone` | PR 9 |
+| Tooltip | composed handlers/description, string content, OverlayRoot portal, collision and delay contract | **done** (PR 10) |
+| Toast | independent tone/priority/persistence, OverlayRoot portal, pause, dedupe and queue contract | **done** (PR 10) |
+| Sheet/ConfirmDialog | required names/action labels, portal default, tokened layers/duration and wrapping titles | **done** (PR 10) |
+| Disclosure | heading level and print resolved — a collapsed panel is `print:block`, so a section no longer prints as if the guideline never mentioned it; truncation remains. The old docstring's Ctrl-F/print claim was false | PR 11 |
+| Progress/StageList | all four resolved: `scaleX`, the theme `animate-shimmer` in place of a hardcoded `1.4s`, a step index clamped to ≥1, and an sr-only `role="status"` sibling in place of `aria-live` on the whole `` | **done** |
+| EmptyState | static live-off default with explicit polite/assertive opt-in | **done** (PR 8) |
+| AccessibleTable | semantic caption, `MissingValue` cells, dense headers (clipped header keeps its full string as `title`) and the expander (now the registered `Button`, off the local ring-focus recipe) landed; content-role widths remain | PR 6/PR 12 remainder |
+| ui-primitives.tsx | 698-line module mixing recipes/actions/feedback/clinical — split. Re-measured 2026-08-12: **157** production files import it (202 including mockups), against the 54 registered components' **31** with product imports; this module, not the registry, is what the product actually runs on | PR 12 |
---
@@ -383,6 +384,13 @@ type ErrorSummaryProps = {
**States.** default · focused · invalid (hint + error) · disabled (encoded via `controlBase`,
not opacity) · required/optional.
+**Optionality marker.** Required fields append the visible word `(required)` to the label and
+set the native `required` contract on the control. Optional fields receive no suffix: `(optional)`
+was deliberately removed from the product because optional is the default and repeating it made
+dense clinical forms harder to scan. Do not restore an optional suffix at individual call sites;
+the rule is text-first, never colour-only, and is pinned by
+`tests/ui-v2-form-field.dom.test.tsx`.
+
**Keyboard & screen reader.** Label → control association by `htmlFor`/`id`; described-by
order: caller, hint, error; Tab order unchanged by validity; summary-link activation moves
focus into the field.
@@ -969,7 +977,7 @@ This generated snapshot is a local source-derived inventory. It does not assert
| `DisclosureGroup` | layout | yes | yes | inherited-global-root | yes | no | 1 |
| `DoseLine` | answer | yes | yes | no | yes | no | 0 |
| `DownloadLink` | controls | yes | yes | no | yes | no | 0 |
-| `EmptyState` | feedback | yes | yes | inherited-global-root | yes | no | 12 |
+| `EmptyState` | feedback | yes | yes | inherited-global-root | yes | no | 13 |
| `ErrorState` | feedback | yes | yes | no | yes | no | 0 |
| `ErrorSummary` | feedback | yes | yes | no | yes | no | 0 |
| `ExternalTextLink` | controls | yes | yes | no | yes | no | 0 |
diff --git a/docs/design-system/adoption-manifest.json b/docs/design-system/adoption-manifest.json
index 0e8b0a3bd..23b3d5b8e 100644
--- a/docs/design-system/adoption-manifest.json
+++ b/docs/design-system/adoption-manifest.json
@@ -363,6 +363,7 @@
"tests/ckb-v2-token-contract.test.ts",
"tests/design-sync-visual-exports.test.ts",
"tests/design-system-contract-utils.test.ts",
+ "tests/design-system-target-evidence.test.ts",
"tests/ui-v2-components.dom.test.tsx"
],
"baseline": {
@@ -650,7 +651,8 @@
"src/components/clinical-dashboard/medication-record-page.tsx",
"src/components/clinical-dashboard/visual-evidence.tsx",
"src/components/document-viewer/document-rail-panels.tsx",
- "src/components/dsm/dsm-search-page.tsx"
+ "src/components/dsm/dsm-search-page.tsx",
+ "src/components/mode-home-template.tsx"
],
"productImportFiles": [
"src/components/ClinicalDashboard.tsx",
@@ -664,7 +666,8 @@
"src/components/clinical-dashboard/medication-record-page.tsx",
"src/components/clinical-dashboard/visual-evidence.tsx",
"src/components/document-viewer/document-rail-panels.tsx",
- "src/components/dsm/dsm-search-page.tsx"
+ "src/components/dsm/dsm-search-page.tsx",
+ "src/components/mode-home-template.tsx"
],
"designSync": {
"listedInSourceMap": true,
@@ -675,6 +678,7 @@
"testFiles": [
"tests/design-sync-contract.test.ts",
"tests/design-sync-visual-exports.test.ts",
+ "tests/design-system-target-evidence.test.ts",
"tests/dsm-search-empty-state.dom.test.tsx",
"tests/therapy-compass-ui.dom.test.tsx",
"tests/ui-primitives.dom.test.tsx"
@@ -1005,7 +1009,7 @@
"preview": ".design-sync/previews/LoadingPanel.tsx",
"previewValid": true
},
- "testFiles": ["tests/design-sync-visual-exports.test.ts"],
+ "testFiles": ["tests/design-sync-visual-exports.test.ts", "tests/design-system-target-evidence.test.ts"],
"baseline": {
"targetLayer": "v2",
"liveLayer": "v2",
@@ -1077,6 +1081,8 @@
"testFiles": [
"tests/app-announcements-root.test.ts",
"tests/design-sync-visual-exports.test.ts",
+ "tests/design-system-target-evidence.test.ts",
+ "tests/sheet.dom.test.tsx",
"tests/ui-v2-components.dom.test.tsx"
],
"baseline": {
diff --git a/src/components/clinical-dashboard/ClinicalSidebar.tsx b/src/components/clinical-dashboard/ClinicalSidebar.tsx
index 55c821dce..613986d13 100644
--- a/src/components/clinical-dashboard/ClinicalSidebar.tsx
+++ b/src/components/clinical-dashboard/ClinicalSidebar.tsx
@@ -181,7 +181,6 @@ function MoreModesMenu({
description="Open a specialist mode home."
closeLabel="Close more modes"
returnFocusRef={triggerRef}
- portal
mobilePlacement="bottom"
mobileSize="content"
testId="sidebar-more-modes-sheet"
diff --git a/src/components/clinical-dashboard/answer-result-surface.tsx b/src/components/clinical-dashboard/answer-result-surface.tsx
index c485a3a8e..2723deee1 100644
--- a/src/components/clinical-dashboard/answer-result-surface.tsx
+++ b/src/components/clinical-dashboard/answer-result-surface.tsx
@@ -372,7 +372,6 @@ function StagedAnswerResultSurfaceImpl({
contentClassName="max-h-[88dvh] bg-[color:var(--surface-raised)] sm:max-h-[min(80dvh,36rem)] sm:max-w-md"
bodyClassName="flex flex-col bg-[color:var(--surface-raised)] px-3 pb-0 pt-2 sm:p-3"
returnFocusRef={clinicalNotesTriggerRef}
- portal
>
diff --git a/src/components/mode-home-template.tsx b/src/components/mode-home-template.tsx
index 5d1165860..8a9e89cab 100644
--- a/src/components/mode-home-template.tsx
+++ b/src/components/mode-home-template.tsx
@@ -3,7 +3,7 @@ import { type ReactNode } from "react";
import { type LucideIcon, ArrowRight } from "lucide-react";
import { DesktopComposerPortalSlot } from "@/components/desktop-composer-portal-slot";
-import { cn, eyebrowText } from "@/components/ui-primitives";
+import { cn, EmptyState, eyebrowText } from "@/components/ui-primitives";
import { modeHomeComposerReservePendingValue } from "@/lib/mode-home-composer";
export type ModeHomeAction = {
@@ -247,25 +247,27 @@ export function ModeHomeStatusNotice({
}) {
const actionClass =
"inline-flex min-h-tap items-center justify-center rounded-lg bg-[color:var(--command)] px-3 text-sm font-semibold text-[color:var(--command-contrast)] hover:bg-[color:var(--command-hover)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)] lg:min-h-9";
+ const action =
+ onAction && actionLabel ? (
+
+ ) : actionHref && actionLabel ? (
+
+ {actionLabel}
+
+ ) : undefined;
+
return (
-