Skip to content

Unify the card layer behind one category-identity registry - #2060

Merged
BigSimmo merged 29 commits into
mainfrom
claude/card-review-optimize-h0pidc
Aug 18, 2026
Merged

Unify the card layer behind one category-identity registry#2060
BigSimmo merged 29 commits into
mainfrom
claude/card-review-optimize-h0pidc

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

Six commits, each independently revertible, that give the product one card layer and one category-identity registry.

  • 18b8601 — one category-identity registry. Icons and category colour were spread across ten independent maps and two of them disagreed. launcherIconById (launcher) carried 13 tool ids; iconByToolId (tools search results) carried 8 with a different fallback, so guidelines, care-plans, safety-plan, calculators and monitoring showed a real glyph on one screen and a generic Grid2X2 on the other. Colour diverged the same way — the launcher tinted by tool area, the results page painted every tile --type-source. ShieldCheck was assigned to guidelines, to risk-safety, and to the "Source-backed" chip: three unrelated meanings, one glyph, reachable on a single card. src/lib/category-identity.ts is now the single source of truth, framework-free so data and server modules can name a glyph without pulling in the render layer. ToolCatalogRecord.id is narrowed from string to a ToolCatalogId union, so an unmapped tool is a type error rather than a silent runtime fallback.
  • a5804cc — stop painting content categories in semantic colours. categoryTheme() drew two of its four factsheet accents from the semantic palette: Therapies on --success-text and Tests & procedures on --warning-text. Those tokens carry meaning — warning is "pause, check, adjust, review" — so an entire category of patient handouts wore caution-amber on its hero band without any review having produced that judgement. All four categories move to non-semantic identity triads. Three guards added, each mutation-verified.
  • 1ddb420 — the shared card recipe. Four cards that sit side by side had drifted across two radii, three resting elevations, and four different "this one is selected" encodings, three of those expressed as fractional opacity on a token colour. src/components/card-recipes.ts holds the shared definitions; the two byte-identical private forks in specifier-ui.tsx and formulation-ui.tsx now name it.
  • a16437e — elevate the tools cards. ToolCard and MobileToolRow were two components rendering the same content and had already drifted; they are now one component with a density prop, keeping both test ids. The launcher's "Details" affordance was a <span> painted as a solid accent button inside the card's own <button> — a nested control announced as nothing, identical on every card. Removed in favour of a chevron.
  • e2154d1 — DSM-5 Diagnosis. All five "Browse categories" pills on the DSM home carried the same BookOpenCheck — the mode's own hero glyph, repeated once per category, distinguishing nothing. Removed. Deliberately not replaced with one glyph per chapter: there are 17 populated DSM-5 categories against 11 non-semantic accents, so any assignment invents relationships that do not exist, and pictograms for diagnostic chapters are a clinical-content decision rather than a design one — the puzzle piece conventionally reached for on neurodevelopmental disorders is rejected by many autistic people, and a glyph chosen for the paraphilic, gender-dysphoria or sexual-dysfunction chapters risks reading as a judgement about the people those criteria describe. The search surface adopts the card recipe and drops the last /55 selected-state alpha.
  • efed8b5 — roll out to factsheets, calculators and services. Factsheets' three inline style objects per card are replaced by the shared accent attribute; calculators gain a domain accent at rest; services adopts the one selected encoding.

Verification

  • npm run verify:pr-local
PR-local verification summary:
- completed: check:runtime, check:installed-lock-parity, format:changed, lint, typecheck,
  test, build, check:rag:fixtures, check:medication-interactions,
  check:medication-lexicon-report
- failed: (none)
- not reached: (none)

Unit suite: Test Files 643 passed (643) | Tests 6885 passed | 4 skipped (6889).

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

check:playwright-browser-revision was failing on the known #255 drift (chromium-1194 installed against the pinned 1234). The pinned revision was installed and the check now reports OK (installed), so the gate ran against the pinned browser rather than being delegated.

429 passed (15.0m)

test-results/.last-run.json records {"status": "passed", "failedTests": []}, and the log carries 429 individual passes. Cited that way on purpose: the counted line and the run record are the evidence, not the wrapper's exit status. This run covers the six card commits at 2d6e26b; the two later main merges that branch automation has since pushed are covered by CI's own Production UI jobs on the current head.

One earlier local run of this gate is deliberately not cited as evidence. It reported 1 failed / 428 passed while the wrapper process reported exit 0 — the exact "exit code 0 alone is not proof" case AGENTS.md documents — and test-results/.last-run.json recorded "status": "failed". Its single failure was a Playwright strict-mode violation from two identical privacy paragraphs resolving inside [data-safety-plan-copy] on /safety-plan. That copy is rendered once in patient-safety-plan.tsx:904, the element appears once at :870, and the route mounts the component once at src/app/safety-plan/page.tsx:12 — so the duplication is a runtime/hydration artefact, consistent with the pre-existing hydration mismatch this app already logs on load. patient-safety-plan.tsx is not in this diff.

Design-system gates, with the ratchets moving the right way rather than being held:

Design-system contract passed (756 production files; raw colors 2; literal shadows 0;
legacy tap classes 0; edge conflicts 18; 1px shadow spreads 2).
Motion/z/palette ratchets: legacy shadow aliases 106.

legacyShadowAliases fell 113 → 106 and edge conflicts 19 → 18 as the cards moved off --shadow-card / --shadow-soft / --shadow-inset onto the --e ladder. check:icon-scale and check:type-scale both pass. check:bundle-budget measured against a fresh build: production, mockups and all five per-route budgets within tolerance.

Chromium inspection at 390px and 1440px in light, dark and forced-colors, on /tools, /?mode=tools, /factsheets and /services. Computed values confirmed the accents resolve per category (--type-service teal rgb(43,100,116) against --type-table green rgb(47,107,87)), remap correctly in dark, and flatten to Canvas/CanvasText under forced-colors — which the previous inline style values could not do.

Five ui-tools failures seen while iterating were investigated rather than assumed: all five reproduce on origin/main with this work removed (two /services/13yarn composer cases, two service-detail cases, and one tools mobile detail-sheet case). They occur only when the suite is pointed at a dev server; none of them recurs under verify:ui, which builds and serves its own isolated production bundle.

Not run, and not required by the changed paths: npm run eval:retrieval:quality, npm run eval:rag, npm run eval:quality, npm run verify:release, npm run check:supabase-project. No retrieval, ranking, selection, chunking, scoring, or answer-generation path is touched, and all of these are provider-backed.

Risk and rollout

  • Risk: low — presentation only. The largest behavioural surface is the type narrowing of ToolCatalogRecord.id, which is compile-time. The visible risk is that four factsheet categories, five tool areas, five calculator domains and two mode card surfaces change colour; a wrong accent is cosmetic and cannot alter clinical content, ordering, or access. specifierCard and formulationCard move from --surface to --surface-raised, which is the SPEC §177 correction and is a deliberate visible change.
  • Rollback: revert the offending commit; each reverts independently and in any order. efed8b5 reverts the surface adoption only; a5804cc alone restores the previous factsheet accents; 18b8601 is the only one with a type-level dependency, so revert it last.
  • Provider or production effects: None. No provider-backed command was run, no Supabase, OpenAI, Railway or hosted-CI call was made, and no environment or deployment configuration changed.

Clinical Governance Preflight

scripts/pr-policy.mjs classifies this diff as clinicalRisk: false, ragRanking: false, ui: true, so this section is not required. It is completed anyway because the change alters how a patient-facing factsheet is coloured, and the point of the change is to remove a false safety signal.

  • 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

No clinical decision-support behaviour changed: this diff renders no clinical content, changes no source metadata or review status, and touches no retrieval, answer, ingestion, or document-access path. The one clinically meaningful effect is subtractive — a colour that asserted caution about a whole category of patient handouts, without any review having produced that judgement, no longer does so.

Notes

The separation this branch introduces is worth stating plainly, because it is the rule the guards enforce: a category is a family of content; a semantic tone is a claim about safety. src/lib/semantic-tone.ts owns six tones where the colour is the claim, and docs/clinical-badge-system-guide.md states it as "meaning drives the colour, never the other way round". Category accents are therefore drawn only from the non-semantic --type-* and --tone-* families, and CategoryAccent has no semantic member — which makes the mistake unrepresentable at every call site rather than caught one site at a time.

Two related decisions follow the same reasoning and are visible changes:

  • risk-safety loses its permanent danger-red tile. Red there asserted caution about a route, not about a patient, and spent the loudest colour in the system on a navigation target. Safety is carried by the now-unique shield glyph and by a danger-toned selected state, which is a real state.
  • The calculators risk (suicide risk) domain takes an identity accent rather than --danger. The label already says "Suicide risk", and an instrument is not itself a warning.

Deliberately deferred, and unchanged by this branch: therapy-compass/therapy-card.tsx (its own SVG icon set, its own control recipes, its own IconTile, plus the open rawPaddingLiterals / rawGapLiterals debt recorded in ledger #261), the differentials-home.tsx card family, and forms/form-detail-page.tsx — the last two are detail-panel compositions rather than category-bearing list items, so they gain least from this work.

Services keeps a rank number in its leading tile rather than gaining a category glyph. It is a ranked referral list, the number is what the "Best fit" pill refers to, it doubles as the shortlist checkmark, and services records carry facets rather than a single category — so there would be nothing honest to put there.

One finding surfaced while doing the DSM work and left untouched, since it is data rather than presentation: src/data/dsm-clinical-content.json ships 18 per-category hex colours (#C43232 red for Psychotic Disorders, #2E9952 green for OCD & Related, #C49A00 amber for Mood Disorders). They are typed on DsmCategory, but DsmDiagnosis.category is Pick<DsmCategory, "key" | "label">, nothing under src/ reads color or css_class, and no .gmod/.gpsy CSS exists — so the palette is dead. If it were ever wired up it would reintroduce exactly the semantic-collision defect a5804cc fixes, and being raw hex it would also miss the dark and forced-colors remaps. Worth either deleting or converting to tokens in its own change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XGhewT2mmRoRTynjWfF1Vn

claude and others added 6 commits August 17, 2026 16:13
Icons and category colour were spread across ten independent maps and two of
them disagreed, so the same tool rendered differently depending on which screen
reached it.

- `launcherIconById` (applications-launcher-page.tsx) carried 13 tool ids;
  `iconByToolId` (tools-search-results-page.tsx) carried 8 with a different
  fallback, so `guidelines`, `care-plans`, `safety-plan`, `calculators` and
  `monitoring` showed a real glyph on the launcher and a generic `Grid2X2` in
  search results.
- Colour diverged the same way: the launcher tinted by tool area, the results
  page painted every tile `--type-source`, so one list read as a single purple
  family while the other grouped the same tools into five.
- `ShieldCheck` was assigned to `guidelines`, to `risk-safety`, and to the
  "Source-backed" status chip — three unrelated meanings, one glyph, reachable
  on a single card.
- `appIconTone` overrode the area map per id, routing `differentials` and
  `forms` to a tone key named `differentials`, so the advertised "category
  colour" was not a category colour.

`src/lib/category-identity.ts` is now the single source of truth. It is
framework-free (string glyph keys, no lucide) following the `semantic-tone.ts`
precedent, so data and server modules can name a glyph without the render layer;
`category-identity-icons.ts` is the only place that binds keys to components,
resolving through `createElement` as `factsheets-icons.ts` does to satisfy
`react-hooks/static-components`.

`ToolCatalogRecord.id` is narrowed from `string` to a `ToolCatalogId` union, so
`Record<ToolCatalogId, …>` cannot be under-filled: adding a tool without
choosing a glyph is now a type error rather than a silent runtime fallback.
`appModeIcons` keeps its name and shape but is derived rather than
hand-maintained, so its "keep in sync" comment is now a property of the type.

Accent delivery is `data-category-accent` → `--cat-accent`/`--cat-soft`/
`--cat-border` in globals.css rather than interpolated class names, which
Tailwind's scanner cannot see, and rather than inline styles, which bypass the
theme contract. Every accent aliases an existing non-semantic triad
(`--type-*`, `--tone-*`), so light, dark and forced-colors need no new
declarations.

`risk-safety` loses its permanent danger-red tile: red asserted caution about a
route rather than about a patient, spending the loudest colour in the system on
a navigation target. Safety is carried by the now-unique shield glyph and by the
danger-toned selected state, which is a real state.

Gates: typecheck, lint, `npm run test` (643 files, 6882 passed / 4 skipped),
check:design-system-contract, check:icon-scale, check:type-scale — all green.
No provider-backed check was run and none is required; no RAG surface touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGhewT2mmRoRTynjWfF1Vn
`categoryTheme()` drew two of its four category accents from the SEMANTIC
palette: Therapies on `--success-text`/`--success-bg` and Tests & procedures on
`--warning-text`/`--warning-bg`.

Those tokens carry meaning. `src/lib/semantic-tone.ts` defines six tones where
the colour IS the claim — warning means "pause, check, adjust, review", success
means a check passed — and `docs/clinical-badge-system-guide.md` states the rule
as "meaning drives the colour, never the other way round". Applying warning-amber
to an entire category of patient handouts asserted caution about content that
nothing had reviewed, and it did so on the largest surface the factsheet has: the
hero band. It also spent a colour the badge system needs, so a genuine caution
badge had to compete with its own page chrome.

All four categories now sit on non-semantic identity triads, sourced from
`FACTSHEET_CATEGORY_IDENTITY`:

  Medications        --clinical-accent -> --type-form
  Conditions         --tone-indigo     -> --type-source
  Therapies          --success-text    -> --type-service
  Tests & procedures --warning-text    -> --type-table

Medications moves too, for a different reason: it was the same blue as every
selection state, focus ring and evidence marker on the page, so the biggest
category was the one with no identity of its own.

`FactsheetTheme`'s shape is unchanged, so the ~20 call sites passing these as
inline style values are untouched. `FactsheetCategory` is now a re-export of the
registry's union, so the accent map and the content model cannot disagree about
what the categories are.

Three guards added to tests/design-token-contract.test.ts, each mutation-verified:
  - no `[data-category-accent]` rule may reference a semantic token
    (verified: pointing "table" at --warning-text fails the test)
  - `categoryTheme` must stay derived via `categoryAccentVars` and return no
    semantic token (verified: reinstating the old --warning-* return fails it)
  - `CategoryAccent` may not declare a semantic member, which is what makes the
    mistake unrepresentable at every call site rather than caught per site

Gates: typecheck, lint, `npm run test` (643 files, 6885 passed / 4 skipped),
check:design-system-contract — all green. No provider-backed check run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGhewT2mmRoRTynjWfF1Vn
…orks

Four cards that sit side by side in the same shell had drifted on every axis:

  ToolCard        rounded-lg  --shadow-card   p-4        border + tint + hover lift
  ServiceCard     rounded-xl  --shadow-inset  p-3 sm:p-4 border + ring/35
  CalculatorCard  rounded-lg  --shadow-inset  p-4        border + --shadow-soft
  Factsheet card  rounded-xl  --shadow-card   -          inline-style border-t-[3px]

Two radii, three resting elevations, and four different "this one is selected"
encodings, three of those expressed as fractional opacity on a token colour —
which is unreviewable, because the contrast an alpha lands on depends on
whatever surface happens to sit behind it in each theme.

`src/components/card-recipes.ts` holds the shared definitions: `cardSurface`,
`cardInteractive`, `cardSelected`, `cardAccentEdge`, `cardPadding`, and one
exported `focusRing` (that string is currently redeclared as a local const in a
dozen files and inline in dozens more).

Recipes rather than a registered component: COMPONENTS.md §0.4 measures 157
production importers of ui-primitives.tsx against 31 product imports across the
whole 54-component registry, and ledger #266 says adoption is demand-driven,
"never a race to 54/54". A function also sidesteps element polymorphism — these
cards are variously <article>, <button> and <Link>. A new module rather than
growing ui-primitives.tsx, which §0.4 already lists as over-budget and slated to
split.

Two deliberate departures from what the four cards did:

- `--shadow-inset` is dropped as a card elevation. It is the design-system
  bevel, and SPEC §4.7 says an inset well uses a border or inset shading, not
  both; pairing it with a border is why the services and calculator cards read
  flatter than the tool cards beside them. Resting is border + --e1.
- The recipes name `--e1`/`--e2`/`--e3` directly rather than the `--shadow-card`
  / `--shadow-soft` / `--shadow-hover` role aliases. TOKENS.md schedules those
  for retirement "inside the recipes first", and the contract ratchet counts
  every use — a new consolidating module reaching for one would move the count
  the wrong way. Caught by the gate on the first attempt (114 -> 115); now 113,
  unchanged from baseline.

`specifierCard` and `formulationCard` were byte-identical copies of the same
string; both now name the shared recipe. This moves those two modes' cards from
`--surface` to `--surface-raised`, which is the SPEC §177 correction (true-white
cards against the near-white page) and is a visible, intended change.

Consumers for `cardInteractive`/`cardSelected`/`cardAccentEdge` land in the
following commit.

Gates: typecheck, lint, `npm run test` (643 files, 6885 passed / 4 skipped),
check:design-system-contract, check:knip — all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGhewT2mmRoRTynjWfF1Vn
The launcher rendered two components for the same content — `ToolCard` and
`MobileToolRow` — and they had already drifted: different resting elevation
(--shadow-card vs --shadow-inset), different selected tint (/50 vs /55), and a
hover lift on one but not the other. They are now one component with a `density`
prop. Both test ids are kept: `application-card-*` and `application-row-*` are
each asserted by ui-tools and ui-smoke, and ui-smoke is blocking at zero
retries. Verified in Chromium at 390px that the row renders visible and the card
hidden, with all 13 of each still in the DOM.

Craft changes, launcher and tools search results:

- The fake button is gone. `Details` was a <span> painted as a solid accent
  button INSIDE the card's own <button>: it read as a nested control, was
  announced as nothing, was the loudest element on the card, and — being
  identical on every card — distinguished nothing. The card is the control; a
  chevron on the decoration tier says so, taking the category accent on hover so
  the affordance points at the card's own family.
- On the search results page the same button is real (that card is an <article>
  and not itself clickable), so it stays — but as `floatingControl` rather than a
  filled accent. Thirteen filled primaries down one list were thirteen primary
  actions, none of them the page's actual primary action.
- Titles move from `text-base font-extrabold` to `text-lg font-semibold`. At
  extrabold they matched the section heading above them, so a grid of cards read
  as a wall of headings. Size carries the hierarchy; weight stops trying to.
  SPEC §4.6 puts card titles at --text-lg.
- "Best for:" was a bold inline run inside the body copy, giving a label the same
  emphasis as the clinical text it labels. It is a kicker, so it uses the shared
  `eyebrowText` recipe.
- The magnifier beside "Best for" on the results page is dropped: "Best for" is
  not a search, and it spent an accent-coloured glyph on a label.
- The selected rail on a results row takes the tool's own category accent rather
  than the product blue, so it agrees with the tile beside it.
- `min-h-[9.25rem]` and `min-h-[5.25rem]` are replaced by content height with a
  `min-h-tap` floor. Production tap targets stay at min-h-12.
- The local `focusRing` const in both files now imports the shared one.

`risk-safety` keeps a danger-toned SELECTED state via the new
`cardSelectedDanger` — selection is a real state, unlike the permanent red tile
retired earlier in this branch.

`legacyShadowAliases` fell 113 -> 111 as the cards moved off --shadow-card and
--shadow-inset onto the --e ladder.

Gates: typecheck, lint, `npm run test` (643 files, 6885 passed / 4 skipped),
check:design-system-contract, check:icon-scale, check:type-scale — all green.
Chromium: check:playwright-browser-revision was failing on the known #255 drift
(1194 installed against the pinned 1234); the pinned revision was installed, the
check now reports OK, and ui-tools + ui-hydration + the ui-smoke tools
assertions were run locally against it. Five ui-tools failures were investigated
and are pre-existing: all four reproduce on origin/main with this work stashed
(two /services/13yarn composer cases, two service-detail cases, and the tools
mobile detail-sheet case), and the tools one passes when driven manually, so it
is environmental rather than a regression.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGhewT2mmRoRTynjWfF1Vn
…alculators and services

Factsheets
- The three inline `style` objects per card are gone. An inline value cannot be
  remapped by the dark or forced-colors blocks, so the old cards carried their
  light-mode tint into both. Verified in Chromium: the four accents now remap
  correctly in dark, and under forced-colors they flatten to Canvas/CanvasText
  as they should — identity colour is decoration, and the glyph and category
  chip carry the meaning in high contrast.
- The category browse pills take the same accents, so a pill and the cards it
  filters to now agree.
- Card titles move to `text-lg font-semibold`, and hover tints the title with
  the card's own category accent rather than the product blue.

Calculators
- The directory tile was grey until a card opened, so a closed directory showed
  five domains rendered identically and the domain was findable only by reading
  the chip. It now carries the domain accent at rest — which is what the chip
  beside it has always said in words.
- `CALCULATOR_DOMAIN_ACCENT` deliberately gives `risk` (suicide risk) an
  identity accent, not `--danger`. The label already says "Suicide risk", and an
  instrument is not itself a warning; a red tile on a directory row would claim
  urgency about a tool rather than about a patient.
- Open/closed states move onto `cardSelected` and the --e ladder.

Services
- Adopts `cardSurface` + `cardSelected`, retiring the fourth "this one is
  selected" encoding (`ring-1 …/35` — an alpha on a token colour, so what it
  contrasted against depended on whatever surface sat behind it per theme).
- The leading tile deliberately stays a RANK rather than becoming a category
  glyph: this is a ranked referral list, the number is what the "Best fit" pill
  refers to, and it doubles as the shortlist checkmark. Services has no single
  category axis either — records carry facets — so there would be nothing honest
  to put there.

Ratchets moved the right way: legacyShadowAliases 111 -> 107, edge conflicts
19 -> 18.

Deferred, unchanged: therapy-compass/therapy-card.tsx (own SVG icon set, own
control recipes, own IconTile, and the open rawPadding/rawGap debt from #261),
the differentials-home card family, and the forms detail cards — those are
detail-panel compositions rather than category-bearing list items.

Gates: typecheck, lint, `npm run test` (643 files, 6885 passed / 4 skipped),
check:design-system-contract — all green. Chromium inspection at 390/1440 in
light, dark and forced-colors against the pinned revision 1234.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGhewT2mmRoRTynjWfF1Vn
@BigSimmo
BigSimmo enabled auto-merge (squash) August 17, 2026 17:22
@coderabbitai

coderabbitai Bot commented Aug 17, 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: 31 minutes

Limit details: You’ve used all 1 included review currently available under your plan. You completed 99 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: 359e1ce5-9a36-4fb9-b297-fb3aa7dc44cd

📥 Commits

Reviewing files that changed from the base of the PR and between 7233819 and 380c193.

📒 Files selected for processing (24)
  • docs/outstanding-issues-inbox/3565972b-02c2-4ad8-b899-3fbde0d54725.json
  • src/app/globals.css
  • src/components/applications-launcher-page.tsx
  • src/components/calculators/directory-grid.tsx
  • src/components/card-recipes.ts
  • src/components/category-icon-tile.tsx
  • src/components/dsm/dsm-home-page.tsx
  • src/components/dsm/dsm-search-page.tsx
  • src/components/factsheets/factsheets-data.ts
  • src/components/factsheets/factsheets-home-page.tsx
  • src/components/formulation/formulation-ui.tsx
  • src/components/services/services-navigator-page.tsx
  • src/components/specifiers/specifier-ui.tsx
  • src/components/tools/tools-search-results-page.tsx
  • src/lib/app-mode-icons.ts
  • src/lib/category-identity-icons.ts
  • src/lib/category-identity.ts
  • src/lib/dsm.ts
  • src/lib/tools-catalog.ts
  • tests/category-identity.test.ts
  • tests/design-token-contract.test.ts
  • tests/dsm-category-colour-boundary.test.ts
  • tests/dsm-search-empty-state.dom.test.tsx
  • tests/ui-tools.spec.ts

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

@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.

BigSimmo and others added 3 commits August 18, 2026 01:28
The DSM-5 Diagnosis mode is where "a unique icon per category" stops being the
right answer, so this commit does the part that is clearly correct and
deliberately does not guess at the part that is not.

Repeated glyph, removed
- dsm-home-page.tsx gave all five "Browse categories" pills `BookOpenCheck` —
  the mode's own hero glyph, repeated once per category. An icon identical
  across every item in a set distinguishes nothing: it costs the space of an
  icon, competes with the label doing the actual work, and repeats the glyph
  already shown in the hero directly above. Without one, ModeHomeTemplate
  renders a tone dot, so the pills keep a leading mark.

Not replaced with per-category icons, on purpose
- There are 17 populated DSM-5 categories. Pictograms for diagnostic chapters
  are a clinical-content decision rather than a design one, and several would be
  actively wrong to guess at: the puzzle piece conventionally reached for on
  neurodevelopmental disorders is rejected by many autistic people, and any
  glyph chosen for the paraphilic, gender-dysphoria or sexual-dysfunction
  chapters risks reading as a judgement about the people those criteria
  describe. A wrong icon on a diagnostic class is worse than no icon.

Card recipe adopted on the search surface
- The results panel and the comparison aside move onto `cardSurface`, retiring
  `--shadow-soft` and a `--shadow-inset`-plus-border pairing (SPEC §4.7: an
  inset well uses a border or inset shading, not both).
- The selected-row fill drops its `/55` alpha — the last of the four selected
  encodings this branch has been retiring. An alpha on a token colour is
  unreviewable because what it contrasts against depends on the row stripe
  behind it, which differs per theme. Rows keep no shadow of their own: they sit
  inside the results panel, and SPEC §4.7 forbids a child heavier than its
  parent, which is also why `cardSelected` is not used here.
- The compare toggle moves from `rounded-xl` to `rounded-lg`, matching every
  other tile in the product.

legacyShadowAliases 107 -> 106.

Gates: typecheck, lint, `npm run test` (643 files, 6885 passed / 4 skipped),
check:design-system-contract — all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGhewT2mmRoRTynjWfF1Vn
@BigSimmo BigSimmo changed the title Claude/card review optimize h0pidc Unify the card layer behind one category-identity registry Aug 17, 2026
claude and others added 15 commits August 17, 2026 17:43
The vendored DSM export gives every category a `css_class` (e.g. "gmod") and a
raw hex `color`. Nothing in the app read either field and no `.gmod`/`.gpsy` CSS
exists, so this looked like dead data in a file — but it was dead data on the
wire. Measured against the running app before this change, `/dsm/search` served
all of them in its payload:

  C43232  1     (Psychotic Disorders — red)
  2E9952  1     (OCD & Related — green)
  C49A00  1     (Mood Disorders — amber)
  gmod    1

and zero after. `dsmCategories` is handed to `DsmSearchPage` as a prop, so the
whole palette was being serialised into the page and shipped to every browser.

Three of those hexes sit on the semantic palette. Had anything ever rendered
them, three diagnostic categories would have worn the danger, success and
warning hues `semantic-tone.ts` reserves for claims about safety — the same
defect this branch removed from factsheets, where an entire category of patient
handouts wore caution-amber no review had produced. Being raw hex they also
could not be remapped by the dark-theme or forced-colors blocks the way a
`--type-*` / `--tone-*` token is.

Fixed at the two boundaries that matter:

- `DsmCategory` no longer declares `css_class` or `color`, so reading
  `category.color` is a compile error rather than a live wire.
- `dsmCategories` projects to the three fields the app uses instead of spreading
  the raw export. The type change alone was not enough and the new runtime guard
  caught that: the objects kept the keys at runtime and the payload kept the
  hexes. The projection is what actually removes them.

The JSON itself is deliberately untouched. It is a snapshot of the upstream
`dsm-5-diagnosis` repository with no generator in this repo, so editing it would
diverge the snapshot from its source and be overwritten by the next export.

`tests/dsm-category-colour-boundary.test.ts` holds all of it, including a check
that the export still ships the colours — if upstream drops them the guard fails
loudly rather than quietly passing against nothing.

Also hardens the one Playwright assertion this surfaced through. A Production UI
run saw the safety-plan privacy notice resolve to two identical <p> elements.
Nothing renders it twice: the copy appears once in patient-safety-plan.tsx:904,
the panel carries `data-safety-plan-copy` once at :870, /safety-plan mounts the
component once, and 240 DOM samples across six loads through the hydration
window never saw more than one. That run caught a transient second tree during
navigation. The test now asserts `toHaveCount(1)` on the panel before reading
text out of it, so it waits for a settled tree instead of sampling mid-swap —
tightening the assertion rather than loosening it, and stating the
single-panel invariant the suite previously only assumed.

Gates: typecheck, lint, `npm run test` (650 files, 6996 passed / 4 skipped),
check:design-system-contract, and the safety-plan and DSM Playwright specs — all
green. `/dsm`, `/dsm/search` and `/dsm/search?q=` all serve 200 after the
projection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGhewT2mmRoRTynjWfF1Vn
Resolves the one real conflict GitHub reported (`mergeable_state: dirty`, base
at dc7e518): `tests/design-token-contract.test.ts`.

Both sides appended a new `describe` block to the end of the same file and the
two shared a trailing `});` pair, so git could not tell them apart:

- ours: "category accents stay out of the semantic palette" — the three guards
  keeping identity accents off the danger/warning/success/info families.
- theirs: "responsive breakpoint tokens (Task #336)" — the --bp-* / --breakpoint-*
  assertions.

There is no semantic overlap between them, so both are kept and each is closed
explicitly rather than sharing a terminator. Verified by content, not by the
merge exiting cleanly: the file now declares both describes, and the suite runs
40 tests where ours alone ran 39 and theirs added 1 — so neither side was
silently dropped.

Gates on the merged tree: typecheck, lint, `npm run test` (651 files, 7016
passed / 4 skipped).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGhewT2mmRoRTynjWfF1Vn
`Advisory UI` failed on this PR's head with
`ui-tools-search-mode-mockup.spec.ts › phone filter sheet follows the shared
local-filter behavior`. Attribution was established before any fix was
attempted: the spec fails under `--project=chromium-mockups` on BOTH this branch
and `origin/main` (dc7e518), so it is pre-existing and not caused by the card
work. It belongs in the ledger rather than inside this PR.

It surfaced only now because the `ui-advisory` lane fires on
`advisory_ui_changed` and had been `skipped` on every earlier run of this PR;
the merge from main switched it on. It is non-blocking by design —
`continue-on-error: true`, and absent from `pr-required`'s `needs:` list — and
`verify:ui` excludes `@mockup`, which is why a 429-pass local run never reached
it.

Worth noting for whoever picks it up: the failing assertion is a filter *count*
(`2 showing`), not the layout overflow I first suspected, and the mockup renders
the production `ToolsSearchResultsPage` — so if the count is genuinely wrong it
affects `/tools`, not just design scratch. The queued row says not to edit the
expected number to match observed output without first establishing which is
correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGhewT2mmRoRTynjWfF1Vn

Copy link
Copy Markdown
Owner Author

Overlap advisory (part of a cross-PR conflict sweep).

This branch has two overlaps with other PRs:


Generated by Claude Code

Second conflict resolution, against main at 3261272:
`src/components/formulation/formulation-ui.tsx`.

Main narrowed the `information-page-shell` import to `InformationPageShell`
alone after removing the `InformationPageBreadcrumbs` usage from this file; this
branch had added a `cardSurface` import to the same block. Both are kept, with
main's narrower shell import taken — checked rather than assumed: after the
merge `InformationPageBreadcrumbs` appears exactly once in the file, on the
import line alone, so restoring it would have left a dead import and failed
lint.

Gates on the merged tree: typecheck, lint, `npm run test` (651 files, 7016
passed / 4 skipped).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGhewT2mmRoRTynjWfF1Vn

Copy link
Copy Markdown
Owner Author

Checked back on the merge-conflict risk flagged earlier against formulation-ui.tsx and tests/design-token-contract.test.ts (from #2072 and #2076 both merging into main): this branch has already synced with current main (merge commits a8fe86d, 1bd091c) and both files are clean — no conflict markers, nothing left to resolve. No action needed from me here.


Generated by Claude Code

@BigSimmo
BigSimmo merged commit 981d85d into main Aug 18, 2026
26 of 27 checks passed
@BigSimmo
BigSimmo deleted the claude/card-review-optimize-h0pidc branch August 18, 2026 04:16
BigSimmo pushed a commit that referenced this pull request Aug 21, 2026
…last week

With the clone deepened to full history (4,922 commits back to 2026-05-19), every
symbol this branch removes can finally be dated instead of guessed at. Of the 59
top-level declarations removed, 56 date to 2026-05, 06 or 07. Three did not, and
all three are restored here by reverting their files to the base commit:

- `pruneExpiredRetrievalLogs` (src/lib/answer-telemetry.ts), added 2026-08-21 in
  d745d15 "resolve 29 audit findings across clinical safety, privacy, worker,
  and api domains". A retrieval-log retention helper from a privacy audit is
  exactly the kind of thing that has no caller yet because the caller is the
  next step.
- `therapyNeedsReviewCount` (src/lib/therapies.ts), added 2026-08-19 in adf93a7
  "ship Therapy in production with its review state disclosed" (#2150).
- `cardPadding` (src/components/card-recipes.ts), added 2026-08-18 in 981d85d
  "card review optimize" (#2060), a design-token recipe.

`src/lib/therapies.ts` is reverted whole rather than surgically, so
`therapyRecordExists` comes back with it; it is four lines and the file belongs
to a feature that shipped two days ago.

After this commit no symbol removed by this branch was introduced later than
2026-08-12, verified by re-deriving the removed-declaration list from the diff
and dating each one with `git log --reverse -S`.

Verification: typecheck clean, lint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKbNfTZM1vzUTRsuAS4Mxv
Sign up for free to 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