From a6c2b4e92374e9002fb00c547eb5677d01ce538c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Jul 2026 17:37:46 +0000 Subject: [PATCH 1/3] polish(design): fix forced-colors button labels, tools rail truncation, privacy microcopy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 5 design-polish sweep (audit-then-fix). Audit: 3 design guards clean; static re-score of docs/redesign/07-token-adoption-audit.md (all July 3 debt resolved); 43-capture route sweep (15 routes x desktop/phone + 320px spots + dark/reduced-motion/forced-colors) with 0 overflow / 0 console errors. Fixes: - globals.css forced-colors block: Chromium paints a Canvas backplate behind glyph runs, so glyph tokens resolving to Canvas/ButtonFace rendered solid button labels as blank boxes (axe-invisible). Command controls flatten to the native HCM pairing (ButtonFace fill / ButtonText glyphs — SVG strokes get no backplate, so a dark fill also swallows icons); accent fills keep their system colors and flip only glyph tokens to ButtonText. New ui-accessibility.spec.ts test locks the tokens off the Canvas resolution. - applications-launcher-page.tsx: desktop 6-up quick-action rail truncated card titles at 1440x1000 ("Ask eviden...", "Safety che..."); tightened card metrics (icon col 2.25rem->2rem, gap-3->gap-2, px-3->px-2.5, icon h-9->h-8) so all six titles render whole. Phone icon grid untouched. - privacy/page.tsx: JSX drops a newline adjacent to a tag, rendering "patient-record systemand does not ask"; explicit interword space added and locked by a privacy-ui.test.ts assertion. Dated July 18 run appended to docs/redesign/07-token-adoption-audit.md (design-qa.md is archived; the rubric doc carries dated audit runs). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EXsJcLrbZUXwnBeG91cVo9 --- docs/redesign/07-token-adoption-audit.md | 66 +++++++++++++++++++ src/app/globals.css | 20 ++++-- src/app/privacy/page.tsx | 5 +- src/components/applications-launcher-page.tsx | 4 +- tests/privacy-ui.test.ts | 3 + tests/ui-accessibility.spec.ts | 42 ++++++++++++ 6 files changed, 129 insertions(+), 11 deletions(-) diff --git a/docs/redesign/07-token-adoption-audit.md b/docs/redesign/07-token-adoption-audit.md index bb7e6562d..683f331b3 100644 --- a/docs/redesign/07-token-adoption-audit.md +++ b/docs/redesign/07-token-adoption-audit.md @@ -78,3 +78,69 @@ findings as "clean before the pattern spreads," not shipped-primitive regression - Raw palette: `rg '\b(bg|text|border|ring|from|to|via|fill|stroke)-(gray|slate|…|rose)-(50|…|950)\b' src/components -g '*.tsx'` - Dead bridge: `rg '("|'"'"'| )(bg-surface|text-muted|text-heading|…)( |"|'"'"')' src` → 0 hits. - Arbitrary values ranked: `rg -oN '…-\[[^]]+\]' src/components -g '*.tsx' | sort | uniq -c | sort -rn` + +## July 18 run — Phase 5 design-polish sweep (re-score + live route sweep) + +Scope: full re-run of the July 3 grep method on current `main`, plus a live +route sweep — 15 production routes × (1440×1000 desktop + 390×844 phone), +320×844 spot checks on the 4 densest routes, and dark / reduced-motion / +forced-colors desktop spots on `/`, `/differentials`, `/documents/search`. +Server identity confirmed via `/api/local-project-id` before attaching. + +**Score: 92/100** — 0 critical · 0 medium · 2 low (naming consistency and the +mockup-file palette leaks, both unchanged P3 charter exclusions). + +### Re-score of July 3 findings + +| Item | July 3 | July 18 | +| --------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| M1 dead `@theme inline` bridge | Fix | ✅ Resolved (bridge deleted). | +| M2 raw-palette tone helpers | Done on branch | ✅ Landed; production `src/components` has 0 raw-palette classes (3 mockup files only). | +| M3 ~295 hardcoded px font sizes | Partly unblocked | ✅ Adopted; `check:type-scale --strict` passes with 0 arbitrary sizes in `src`. | +| L4 hardcoded white/black neutrals | Polish | ✅ Down to 4 sites, all the deliberate `ring-1 ring-white/N dark:ring-white/10` glass-highlight idiom with explicit dark variants. 0 accent-button `text-white`. | +| L5 `border-blue-400` | Polish | ✅ Gone. | +| L6 tap-target magic numbers | Done | ✅ Holds (0 arbitrary tap tokens). | +| L7 `rounded-[var(--radius-lg)]` | Polish | ✅ Gone. | +| L8 recipe contract docs | Done | ✅ Holds (`09-ui-primitives-recipes.md`). | + +Hex audit note: every current hex hit in production components is a legitimate +class — print-handout surfaces (`FactsheetPrintSheet`, therapy-compass sheet +`@media print`), official provider brand marks (Google/Microsoft), web-vitals +console-log colors, PR-number code comments (regex false positives), and the +white switch-knob-on-accent-track pattern. + +### Route sweep result + +43 captures: 0 horizontal overflow at any viewport (including 320px), 0 +console errors, 0 failed network requests. Mode-home template routes render +consistently across desktop/phone/320; dark theme and reduced-motion spots +clean. + +### New findings (all fixed in this pass) + +1. **Forced-colors blank button labels** (`globals.css` forced-colors block). + Chromium paints a Canvas backplate behind every glyph run in forced-colors + mode, so glyph tokens that resolved to the Canvas/ButtonFace family + (`--command-contrast`, `--primary-contrast`, `--clinical-accent-contrast`, + `--danger-solid-contrast`) rendered solid-button labels as blank boxes — + invisible to axe, which reads CSS rather than painted pixels. SVG strokes + get no backplate, so the dark `--command` fill also swallowed ButtonText + icons. Fix: command controls flatten to the native HCM pairing + (ButtonFace fill / ButtonText glyphs); accent fills keep their system + colors and flip only their glyph tokens to ButtonText. Regression-locked by + a new `ui-accessibility.spec.ts` test asserting the glyph tokens never + resolve to the Canvas color. +2. **Tools quick-action rail title truncation at 1440×1000** + (`applications-launcher-page.tsx`). The desktop 6-up rail left ~85px for + text against ~92px titles ("Ask eviden…", "Safety che…"). Fix: tightened + card metrics (icon column 2.25rem→2rem, gap-3→gap-2, px-3→px-2.5, icon + h-9→h-8); all six titles now render whole, descriptions remain + designed-supplementary ellipsis (full copy lives in the All-tools cards and + aria-labels). Phone icon-grid variant untouched. +3. **Privacy-page microcopy** (`src/app/privacy/page.tsx`). JSX drops a + newline adjacent to a tag, rendering "…patient-record systemand does not + ask…". Fixed with an explicit `{" "}`; locked by a `privacy-ui.test.ts` + assertion. + +Observation, no action: a sub-perceptual glass-header backdrop-gradient ghost +at the content top-left (visible only at 4× zoom) — by-design translucency. diff --git a/src/app/globals.css b/src/app/globals.css index d991a1ba1..47bd5d673 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2336,16 +2336,24 @@ html[data-motion="reduced"] .source-capsule-hit[aria-expanded="true"]:hover .sou --primary: LinkText; --primary-strong: LinkText; --primary-soft: Canvas; - --primary-contrast: Canvas; + --primary-contrast: ButtonText; --surface-chrome: Canvas; - --command: ButtonText; - --command-hover: ButtonText; - --command-contrast: ButtonFace; + /* Chromium paints a Canvas backplate behind every glyph run in + forced-colors mode, so label/glyph tokens must never resolve to the + Canvas/ButtonFace family — Canvas-on-Canvas glyphs vanish and solid + buttons render blank label boxes. SVG icon strokes get no backplate at + all, so the dark command fill also cannot survive: it would swallow + ButtonText icons. Command controls therefore flatten to the native HCM + button pairing (ButtonFace fill, ButtonText glyphs); accent fills keep + their system colors and flip only their glyph tokens. */ + --command: ButtonFace; + --command-hover: ButtonFace; + --command-contrast: ButtonText; --clinical-accent: LinkText; --clinical-accent-hover: LinkText; --clinical-accent-soft: Canvas; --clinical-accent-border: ButtonBorder; - --clinical-accent-contrast: Canvas; + --clinical-accent-contrast: ButtonText; --info: LinkText; --info-soft: Canvas; --success: CanvasText; @@ -2355,7 +2363,7 @@ html[data-motion="reduced"] .source-capsule-hit[aria-expanded="true"]:hover .sou --danger: Mark; --danger-soft: Canvas; --danger-solid: Mark; - --danger-solid-contrast: Canvas; + --danger-solid-contrast: ButtonText; --tone-purple: CanvasText; --tone-indigo: CanvasText; --tone-rose: CanvasText; diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx index a5de27ca2..0375be696 100644 --- a/src/app/privacy/page.tsx +++ b/src/app/privacy/page.tsx @@ -25,9 +25,8 @@ const SECTIONS: Section[] = [ body: ( <> Clinical KB is a knowledge base over clinical reference material. It is{" "} - not a patient-record system - and does not ask for patient data. The main privacy risk is incidental patient information entered into a - free-text question or uploaded document. + not a patient-record system and does not ask for patient data. The main privacy risk is + incidental patient information entered into a free-text question or uploaded document. ), }, diff --git a/src/components/applications-launcher-page.tsx b/src/components/applications-launcher-page.tsx index 718de1325..0e8b46b9b 100644 --- a/src/components/applications-launcher-page.tsx +++ b/src/components/applications-launcher-page.tsx @@ -287,14 +287,14 @@ function QuickActions({ onSelect, mobile }: { onSelect: (id: string) => void; mo focusRing, mobile ? "grid h-14 min-w-0 place-items-center gap-0.5 rounded-lg px-1 py-1.5 text-center" - : "grid min-h-14 grid-cols-[2.25rem_minmax(0,1fr)] items-center gap-3 rounded-lg px-3 py-2.5", + : "grid min-h-14 grid-cols-[2rem_minmax(0,1fr)] items-center gap-2 rounded-lg px-2.5 py-2.5", )} > diff --git a/tests/privacy-ui.test.ts b/tests/privacy-ui.test.ts index 1192f575d..6c3f70392 100644 --- a/tests/privacy-ui.test.ts +++ b/tests/privacy-ui.test.ts @@ -32,6 +32,9 @@ describe("privacy UI", () => { ]) { expect(markup).toContain(heading); } + // JSX drops a newline adjacent to a tag, so the space after the bolded + // phrase must be explicit or this renders as "systemand". + expect(markup).toContain("not a patient-record system and does not ask for patient data"); expect(markup).toContain("Generated answer text is also omitted from durable query logs by default"); expect(markup).toContain("application service in Singapore"); expect(markup).toContain("Railway in Singapore"); diff --git a/tests/ui-accessibility.spec.ts b/tests/ui-accessibility.spec.ts index e18ffd1c1..e4897c067 100644 --- a/tests/ui-accessibility.spec.ts +++ b/tests/ui-accessibility.spec.ts @@ -169,6 +169,48 @@ test.describe("Clinical KB accessibility media smoke", () => { await expectNoPageHorizontalOverflow(page); }); + test("solid-button label tokens stay legible with forced colors", async ({ page }) => { + // Chromium paints a Canvas backplate behind every glyph run in forced-colors + // mode: a label whose color resolves into the Canvas/ButtonFace family + // disappears into its own backplate (axe cannot see this — it reads CSS, + // not painted pixels). Lock the glyph tokens to a non-Canvas resolution. + await page.emulateMedia({ forcedColors: "active" }); + await page.setViewportSize({ width: 1440, height: 1000 }); + await mockMinimalDashboardApi(page); + await gotoApp(page); + await expectDashboardUsable(page); + + const newChat = page.getByRole("button", { name: "New chat" }).first(); + await expect(newChat).toBeVisible(); + const { canvas, buttonLabelColor, tokenColors } = await newChat.evaluate((button) => { + const probe = document.createElement("span"); + document.body.append(probe); + const resolveColor = (value: string) => { + probe.style.color = ""; + probe.style.color = value; + return getComputedStyle(probe).color; + }; + const resolvedCanvas = resolveColor("Canvas"); + const glyphTokens = [ + "--command-contrast", + "--primary-contrast", + "--clinical-accent-contrast", + "--danger-solid-contrast", + ].map((token) => [token, resolveColor(`var(${token})`)] as const); + probe.remove(); + return { + canvas: resolvedCanvas, + buttonLabelColor: getComputedStyle(button).color, + tokenColors: glyphTokens, + }; + }); + + expect(buttonLabelColor).not.toBe(canvas); + for (const [token, color] of tokenColors) { + expect(`${token}: ${color}`).not.toBe(`${token}: ${canvas}`); + } + }); + test("dashboard remains usable at 200 percent zoom", async ({ page }) => { await page.setViewportSize({ width: 1280, height: 800 }); await mockMinimalDashboardApi(page); From 0c407d1c1f866c0436f52e6afd01bc149091283f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Jul 2026 17:53:07 +0000 Subject: [PATCH 2/3] docs(ledger): record plan Phase 5 design-polish review row (PR #896) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EXsJcLrbZUXwnBeG91cVo9 --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index b1d8b0d1e..34585b3be 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -611,3 +611,4 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-18 | codex/main-merge-51278-final-20260718-late | 45fa3c6c7, 14a0a898c | merge integration of 51278a70d onto fresh origin/main | Replayed the requested historical design-audit commit onto fresh `origin/main`. Kept current-main versions for five conflicts, including the regenerated drift manifest and current schema assertions. Fixed the duplicate sitemap-generator declaration, corrected redirect-section coverage, and restored the current `Clinical Guide` UI contract. | `git diff --check` and `npm run sitemap:check` passed. `npm run test` was attempted twice but blocked by the repository-wide heavy-command lock held by a separate Playwright worktree; no provider-backed checks ran. | | 2026-07-18 | origin/main framework and dependency modernization snapshot | 4057677c8b92a5e1d997ec44958764fa91f5d424 | parallel build/infra, backend, and frontend modernization audit | Changes requested. No P0. Confirmed two P1 defects: Supabase SSR 0.12 auth-cookie responses discard mandatory anti-cache headers, and reindex bypasses the server-only-aware TSX runner after mutation-capable setup. Five P2 blockers cover the Webpack-to-Turbopack production cutover, incomplete Railway image-build watch ownership, missing clean `next typegen`, Node 26 types over a Node 24 runtime, and App Router retry actions that reset without re-fetching. P3 removal-readiness debt remains in Zod and Next Image APIs. Manual rewrite zones are auth response ownership, bundler/CSP/artifact consumers, JSZip resource limits, OpenAI request typing, and React Compiler adoption in the stateful dashboard/viewer roots. | Parallel read-only source/config/test audit against the exact snapshot; Node 24.18.0/npm 11.17.0; exact-version Next 16.2.10 bundled upgrade, Turbopack, error-boundary, and codemod guidance; TypeScript 6 backend no-emit analysis and Node import probes via a separately installed exact-version local dependency tree; `git diff --check`. No install, registry outdated/audit, full repo lint/typecheck/test/build/browser/Docker, Supabase/OpenAI, deployment, or hosted CI run; this worktree had no `node_modules`. | | 2026-07-18 | claude/clinical-kb-pwa-review-asi3wb (PR #890, plan Phase 4; single-batch implementation+ledger commit — Phase 3 landed as squash 44a4c511bc1381168474794d6f89273564659ead) | ceabc04d75fd0cea2a120d504418d44edd704bfb | App-wide performance pass: audit-then-fix + budget ratchet (plan Phase 4) | Audit on post-#872 main: enforced gzip budget +3.13% over the 1,363,382-byte baseline; analyzer treemap showed pdfjs-dist (123 KB) and cross-mode-differentials data (121 KB) already correctly lazy, and disproved the suspected forms-catalog dashboard leak (type-only import). Confirmed one real defect: the 143-line `/services` client home page value-imported `defaultServiceSlug` from `@/lib/services`, compiling the ~915 KB services snapshot (~100 KB gzip) into its route chunk. Fixed by computing the slug in the server page (`src/app/services/page.tsx`) and passing it as a prop; the client component builds its task cards from the prop. Measured result: budget swung from +3.13% to −3.97%; baseline ratcheted down 1,363,382 → 1,309,286 bytes gzip so CI locks the win. pdfjs/differentials/#718 paths deliberately untouched per audit rules. Two transient `.next/dev` generated-type corruptions from the long-lived dev server were resolved by stopping the server and setting the generated dir aside (reversible); nothing hand-deleted. | Build + `check:bundle-budget --json` before/after (artifacts in session scratchpad); `check:bundle-budget -- --update` for the ratchet; `npm run test` 2789 passed/1 failed (known container-only `pdf-extraction-budget` artifact, hosted-CI-green through #826/#835/#872); `verify:cheap` green to the same artifact; `verify:ui` 219 passed/2 failed (the two long-established container artifacts; ui-tools spec covering the services surface passed); `verify:pr-local` unit stage identical; typecheck/lint/format clean. Lighthouse not run (dev-server churn; bundle evidence sufficed for this fix set). No provider-backed checks run. | +| 2026-07-18 | claude/clinical-kb-pwa-review-asi3wb (PR #896, plan Phase 5; content commit + this ledger follow-up) | a6c2b4e92374e9002fb00c547eb5677d01ce538c | Design-polish sweep: audit-then-fix (plan Phase 5, final phase) | Audit on post-#890 main: three strict design guards clean; full re-run of the 07-token-adoption-audit grep method shows all July 3 debt resolved (M1–M3 done, L4 reduced to the deliberate theme-aware `ring-white/N dark:ring-white/10` glass idiom, L5/L7 gone; production hex all legitimate print/brand/console/comment classes); 43-capture live sweep across 15 routes × desktop/phone + 320px spots + dark/reduced-motion/forced-colors spots found 0 overflow and 0 console errors. Three defects found and fixed: (1) forced-colors solid-button labels rendered as blank Canvas-on-Canvas backplate boxes (axe-invisible) — command controls flattened to the native HCM ButtonFace/ButtonText pairing and accent glyph tokens flipped to ButtonText inside the existing forced-colors block, regression-locked by a new ui-accessibility test; (2) tools desktop 6-up quick-action rail truncated card titles at 1440×1000 — card metrics tightened, all six titles verified unclipped; (3) privacy page rendered "systemand" from a JSX newline-adjacent-to-tag drop — explicit space, locked by a privacy-ui assertion. Dated July 18 run appended to docs/redesign/07-token-adoption-audit.md (archived design-qa.md not resurrected). | Guards + focused vitest 14/14; `verify:cheap` chain green to the known container-only pdf-extraction-budget artifact (2806/2809); `verify:ui` 220 passed/2 failed (the two long-baselined container artifacts, hosted-CI-green through #826/#835/#872/#890); `test:e2e:accessibility` 8/8 incl. the new forced-colors token test; production build + client-bundle secret scan passed; `check:bundle-budget` within tolerance vs the Phase 4 ratchet (1290.6 vs 1278.6 KiB baseline); `verify:pr-local` runtime/format/lint/typecheck/build/rag-fixtures green with the same sole unit-suite artifact. `verify:release` not run (provider-backed; awaits explicit confirmation). No provider-backed checks run. | From 4b9e5ed9ffbf113aed4fa5673e0464a1e776ffb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 18 Jul 2026 18:01:31 +0000 Subject: [PATCH 3/3] polish(design): pair danger-solid glyphs with MarkText in forced colors Review follow-up: ButtonText is not palette-guaranteed to contrast a Mark fill in arbitrary forced-colors schemes; MarkText is Mark's paired foreground. No visual change in the emulated palette (both resolve black). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EXsJcLrbZUXwnBeG91cVo9 --- src/app/globals.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/globals.css b/src/app/globals.css index a6a936b00..48dc21e36 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2362,7 +2362,9 @@ html[data-motion="reduced"] .source-capsule-hit[aria-expanded="true"]:hover .sou --danger: Mark; --danger-soft: Canvas; --danger-solid: Mark; - --danger-solid-contrast: ButtonText; + /* Mark's palette-paired foreground — ButtonText is not guaranteed to + contrast a Mark fill in arbitrary user schemes. */ + --danger-solid-contrast: MarkText; --tone-purple: CanvasText; --tone-indigo: CanvasText; --tone-rose: CanvasText;