Skip to content

refactor(therapy): converge Therapy Compass onto the shared Button, card, and PageHeader components - #2122

Merged
BigSimmo merged 13 commits into
mainfrom
claude/therapy-mode-consistency-a466b0
Aug 18, 2026
Merged

refactor(therapy): converge Therapy Compass onto the shared Button, card, and PageHeader components#2122
BigSimmo merged 13 commits into
mainfrom
claude/therapy-mode-consistency-a466b0

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

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.ts still exported three forked button recipes. All 19 of their call sites across seven files now use Button from @/components/ui/button per COMPONENTS.md §9.1, and the recipes are deleted. The one hand-rolled --command fork left in detail-screen.tsx converted too, which also lifted its sub-floor h-[46px] face onto the 48px tap floor. "One filled --command per surface" was 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 became secondary — a mechanical mapping would have put three filled buttons on that page. The aria-pressed selected styling that secondary does not carry moved to controls.ts as controlPressed, 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 writing rounded-xl / --surface / --shadow-soft. All 16 now compose the shared cardSurface, so radius, background, elevation and forced-colors:border come 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 adopting cardAccentEdge, 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 :root aliases 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 used text-3xl-minus — a fixed step no other h1 in src/ uses, where peers take text-2xl … sm:text-3xl. Six page headers now use PageHeader, following dsm-page-header.tsx. Non-interactive count chips moved to meta; buttons and the density control to actions.

Three h1 elements were deliberately left alone, and one is worth calling out for reviewers: PageHeader 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, so adopting PageHeader there would silently remove the record name from a printed clinical record. The other two are the patient handout's contentEditable document title and a centred placeholder hero already at text-2xl. All three, plus the pre-existing double-h1 on 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. responsiveStackCount matched only className="…", 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 own openingTagWith helper, 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 under src/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-local

Full 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: build exited 76 with BUILD_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 with Client bundle secret surface check passed. Because that build output was fresh rather than cached, npm run check:bundle-budget was also run against it: production 1531.6 KiB gzip against a 1482.5 KiB baseline and every measured route — including /therapy-compass at 219.3 KiB against 206.1 KiB — reported within tolerance.

  • npm run verify:ui when UI, routing, styling, browser behavior, reduced-motion, or forced-colors behavior changed

447 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). No PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH override was used — a gate against the wrong revision is not evidence. The documented restore recipe in docs/testing.md was followed to install the matching revision, and the figure above is from a real run against it.

  • npm run verify:release before release or handoff confidence claims

Verification 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 changed

Verification 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-only when answer generation, the synthesis prompt, or answer post-processing changed

Verification not run: not applicable — answer generation, the synthesis prompt and answer post-processing are untouched.

  • npm run check:production-readiness when clinical workflow, privacy, environment, Supabase, source governance, or deployment behavior changed

Verification 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-readiness when deployment startup, hosting, or rollout behavior changed

Verification not run: not applicable — no deployment startup, hosting or rollout behaviour changed.

Additionally run: npx eslint src/components/therapy-compass clean, npx tsc --noEmit -p tsconfig.json clean, and npm run check:design-system-contract reporting Design-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 cardSurface owns, rather than by eyeballing the rendered result.

Risk and rollout

  • Risk: Low, but visual rather than invisible. Every therapy card changes radius (16px → 12px), background (--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.
  • Rollback: git revert the 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.
  • Provider or production effects: None. No OpenAI, Supabase, Railway or hosted-CI mutation was performed. The Supabase target is untouched.

Clinical Governance Preflight

scripts/pr-policy.mjs classifies this diff as clinicalRisk: 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.

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

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

therapyBtn is 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.tsx deliberately kept its own styling. It is rounded-xs with no background because the <table> inside paints its own, so it is a frame rather than a card, and cardSurface would have given it a second background and a 12px radius.

The branch was synced with origin/main by 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 behind origin/main at push time, which check-base-freshness reports as ok.

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.tsx that survived the earlier lucide sweep, and five independent SectionHeading implementations spanning four modes with no shared recipe.

The four commits authored in this session are missing the Co-Authored-By trailer 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

BigSimmoand others added 11 commits August 18, 2026 17:32
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.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@supabase

supabaseBot commented Aug 18, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 72bdef23-4945-4734-b0fa-0b31bd0047ec

📥 Commits

Reviewing files that changed from the base of the PR and between abf1d14 and 7733eb8.

📒 Files selected for processing (22)
  • docs/branch-review-records/73c130d8ea3909acf382f2a098d9e9edcc221d659be6e0f4920750e4c5360c21.record.md
  • docs/design-system/COMPONENTS.md
  • docs/design-system/adoption-manifest.json
  • docs/outstanding-issues-inbox/197cb013-5541-4877-9d56-c37a4953d79f.json
  • docs/outstanding-issues-inbox/654217d2-416b-4de8-a847-6e2812c3653d.json
  • docs/outstanding-issues-inbox/8bfa1d6f-e8ae-496b-9ad1-15c1ba718831.json
  • docs/outstanding-issues-inbox/8c14c6a0-f893-4659-b84e-62823d832c97.json
  • src/components/therapy-compass/controls.ts
  • src/components/therapy-compass/icons.tsx
  • src/components/therapy-compass/screens/brief-screen.tsx
  • src/components/therapy-compass/screens/compare-screen.tsx
  • src/components/therapy-compass/screens/detail-screen.tsx
  • src/components/therapy-compass/screens/other-screen.tsx
  • src/components/therapy-compass/screens/pathways-screen.tsx
  • src/components/therapy-compass/screens/recommend-screen.tsx
  • src/components/therapy-compass/screens/search-screen.tsx
  • src/components/therapy-compass/screens/sheets-screen.tsx
  • src/components/therapy-compass/therapy-card.tsx
  • src/components/therapy-compass/ui.tsx
  • src/components/therapy-compass/workspace.tsx
  • tests/therapy-compass-responsive-contract.test.ts
  • tests/therapy-compass-ui.dom.test.tsx

Comment @coderabbitai help to get the list of available commands.

claudeand others added 2 commits August 18, 2026 13:57

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>
@BigSimmo
BigSimmo enabled auto-merge (squash) August 18, 2026 14:09
@BigSimmo
BigSimmo merged commit 092633e into mainAug 18, 2026
26 checks passed
@BigSimmo
BigSimmo deleted the claude/therapy-mode-consistency-a466b0 branch August 18, 2026 14:11
BigSimmo added a commit that referenced this pull request Aug 18, 2026
BigSimmo pushed a commit that referenced this pull request Aug 18, 2026
…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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@BigSimmo@claude