Skip to content

feat(design-system): give the brand a written identity and a per-ground mark - #2462

Open
BigSimmo wants to merge 1 commit into
mainfrom
claude/design-system-mobile-redesign-sha4cn
Open

feat(design-system): give the brand a written identity and a per-ground mark#2462
BigSimmo wants to merge 1 commit into
mainfrom
claude/design-system-mobile-redesign-sha4cn

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • Brand copy had four homes, and two of them disagreed. The page metadata, the web manifest, the OG card and the install prompt each carried a hand-typed sentence; two described PsychSift as a "RAG knowledge base" and two as a "knowledge base". Nobody decided to say two things — the second sentence was written later by someone who could not see the first. src/lib/brand.ts now owns the name, the catchphrase and the descriptive lines, and tests/brand-copy.test.ts fails if any of them is retyped as a literal outside that module.
  • A catchphrase, chosen to be defensible rather than confident. "From question to source." promises a path, not a verdict, which is the only kind of claim this product may make given it is a clinical reference prototype and not validated decision support. A test enforces that boundary in words: the line may not contain trust, accuracy, safety or evidence-based claims. The wording is not new either — guide-content.ts already taught the "evidence-first workflow from question to source". "RAG" also leaves the user-facing description; it is accurate internally and meaningless on an install sheet.
  • The logo had no colour role of its own. It read --clinical-accent directly, so a future accent retune for selection/evidence/send would have silently repainted the brand, and a surface needing the mark to hold its ground had nowhere to go but a pressed-state token. Adds --brand-mark, --brand-mark-emphasis and --brand-mark-contrast as aliases onto the accent ramp — one declaration each, since .dark and the forced-colors block both land on <html> — plus a tone prop on <BrandMark>.
  • Every production chrome surface now asks for the emphasis tone. The mark is two counter-turning strokes whose lower one is the thinner read, so at chrome sizes (28–40px) on a near-white band the resting accent let the bottom of the S dissolve into the ground while the dot-and-bowl cluster at the top stayed solid: the mark went top-heavy. Emphasis is a full ramp step, not a nudge, so it restores an even read without touching the geometry. Design-scratch mockups keep the resting tone.
  • The phone drawer header spent roughly 160px saying what the drawer is — a third of the first screenful above the notch on a 390px phone, to a user who had just tapped the menu button and could see it. The description also wrapped to two lines, because its column is squeezed between the brand mark and a 48px close control. It now reads as a brand lockup: mark, wordmark, one strapline that cannot wrap, and a close control that keeps its full 48px tap target while giving up the boxed chrome that made it the loudest thing in the header. The desktop sidebar takes the same lockup, so the two entry points to the same navigation read as one brand.
  • The functional sentence moves to sr-only, where it is still the accessible description. A screen-reader user arriving in the dialog wants to know what the dialog contains, not what the product stands for. The two audiences get different sentences rather than one compromise serving neither.

These are overrides on the shared Sheet, not edits to it: every other dialog in the app uses that header, and the case for a compact brand header is specific to a navigation drawer that is already showing its own contents.

Verification

  • npm run verify:pr-local — completed check:runtime, check:installed-lock-parity, format:changed, lint, typecheck; test then failed on six pre-existing failures (see below), so build and the trailing domain checks were not reached by the wrapper and were run directly instead.
  • npm run test11721 passed | 4 skipped across 929 files. Six failures are pre-existing and not in this diff, in clinical-hazard-controls, privacy-readiness-contract, rag-plan-package-parity and caring-contacts-contact-time-adjustment. Reproduced on a clean stashed tree, which failed the same four files (six cases there; one caring-contacts case is flaky between runs). None of the four touches brand copy, tokens, BrandMark, Sheet or the sidebar.
  • npm run build — succeeded on a cleared .next. Client bundle secret surface check passed.
  • npm run check:bundle-budget on that fresh build — production, routes and mockups all within tolerance (route / 237.1 KiB gzip against a 278.5 KiB baseline). The baselineSource ... does not resolve to a local Git commit warning is a shallow-clone ancestry limitation and pre-exists this branch.
  • npm run check:design-system-contract — passed, every ratchet at or under baseline; sub-floor interactive min-heights 2, matching baseline. Also ran check:design-system-adoption (55 components, 84 roots) and check:design-sync-contract.
  • npm run check:type-scale, npm run check:icon-scale — both clean under --strict.
  • npm run lint — clean at --max-warnings 0. npm run typecheck — exit 0, unscoped.
  • npm run format — whole-tree, committed with the work rather than after it.

UI verification not run: npm run verify:ui needs a running dev server and a Chromium journey pass, which this container has provisioning in the background and which I did not confirm ready; the Chromium journeys in CI are the gate of record for this diff. The affected surfaces have offline DOM coverage that did run (sheet.dom, sheet-focus.dom, sheet-focus-stress.dom, sidebar-production.dom, mobile-sheet-safe-area-contract, pwa-lifecycle.dom — 70 passed across the sheet/sidebar set).

One measurement worth stating plainly: the header's height reduction is reasoned from the token values and the removed wrapping line, not from a screenshot on a device. The two-line description is gone by construction (truncate on a single strapline), but the exact pixel saving is an estimate until a phone journey confirms it.

Risk and rollout

  • Risk: Low, with one broad surface. No data, no schema, no retrieval, no auth. The behavioural surface is display wording, one CSS custom-property group, and the ink of the brand mark. The broadest part is the mark's colour, which changes on every production chrome surface at once; it moves one ramp step within the existing accent scale, so it cannot produce an off-brand hue, only a deeper or brighter one per theme.
  • Rollback: single-commit revert. The tokens are additive aliases and the tone prop defaults to the previous behaviour, so reverting the call sites alone would also restore the old appearance without touching the token layer.
  • Provider or production effects: None. No OpenAI, Supabase, Railway or hosted-CI call was made; every gate above is local and offline.
  • RAG impact: no retrieval behaviour change — this diff touches no file under src/lib/rag/** and none of the protected ranking surfaces (clinical-search, retrieval-selection, released-search-order, ranking-config, answer-ranking, answer-verification), adds no comparator key, and leaves the golden fixture and the eval harness untouched. Verified by listing the diff's fourteen paths against that set.

Clinical Governance Preflight

  • 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

Notes against those items, in full rather than as ticks alone.

This change introduces no retrieval path, no source-backed claim, no clinical content and no new data. It changes brand wording, a colour role, and the layout of one navigation drawer header. No Supabase target, migration, credential or document-access path is touched, and nothing in the diff runs server-side beyond static metadata generation.

The clinically material part is the catchphrase, and it was constrained deliberately. A brand line sits above every surface and is read as the product's own claim about itself, so on a clinical tool it is a governance artefact rather than decoration. "From question to source" describes the mechanism — take a clinical question, land the reader on the passage in the original document — and asserts nothing about whether the guidance found is correct, current or sufficient. That restraint is the point: the entire citation architecture exists so a clinician can check those things for themselves, and a line like "the answers you can trust" would assert exactly what the architecture is built to let them verify. tests/brand-copy.test.ts encodes the boundary so a future edit has to argue with a failing test rather than slip past review.

The one item touching a clinical-facing page is privacy-quiet-signal-page.tsx, and the change there is the brand mark's tone attribute only — no privacy copy, no retention statement, no obligation band, and no change to what the page discloses.

Failure behaviour is unchanged and remains conservative. Nothing in this diff sits on a degradation path: the tokens are aliases that resolve to the existing accent in every theme including forced-colors, and the drawer header renders identically whether or not the brand module resolves, because the strings are build-time constants rather than fetched content.

Notes

Recorded rather than guessed, and deliberately not fixed here: the app renders the mark's display geometry at every chrome size. docs/brand/psychsift-logo.md and src/lib/brand-mark.ts describe a small-size optical cut for exactly that 20–32px band — a widened stroke gap (BRAND_STROKE_PATH_SMALL) and a point slid out of its cradle (BRAND_POINT_SMALL) so the dot separates from the S instead of fusing into it. Nothing in the app uses either; only the favicon and the raster icon routes do. That fusion is the actual root cause of the top-heavy read, and the emphasis tone compensates for it rather than fixing it.

Fixing it properly means deriving a bare-glyph placement transform for the small paths, which is new brand geometry. That is an artwork decision for the owner, not a styling one an implementation PR should take unilaterally — brand-mark.ts says in terms not to redraw the construction by hand — so it is written down here instead.

Also unaddressed, and visible in the same screenshots: the phone chrome band on the privacy route paints --surface-glass at 95%, which reads whiter than the tinted page beneath it and leaves a seam the mark appears to float on. Changing it means moving a phone chrome ground, which is docs/search-chrome-behaviour.md territory and wants verify:phone-chrome evidence of its own. It is a separate change, not a line to slip into this one.


Generated by Claude Code


Note

Low Risk
Display-only branding, CSS token aliases, and sidebar/drawer layout—with broad but low-impact visual change to mark color on chrome surfaces; no auth, data, or retrieval paths.

Overview
Introduces src/lib/brand.ts as the single source for product name, catchphrase ("From question to source."), metadata description (drops user-facing "RAG"), install tagline, menu a11y copy, and OG alt text. Metadata, manifest, OG image, PWA install UI, and the Clinical Guide sidebar now import those constants instead of hard-coded strings; tests/brand-copy.test.ts fails if owned lines are retyped elsewhere and checks catchphrase length and clinical-claims boundaries.

Adds --brand-mark, --brand-mark-emphasis, and --brand-mark-contrast in globals.css (aliases on the clinical accent ramp) so logo ink can diverge from UI accent tuning. BrandMark gains a tone prop (resting | emphasis | contrast); production chrome call sites switch to emphasis for clearer small marks on light bands.

Clinical Guide navigation gets a tighter brand lockup: expanded sidebar and phone drawer show wordmark plus a truncating strapline; the drawer's functional description moves to sr-only while Sheet header spacing/close styling is overridden locally (not in the shared dialog component).

Reviewed by Cursor Bugbot for commit de9d97b. Configure here.

…nd mark

Three related things the design system did not have: one home for the product's
written identity, a colour role for the logo, and a phone drawer header built
for a phone.

**Brand copy had four homes.** The page metadata, the web manifest, the OG card
and the install prompt each carried a hand-typed sentence, and two of them
disagreed about whether PsychSift is a "RAG knowledge base" or a "knowledge
base". Nobody decided to say two things; the second sentence was written later
by someone who could not see the first. `src/lib/brand.ts` now owns the name,
the catchphrase and the descriptive lines, and `tests/brand-copy.test.ts` fails
if any of them is retyped as a literal outside that module.

The catchphrase is "From question to source." It promises a path, not a verdict
— which is the only kind of claim this product may make, given it is a clinical
reference prototype and not validated decision support. A test enforces that
boundary in words: the line may not contain trust/accuracy/safety claims. The
wording is not new, either; `guide-content.ts` already taught the "evidence-first
workflow from question to source".

"RAG" also leaves the user-facing description. It is accurate internally and
meaningless on an install sheet.

**The logo had no colour role of its own.** It read `--clinical-accent` directly,
so a future accent retune for selection/evidence/send would have silently
repainted the brand, and a surface needing the mark to hold its ground had
nowhere to go but a pressed-state token. Adds `--brand-mark`,
`--brand-mark-emphasis` and `--brand-mark-contrast` as aliases onto the accent
ramp — one declaration each, since `.dark` and the forced-colors block both land
on `<html>` — plus a `tone` prop on `<BrandMark>`.

Every production chrome surface now asks for `emphasis`. The mark is two
counter-turning strokes whose lower one is the thinner read, so at chrome sizes
(28-40px) on a near-white band the resting accent let the bottom of the S
dissolve into the ground while the dot-and-bowl cluster stayed solid: the mark
went top-heavy. Emphasis is a full ramp step, so it restores an even read
without touching the geometry. Mockups keep the resting tone.

**The phone drawer header spent ~160px saying what the drawer is.** A third of
the first screenful above the notch on a 390px phone, to a user who had just
tapped the menu button and could see it — and the description wrapped to two
lines because its column is squeezed between the mark and a 48px close control.
It now reads as a brand lockup: mark, wordmark, one strapline that cannot wrap,
and a close control that keeps its full 48px tap target while giving up the
boxed chrome that made it the loudest thing in the header. The functional
sentence moves to `sr-only`, where it is still the accessible description —
a screen-reader user wants to know what the dialog contains, not what the
product stands for, so the two audiences get different sentences instead of one
compromise serving neither. The desktop sidebar takes the same lockup.

These are overrides on the shared Sheet, not edits to it: every other dialog
uses that header, and the case for a compact brand header is specific to a
navigation drawer already showing its own contents.

Verification
- Full unit suite: 11721 passed | 4 skipped, 929 files. 5 pre-existing failures
  in clinical-hazard-controls, privacy-readiness-contract, rag-plan-package-parity
  and caring-contacts-contact-time-adjustment — all reproduced on a clean stashed
  tree (6 there; one caring-contacts case is flaky), none in this diff.
- Design-system contract passed; every ratchet at or under baseline. The
  hardcoded-CSS-duration count reads 25 against a baseline of 26 on a clean tree
  too, so it is pre-existing slack, not this change masking a finding.
- check:type-scale, check:icon-scale clean. Lint clean at --max-warnings 0 on the
  changed files. Typecheck exit 0, unscoped. Whole-tree format committed.

Not done here, and recorded rather than guessed: the app renders the mark's
display geometry at every chrome size, where docs/brand/psychsift-logo.md
describes a small-size optical cut (a widened stroke gap and a point slid out of
its cradle) for exactly that band. That is the root cause of the top-heavy read
that the emphasis tone compensates for, and fixing it properly means new brand
geometry — an artwork decision, not a styling one.

RAG impact: no retrieval behaviour change — this diff touches no file under
src/lib/rag/** and none of the protected ranking surfaces (clinical-search,
retrieval-selection, released-search-order, ranking-config, answer-ranking,
answer-verification), adds no comparator key, and leaves the golden fixture and
eval harness untouched. Verified by listing the diff's paths against that set.

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

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b97e246-ea49-409c-a9ae-94ff0b2c04e7


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T06:27:33.938032Z de9d97b PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cursor

cursor Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_99d3b6ba-0ddc-4b4c-a0f0-253f7ebd5d59)

@supabase

supabase Bot commented Aug 30, 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 ↗︎.

Copy link
Copy Markdown
Owner Author

Advisory UI is red, and it is not this PR's

What failed. One test, in the non-blocking lane:

tests/ui-tools-search-mode-mockup.spec.ts:24
  Perfected Tools results mode mockup @mockup ›
  desktop uses universal search and keeps results beside the selected-tool panel

1 failed · 2 skipped · 127 passed (4.7m). The lane carries continue-on-error: true (ci.yml L754) and is not in pr-required, so it cannot block the merge; this note is here because a red check with no explanation is worse than a red check with one.

Why it is not this PR's — traced, not assumed. I walked the full transitive @/-import graph from the route under test (/mockups/tools-search-modeToolsSearchModeMockupToolsSearchResultsPage). It reaches 181 modules and none of the nine files this PR changes:

src/lib/brand.ts
src/components/clinical-dashboard/{brand,ClinicalSidebar,master-search-header,account-setup-dialog}.tsx
src/components/{privacy-quiet-signal-page,pwa-lifecycle}.tsx
src/components/ward-management/{ward-management-navigation,ward-sidebar-content}.tsx

The only other coupling is globals.css, where this PR is purely additive: three custom properties (--brand-mark, --brand-mark-emphasis, --brand-mark-contrast) that are aliases onto the existing accent ramp and are read by exactly one component, <BrandMark>, which this route never renders. git diff --numstat src/app/globals.css is 21 0 — no deletion, so nothing existing changed value. A custom property nothing on the page consumes cannot move layout or pointer events.

Root cause, from the log. It is an overlay race, not a regression. The test types into the composer and then immediately clicks a filter:

await page.locator('[data-testid="global-search-input"]:visible').fill("Safety");
await expect(mockup.getByRole("heading", { level: 1, name: "Safety" })).toBeVisible();
const treatmentFilter = categoryRail.getByRole("radio", { name: "Treat (2)" });
await treatmentFilter.click();          // ← times out here

Typing opens the universal-search suggestion panel, which paints over the category rail. Playwright's actionability check then fails on the same node for 113 retries:

<div role="presentation" class="…">Current mode · Tools · 2</div> from <div … id="desktop-page-search-composer-slot"> subtree intercepts pointer events

The heading assertion in between passes — the results heading updates while the panel is still open — so it does not serve as the barrier the test needs. Whether the click lands depends on whether the panel has closed yet, which is why this is intermittent rather than deterministic.

Proposed patch, not pushed. The fix belongs to that spec, not to a brand-tokens PR, and widening this diff to carry it would put an unrelated mockup change under a design-system commit:

await page.locator('[data-testid="global-search-input"]:visible').fill("Safety");
await expect(mockup.getByRole("heading", { level: 1, name: "Safety" })).toBeVisible();
// Typing opens the universal-search suggestion panel over the category rail.
// Dismiss it and wait for it to actually clear before clicking through.
await page.keyboard.press("Escape");
await expect(page.getByTestId("desktop-page-search-composer-slot")).toBeEmpty();
await treatmentFilter.click();

Stated honestly: I have not run that patch. This container's Playwright browsers were still provisioning, so the second assertion in particular is written from the DOM in the failure log rather than from a green run, and the exact settled state of that slot should be confirmed before it lands. Pushing an unverified fix for an unrelated non-blocking test is the speculative push worth avoiding, so it is written down instead.

I am re-running the lane once to confirm the intermittency. No other check is red: Build, Static PR checks, Production UI critical, Safety and config checks, Lighthouse budget, Caring Contacts database, Change scope, PR policy, PR mergeability, Gitleaks, Semgrep and GitGuardian have all passed on de9d97b.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

CI triage

CI failed on this PR. Automated classification of the 3 failed job(s):

  • Production UI (1)needs investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • Advisory UInot baselined: this job did NOT run on the main comparison below (path-scoped skip), so that run says nothing about it either way. Treat the comparison as absent, not green, and inspect the failing step.
  • PR requiredneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.

Compared with main CI run #14502 (failure). That run's conclusion is an aggregate and did not exercise Advisory UI.

Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger.

Copy link
Copy Markdown
Owner Author

Re-run result: required checks green, and a correction

PR required is green. Production UI (1) passed on the re-run of the same commit (de9d97b), unchanged — so its ui-smoke failure at expectAccountProviderLayout was intermittent. mergeable_state is unstable rather than clean only because the non-blocking Advisory UI check still reports red.

Correcting myself on the other one. In my previous comment I described the Advisory UI mockup failure as intermittent and said I was re-running to confirm that. The re-run reproduced it, on the same commit. So it is deterministic, not flaky, and my framing was wrong — worth stating plainly rather than leaving the earlier comment as the record.

What does not change is whose failure it is. The import trace stands: the route under test reaches 181 modules and none of this PR's nine changed files, and globals.css is additive-only (21 0) with its new properties consumed solely by <BrandMark>, which that route never renders. A reproducible failure in code this PR cannot reach is a pre-existing defect this PR surfaced, not one it caused.

Two things follow, and I am doing neither here:

  1. It is not fixed in this PR. The remedy belongs to ui-tools-search-mode-mockup.spec.ts, and carrying an unrelated mockup fix inside a design-system commit is the widening the repo's own rules warn against. The proposed patch is in my previous comment; it remains unverified because this container's Playwright browsers were still provisioning, and now that the failure is known to be deterministic rather than flaky, that patch should be run before anyone trusts it — a deterministic overlay race may need the barrier to be the panel's actual dismissal rather than an Escape keypress.
  2. The lane is not baselined. Advisory UI is gated on advisory_ui_changed and did not run on the main comparison, so as the triage bot correctly says, that run is silent about it — absent, not green. I have not established whether this test also fails on main; I would need a run of that lane on the base to say, and I am not going to assert it either way without one.

Summary of the whole run on de9d97b: PR required, Build, Unit coverage, Static PR checks, Production UI critical, Production UI (1), (2), (3), Safety and config checks, Lighthouse budget, Caring Contacts database, Change scope, PR policy, PR mergeability, Gitleaks, Semgrep, GitGuardian — all green. Codex review completed with no findings. Advisory UI red, non-blocking, unrelated, reproducible, and left for its own change.

Worth noting for the PR body's own verification section: the six unit failures I recorded from my local run did not reproduce in CI — Unit coverage passed. They are an artefact of this container, not of the tests, and the PR body's claim that they are pre-existing is right about the conclusion but understates it; CI is the better evidence and it is clean.

That is my one re-run spent, and CI has settled. Merging is the owner's call.


Generated by Claude Code

@BigSimmo
BigSimmo enabled auto-merge August 30, 2026 11:20
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