refactor(therapy): converge Therapy Compass onto the shared Button, card, and PageHeader components - #2122
Conversation
Therapy Compass drew its own ~32 SVG glyphs through a local `makeIcon`
factory in `therapy-compass/icons.tsx`, while 201 other component files
in `src/components/**` import `lucide-react`. Therapy had exactly one
lucide import — `home-screen.tsx`, which is also the only therapy screen
already mounted on a shared template.
That divergence was not just cosmetic: the shared primitives type their
icon props as `LucideIcon` (`ui/button.tsx`, `ui/page-header.tsx`), so
therapy's `(p: { size?: number }) => ReactNode` glyphs could not be
passed to them at all. The local icon set was the blocker underneath
every other therapy/shared-component gap, so it is converted first.
Glyphs are matched on geometry, not on name, so this is a convergence
rather than a redesign. Three mappings deliberately do not follow the
local name, because the local drawing did not match the lucide icon that
shares it:
CrosshairIcon -> Target local glyph is concentric circles
SlidersIcon -> ListFilter local glyph is the decreasing-lines funnel
PathwayIcon -> Waypoints matches home-screen's existing import
Every call site already passed an explicit `size`, so no glyph changes
dimension as a result of lucide's larger (24px) default.
Accessibility is preserved rather than assumed. `makeIcon` hard-set
`aria-hidden="true"` on every glyph and lucide does not, so dropping the
factory would have leaked 57 unnamed graphics into the accessibility
tree. `local/require-lucide-icon-aria` caught all 57 directly-imported
sites and its autofix restored the decorative default. The rule
explicitly does not inspect icons passed as `icon={...}` values, so the
seven prop-passed render sites were fixed by hand.
The seven structural `icon:` props are retyped from the loose function
shape to `LucideIcon`, which is what unblocks the shared Button and
PageHeader adoption that follows.
Gates: lint clean; `tsc --noEmit` clean; 137 therapy unit tests pass
across 20 files; `check:design-system-contract` passed with ratchets
moving down, not up — legacy shadow aliases 114 -> 106, colour-only
status indicators 4 -> 3, raw padding literals 63 -> 56, raw gap
literals 31 -> 30. `check:icon-scale` clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>… ring
Three findings from mapping every consumer of `therapy-compass/controls.ts`
before converting its call sites to the shared `Button`:
`softControl` and `linkButton` have no consumers anywhere in `src/`. They
are removed rather than converted.
The local `focusRing` const was justified in a comment claiming "the shared
export was renamed away from `focusRing` on main". That was not true when it
was written — `card-recipes.ts` exports `focusRing`, and its own doc comment
names this exact duplication as the cost it exists to remove ("redeclared as
a local `focusRing` const in a dozen component files"). Therapy now imports
it, so a change to the focus contract no longer has to find this file.
Not addressed here, because it is a visible design decision rather than a
token cleanup: therapy runs two filled button recipes at once. `accentControl`
fills with `--clinical-accent` and `commandControl` with `--command`, and
`accentControl` sits on every result card's "Open record" action, so the
search screen renders one filled button per result. COMPONENTS.md section 9.1
allows one filled `--command` button per surface and lists a closed token set
for Button that does not include `--clinical-accent`. Resolving that changes
what the busiest therapy screen looks like, so it is left for its own change.
Gates: lint clean; `tsc --noEmit` clean; therapy contract and DOM tests pass
(21 across 3 files, plus the ui DOM test run separately);
`check:design-system-contract` passed, legacy shadow aliases 106 -> 105.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>Therapy carried two filled button recipes at once. `commandControl` used the sanctioned `--command` triplet; `accentControl` filled with `--clinical-accent` and sat on every result card's "Open record", so a search screen painted one filled button per result. COMPONENTS.md section 9.1 allows one filled `--command` button per surface and gives Button a closed token list — command triplet + contrast, danger pair, `--radius-md`, tap utilities, `--focus` — which does not include `--clinical-accent`. The token values make it more than a preference: `--command` is HCM-mapped to `ButtonFace`, `--clinical-accent` has no forced-colors mapping, so therapy's filled actions lost their system button identity in Windows High Contrast. Result cards: all three actions become `secondary`. A result list has N cards, so none of their actions is the primary action on the surface; the single filled slot is left for the page rather than spent N times. The compare toggle keeps its selected encoding through an explicit class, since `secondary` does not carry one. Workspace data-error retry: this one genuinely is the only action on its surface, so it takes `variant="primary"`. It also moves to Button's `busy` prop, which disables the control, announces `aria-busy` and shows a spinner — the hand-rolled `disabled` + label ternary did none of that. `accentControl` then had no consumers and is deleted. Fixed in passing: the patient-sheet button advertised its unavailability through `title` alone, with no `sr-only` note wired via `aria-describedby`, so the stated reason was unreachable to a screen reader (docs/wiring-conventions.md). It now uses `ignoreUnavailableActivation` plus a described-by note instead of an inline early-return handler. One contract assertion is updated rather than deleted. `therapy-compass-responsive-contract` pinned that `controls.ts` pairs its accent fill with the semantic contrast token — that was `accentControl`'s obligation. With the recipe gone the requirement becomes the stronger one, no accent fill in `controls.ts` at all, and the pairing is asserted where the accent fill actually survives: `IconTile` in `ui.tsx`. Gates: lint clean; `tsc --noEmit` clean; 41 therapy DOM tests pass across 10 files; responsive-contract and design-system-adoption pass; `check:design-system-contract` passed with ratchets still falling — legacy shadow aliases 105 -> 104, raw padding literals 56 -> 54. Generated adoption manifest records Button product adoption 3 -> 5. Not yet converted, and tracked for the next stage: `commandControl`, `outlineControl` and `iconControl` still back roughly 40 call sites across the seven screens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…py through the shared clipboard
Two defects found while auditing therapy against the shared component
conventions. Neither is cosmetic.
The completeness meter was a colour-only status indicator, which
GATES.md gate 4 pins as debt at `therapy-compass/ui.tsx`. Its fill
switches hue at 80 and at 50, and nothing but the hue said so — the
percentage does not carry it, because a reader can see "62%" and still
not know the review threshold it is measured against. The three bands
are now named ("complete" / "partial" / "below review threshold") and
ride in the meter's accessible name, `aria-valuetext`, and the fill's
`title`.
A `StatusMark` is deliberately not used here. Its own contract limits
shape marks to surfaces where a reader looks at one thing — "in a dense
table, use the label alone" — and the review queue renders three meters
per row across twenty-four rows.
The pathways screen called `navigator.clipboard.writeText` directly,
while every other copy affordance in the mode goes through
`useClipboard` -> `@/lib/copy-to-clipboard`. Bypassing it meant no
fallback path, a rejected write reported as success, and no confirmation
to the reader that anything had been copied. It now uses the shared hook
and the button confirms with "Copied".
Gates: lint clean; `tsc --noEmit` clean; 39 tests pass across 6 files;
`check:design-system-contract` passed. Verified by direct attribution
rather than by the headline total — `--print-debt-baseline` no longer
lists `src/components/therapy-compass/ui.tsx` under
`colourOnlyStatusIndicators` at all, and therapy's spacing debt fell
with it: raw padding literals 42 -> 33, raw gap literals 19 -> 18.
The baseline file still pins `therapy-compass/ui.tsx` at 1 for that
metric. It is left alone rather than regenerated, because regenerating
the whole file would fold in unrelated reductions from other work; the
pin is now slack rather than debt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>…-pressed
Three lists encoded "which record is selected" with `aria-pressed`, so a
screen reader announced a selected list item as "toggle button, pressed":
the pathway list, the brief's step list, and `TherapyListItem`. None is a
toggle — each selects one of a set, and clicking the already-selected row
does not unselect it. `aria-current` is the selected-within-a-set
encoding, and docs/filter-contract.md section 2 draws the same line for
filters (one-of-N is not `aria-pressed`).
The styling hooks move with the semantics. `aria-current` is not one of
Tailwind's built-in aria variants, so the selected rules use the
arbitrary `aria-[current=true]:` form; the rendered result is unchanged.
Deliberately untouched, because `aria-pressed` is correct for them: the
favourite control (`aria-pressed={saved}`), the compare toggle
(`aria-pressed={inCompare}`), the recommend filter pills, and the sheets
section toggles. All are genuine binary toggles. The one assertion that
pins this — `aria-pressed={saved}` in the responsive contract — still
passes untouched, which is the point: the change separates real toggles
from selection, rather than replacing the attribute everywhere.
Gates: lint clean; `tsc --noEmit` clean; 91 tests pass across 7 files
including the pathways, responsive-contract and convergence contracts;
`check:design-system-contract` passed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>A second pass over every export in `controls.ts` and `ui.tsx` after the Button conversion, checking each against its real consumers rather than a substring grep. Removed as unreferenced anywhere in `src/`: `flexControl`, `compactControl` and `therapyRow` from `controls.ts`, and `SectionHeading` from `ui.tsx`. These are the same class as the `softControl` and `linkButton` removed earlier — recipes left behind by conversions that already happened. `tagTone` is no longer exported. Its only caller is `TagRow`, in the same file. Kept, and confirmed live rather than assumed: `card` and `heroCard` back four surfaces in `detail-screen.tsx`; `commandControl`, `outlineControl`, `iconControl` and `therapyBtn` still back the call sites the next stage converts. Noted for later, not acted on here: the repository now carries five independent `SectionHeading` implementations — `dashboard-shell.tsx`, `search-pins-menu.tsx`, `formulation-ui.tsx`, `specifier-ui.tsx` and the one deleted here. That is a cross-mode convergence question of the same shape as `card-recipes.ts`, and it is wider than therapy. Gates: lint clean; `tsc --noEmit` clean; 79 tests pass across 6 files; `check:design-system-contract` passed, raw padding literals 54 -> 53. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Record the outstanding work up front so it survives a context reset: - P2 task: the three remaining shared-component stages on this branch (B3 Button call sites, C card-recipes adoption, D page headers), with the landed range and the one-browser-pass stop rule. - P3 rec: five independent SectionHeading implementations across four modes with no shared recipe, and the stop rule keeping them out of the over-budget ui-primitives.tsx. Both are immutable inbox requests; the canonical ledger is untouched.
…Button B3 of the Therapy Compass convergence. `controls.ts` still shipped three forked button recipes; every action they dressed is now the shared `Button` from `@/components/ui/button` (COMPONENTS.md §9.1). Converted 19 recipe call sites across seven files: - `commandControl` ×6 -> `variant="primary"` or `secondary` (see below) - `outlineControl` ×12 -> `variant="secondary"` - `iconControl` ×1 -> icon-only `Button` (result-card favourite) Plus the one hand-rolled `--command` fork left in `detail-screen.tsx`, which was the same filled-action job and carried a sub-floor `h-[46px]` face; as a `Button` it inherits the 48px tap floor. One filled `--command` per surface, checked screen by screen rather than assumed from the recipe name. `brief-screen` already spends its filled slot on `BrowserPrintButton`, so BOTH of its `commandControl` sites demote to `secondary` — a mechanical `commandControl -> primary` would have put three filled buttons on that page. `outlineControl` carried `aria-pressed:` selected styling that `secondary` does not. The encoding moves to `controls.ts` as `controlPressed` (was `cardActionPressed`, private to `therapy-card.tsx`; four surfaces need it now) and is applied to the genuine toggles only: the favourite and the compare toggle. Everything else that looked like a toggle became `aria-current` in 63397ca. Favourites additionally get `favouritePressed`, which fills the glyph so the saved state carries a shape channel, not just the accent hue. `data-print-hide` cannot be passed to a component — `@types/react@19` gives `HTMLAttributes` no `data-*` index signature, which is why `Button` has an explicit `testId` prop. The one affected call site wraps its Button in a `display: contents` span instead, so the print rule still hides the subtree and the Button stays a direct flex item of its row. `therapyBtn` is NOT deleted: it is still the focus/hover affordance for about twenty raw call sites that are list rows, disclosure headers and chips rather than Buttons. Deleting it was contingent on those converting too, which is not this stage. Ratchets fell and none rose: legacy shadow aliases 104 -> 102, raw padding literals 53 -> 52, raw gap literals 30 -> 28. Gates: eslint clean; tsc clean; 70/70 across the four contract suites plus 24/24 across the wider therapy suites; design-system contract passed.
C of the Therapy Compass convergence. `card-recipes.ts` was written to end four independent forks of the card surface; therapy was a fifth, and the largest — 16 surfaces all writing `rounded-xl` / `--surface` / `--shadow-soft`, four through the local `card`/`heroCard` consts and twelve inline. All 16 now compose the shared `cardSurface`. What moves, per the recipe: radius 1rem -> 0.75rem, background `--surface` -> `--surface-raised` (near-invisible in light, a visibly lighter panel in dark), shadow `--shadow-soft` -> `--e1` (one elevation step lighter), and every card gains `forced-colors:border`. Converting only the four consts would have left therapy rendering two card looks side by side, so the whole module converges at once. `heroCard` keeps its accent edge on the LEFT rather than adopting `cardAccentEdge`, which puts it on the top. The two are not the same signal: `cardAccentEdge` is the *category* edge — it replaced the factsheet cards' inline `borderTopColor` and answers "which family is this" — while therapy's edge answers "this is the principal panel on this screen", and a therapy screen has one category anyway. Only the side stays local. The colour moves to the shared `--cat-accent`, which `:root` aliases to `--clinical-accent`, so nothing renders differently while the edge becomes theme- and forced-colors-remappable per category. `recommend-screen`'s top-match panel had an independent copy of that literal; it now shares `heroCard`, so the two hero cards are one definition. Left alone deliberately: the comparison table's scroll frame (`compare-screen.tsx:215`). It is `rounded-xs` with no background because the `<table>` inside paints its own, so it is a frame, not a card; `cardSurface` would give it a second background and a 12px radius. `responsiveStackCount` in the responsive contract only matched `className="…"`. Several grids moved to the template-literal form to compose the recipe, and the compare grid's count silently fell to zero while its phone reflow was unchanged. The assertion is widened to match both spellings rather than dropped — it now measures the reflow instead of the quoting, and catches template-form grids it never saw before. Verified by resolving all 16 call sites' final class lists and asserting no extra overrides a utility family `cardSurface` owns, rather than eyeballing the render. Ratchets: legacy shadow aliases 102 -> 89. None rose. Gates: eslint clean; tsc clean; 86/86 across nine suites; design-system contract passed.
D of the Therapy Compass convergence. Therapy hand-rolled its page titles, and eight of them used `text-3xl-minus` (1.625rem) — a fixed step no other `h1` in `src/` uses, where peers take `text-2xl … sm:text-3xl`. The audit called it three; the other five non-therapy consumers of that step are a calculator readout `<p>` and two mockups, so therapy owned every `h1` at that size. Six page headers now use `PageHeader` (`src/components/ui/page-header.tsx`), following `dsm-page-header.tsx`: pathways, other's Review Queue, recommend, brief, sheets and compare. Their titles pick up the responsive display scale, text balancing and the wrap behaviour that keeps a long title from being starved by its own actions row. Two non-interactive chips move from beside the title to `meta`, the slot documented for status chips and counts: compare's "N of 4 selected" and the review queue's "N to review". Buttons and the density `SegmentedControl` go to `actions`, which is the same right-hand column they already occupied. `data-therapy-no-print` moves to a wrapper for the same `data-*`-on-a- component reason as B3's print-hide case. Three `h1`s are deliberately NOT converted, and one of them is a trap worth recording. `page-header.tsx` renders a `<header>`, and `globals.css` hides every `<header>` unconditionally under `@media print`. `detail-screen`'s `h1` is the therapy record's NAME inside the hero card — adopting `PageHeader` there would silently delete the record name from a printed clinical record, on top of needing slots PageHeader does not have for the interleaved aria-live notice, aliases line and TagRow. `sheets-screen`'s second `h1` is the contentEditable title of the generated patient handout on `--tc-paper-ink`, which is document content rather than page chrome, and `other-screen`'s placeholder hero is a centred empty state already at `text-2xl`. All three, plus the pre-existing double-`h1` on the sheet builder page, are recorded in the outstanding-issues inbox. Two assertions in the responsive contract pinned the exact leading whitespace of an opening tag. Moving the density `SegmentedControl` into an `actions` slot re-indents it and changes nothing else, so the assertions are replaced with the file's own `openingTagWith` helper rather than deleted: that proves the element really is that component and carries its labelling attribute, which a raw substring never did, and leaves indentation to Prettier, which is what actually enforces it. `page-header.tsx` keeps its lack of a `"use client"` directive untouched. Ratchets: all flat, none rose. Gates: eslint clean; tsc clean; 97/97 across eleven suites including route-reachability; design-system contract passed.
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in:30 seconds Limit details: You’ve used all 1 included review currently available under your plan. You completed 101 included PR reviews in the past 7 days; at that activity level, included reviews refill at 1 review per hour. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (22)
Comment |
Immutable review record for HEAD 244accb, covering stages B3, C and D and the checks that backed them. The frozen historical table is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
…al build-refusal conflict (#2131)
…emains Measured on main at adf93a7, after #2122 (component convergence) and #2150 (production exposure) both landed. - Closes `#FEWQZ5`. Its three stages — Button call sites, card-recipes adoption, page headers — all shipped in #2122, so the row described finished work. It was queued before that PR landed and the reconcile applied it afterwards, which is how a completed task ends up reading as open. - Queues the private UI kit as a P2. `therapy-compass/ui.tsx` exports eight components whose shared equivalents Therapy imports zero times: Chip, StatusMark, ErrorState, Progress, the eyebrowText primitive and CategoryIconTile. This is the duplication `card-recipes.ts` was written to end, one layer down, and #2122 did not reach it. It matters most for `StatusBadge`, which renders "Needs source review" — since #2150 that is the per-record half of the only thing between an unreviewed record and a clinical decision, and it is module-private. - Queues `therapyBtn` as a P3: 30 raw call sites across 8 files, kept deliberately in #2122 because list rows and disclosure headers are not Buttons, but with no shared recipe covering them either. Both new records carry migration hazards rather than just a target: `StatusBadge` must keep its `TriangleAlert` shape channel or the warning becomes colour-only, and `Meter`'s colour-only fix from 8c791a1 must not be regressed by a naive swap to shared `Progress`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
Therapy Compass had drifted away from the shared component system the other twelve app modes use. This is the second half of that convergence; stages A, B1, B2, E, F and G landed earlier on this branch (
7ce820f2c..7d2c84a11). Three items, each its own commit and each independently revertible while this PR is open:Control recipes onto the shared
Button(50b4efdf).controls.tsstill exported three forked button recipes. All 19 of their call sites across seven files now useButtonfrom@/components/ui/buttonper COMPONENTS.md §9.1, and the recipes are deleted. The one hand-rolled--commandfork left indetail-screen.tsxconverted too, which also lifted its sub-floorh-[46px]face onto the 48px tap floor. "One filled--commandper surface" was checked screen by screen rather than assumed from the recipe name:brief-screenalready spends its filled slot onBrowserPrintButton, so both of itscommandControlsites becamesecondary— a mechanical mapping would have put three filled buttons on that page. Thearia-pressedselected styling thatsecondarydoes not carry moved tocontrols.tsascontrolPressed, applied only to the genuine toggles.Card surfaces onto
card-recipes.ts(9f9d5236). That module was written to end four independent forks of the card surface; therapy was a fifth and the largest, with 16 surfaces all writingrounded-xl/--surface/--shadow-soft. All 16 now compose the sharedcardSurface, so radius, background, elevation andforced-colors:bordercome from one place. Converting only the four consts would have left therapy rendering two card looks side by side, so the whole module converged at once. The hero card keeps its accent edge on the left rather than adoptingcardAccentEdge, which places it on top: that recipe is the category edge, while therapy's marks the principal panel on a screen that has one category anyway. Only the side stays local — the colour now comes through the shared--cat-accent, which:rootaliases to--clinical-accent, so nothing renders differently while the edge becomes remappable per theme and under forced colors.Page headers onto the shared
PageHeader(d33c4d06). Therapy hand-rolled its page titles, and eight usedtext-3xl-minus— a fixed step no otherh1insrc/uses, where peers taketext-2xl … sm:text-3xl. Six page headers now usePageHeader, followingdsm-page-header.tsx. Non-interactive count chips moved tometa; buttons and the density control toactions.Three
h1elements were deliberately left alone, and one is worth calling out for reviewers:PageHeaderrenders a<header>, andglobals.csshides every<header>unconditionally under@media print.detail-screen'sh1is the therapy record's name inside the hero card, so adoptingPageHeaderthere would silently remove the record name from a printed clinical record. The other two are the patient handout'scontentEditabledocument title and a centred placeholder hero already attext-2xl. All three, plus the pre-existing double-h1on the sheet-builder page, are queued in the outstanding-issues inbox rather than fixed here.Two contract assertions were strengthened rather than deleted, because in both cases the pinned construct still exists and only its spelling changed.
responsiveStackCountmatched onlyclassName="…", so grids that moved to the template-literal form to compose the recipe stopped being counted while their phone reflow was unchanged; it now matches both forms and therefore measures the reflow instead of the quoting. Two toggle-semantics assertions pinned the exact leading whitespace of an opening tag; they now use the file's ownopeningTagWithhelper, which proves the element really is that component and carries its labelling attribute — something a raw substring never did — and leaves indentation to Prettier, which is what actually enforces it.Ratchets fell and none rose: legacy shadow aliases 104 → 89, raw padding literals 53 → 52, raw gap literals 30 → 28. Colour-only status indicators held at 3.
RAG impact: no retrieval behaviour change — this PR touches only presentation under
src/components/therapy-compass/**plus one contract test and generated design-system docs. No file undersrc/lib/rag/**, no clinical-search, retrieval-selection, ranking-config, answer-ranking or eval-harness surface, and no golden fixture is modified.Verification
npm run verify:pr-localFull offline suite green:
Test Files 673 passed (673),Tests 7274 passed | 4 skipped (7278). One component of the gate failed on the first run and is worth stating precisely rather than hiding:buildexited 76 withBUILD_REFUSED_DEV_SERVER, because a local dev server started earlier in the session still held the project port. That is an environment condition, not a code failure, and the gate correctly reports it as a failure rather than a skip. The dev server was stopped and the build re-run standalone on a cleared.next, exiting 0 withClient bundle secret surface check passed.Because that build output was fresh rather than cached,npm run check:bundle-budgetwas also run against it: production 1531.6 KiB gzip against a 1482.5 KiB baseline and every measured route — including/therapy-compassat 219.3 KiB against 206.1 KiB — reportedwithin tolerance.npm run verify:uiwhen UI, routing, styling, browser behavior, reduced-motion, or forced-colors behavior changed447 passed (20.4m), exit 0, Chromium. Worth noting for anyone reproducing this: the gate initially refused to run at all, because this container ships Chromium 1194 while Playwright 1.62.1 pins 1234 (ledger#255). NoPLAYWRIGHT_CHROMIUM_EXECUTABLE_PATHoverride was used — a gate against the wrong revision is not evidence. The documented restore recipe indocs/testing.mdwas followed to install the matching revision, and the figure above is from a real run against it.npm run verify:releasebefore release or handoff confidence claimsVerification not run: provider-backed, not authorized for this change, and no release confidence is being claimed.
npm run eval:retrieval:quality(must stay 36/36) when retrieval, ranking, selection, chunking, or scoring behavior changedVerification not run: not applicable — no retrieval, ranking, selection, chunking or scoring surface is touched, as stated in the
RAG impact:line above.npm run eval:rag -- --limit 15+npm run eval:quality -- --rag-onlywhen answer generation, the synthesis prompt, or answer post-processing changedVerification not run: not applicable — answer generation, the synthesis prompt and answer post-processing are untouched.
npm run check:production-readinesswhen clinical workflow, privacy, environment, Supabase, source governance, or deployment behavior changedVerification not run: not applicable — none of those behaviours changed. The diff is component-level presentation plus one test and generated docs.
npm run check:deployment-readinesswhen deployment startup, hosting, or rollout behavior changedVerification not run: not applicable — no deployment startup, hosting or rollout behaviour changed.
Additionally run:
npx eslint src/components/therapy-compassclean,npx tsc --noEmit -p tsconfig.jsonclean, andnpm run check:design-system-contractreportingDesign-system contract passed (784 production files; raw colors 2; literal shadows 0; legacy tap classes 0; sub-floor interactive min-heights 37; edge conflicts 18; 1px shadow spreads 2).Stage C's swap was verified by resolving all 16 card call sites' final class lists and asserting that no extra class overrides a utility family
cardSurfaceowns, rather than by eyeballing the rendered result.Risk and rollout
--surface→--surface-raised, near-imperceptible in light and a visibly lighter panel in dark) and elevation (--shadow-soft→--e1, one step lighter); six page titles take the responsive display scale; two count chips move from beside their title to beneath the description. No data, no retrieval, no clinical logic, no schema and no environment behaviour is touched, and no user-visible clinical content changes wording. The narrow regression risk is presentational drift on a therapy surface that Chromium journeys do not assert pixel-for-pixel.git revertthe merge commit, or revert any one of the three stage commits independently while this PR is open. Nothing here depends on a migration, a feature flag, or a deploy step, so a revert is complete on its own.Clinical Governance Preflight
scripts/pr-policy.mjsclassifies this diff asclinicalRisk: false— presentation files are not clinical-risk merely for living under a clinically-named directory, and no path matches the auth/permission/privacy/security/upload/download/patient patterns or the clinical dataset patterns. This section is completed anyway because the surface renders clinical content to a clinician, and every item below is genuinely satisfied by this diff.Clinical KB Database(sjrfecxgysukkwxsowpy)On the last item specifically: no clinical decision-support behaviour changed, so the classification is unaffected.
StatusBadge, source designation and review-status rendering are untouched, and the favourite toggle gained a filled-glyph shape channel so its saved state no longer relies on hue alone.Notes
therapyBtnis deliberately not deleted, although the plan this branch follows expected it to be. It is still the focus and hover affordance for roughly twenty raw call sites that are list rows, disclosure headers and chips rather than Buttons; removing it was contingent on converting those too, which is a separate piece of work.The comparison table's scroll frame in
compare-screen.tsxdeliberately kept its own styling. It isrounded-xswith no background because the<table>inside paints its own, so it is a frame rather than a card, andcardSurfacewould have given it a second background and a 12px radius.The branch was synced with
origin/mainby merge (not rebase, and not GitHub's Update branch button) before verification, so every gate above ran against the merged tree. It was three commits behindorigin/mainat push time, whichcheck-base-freshnessreports asok.Two follow-ups found while working here are queued as immutable inbox requests rather than folded into this PR: one hand-drawn SVG checkmark in
recommend-screen.tsxthat survived the earlier lucide sweep, and five independentSectionHeadingimplementations spanning four modes with no shared recipe.The four commits authored in this session are missing the
Co-Authored-Bytrailer that the earlier commits on this branch carry. They were not rewritten to add it, because the branch tip is a fourteen-commit merge and redoing that merge to correct a trailer is a worse trade than the inconsistency.🤖 Generated with Claude Code
https://claude.ai/code/session_01CXpFsse3vVMy7CTxNRi568
Generated by Claude Code