Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -614,3 +614,4 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD
| 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 | PR #871 / codex/design-audit-final-pr-20260718 | bb85b546e + ledger closeout | final current-main design/accessibility audit integration, hosted review resolution, and PR-readiness review | No remaining high-confidence P0-P2 defect after current-main reconciliation, two independent read-only reviews, and remediation of every confirmed review finding. The follow-ups made client demo state fail closed in production for both prototype items and set suggestions, separated local no-auth upload capability from Favourites demo treatment, kept unavailable Favourites controls focusable with accessible reasons, restored visible skip-target focus, limited Favourites selection to the wide layout that exposes its workspace, aligned upload/index semantics with each responsive rendering mode, exposed filtered Tools results as a labelled group, and kept Therapy Compass in an honest loading state until its catalogue resolves. The audit also distinguishes completed browser assertions from an incomplete valid document-source redirect case. External design-target fidelity remains unverified without an approved target. | Canonical local PR verification completed through runtime, changed-file formatting, full ESLint, TypeScript, full Vitest, webpack production build/client-secret scan, and offline RAG fixtures. Focused Therapy, navigation/auth, demo-boundary, and unavailable-control regressions passed after correcting one test-order issue; the final demo/upload boundary selection passed 4/4; scoped zero-warning ESLint, changed-file formatting, and `git diff --check` passed. The full local Chromium sweep completed 236/237 with one hydration-timing failure: the app-mode test clicked before React attached its handler. The existing handler-readiness pattern was applied; focused rerun attempts were then lock-blocked by another registered worktree, so exact-head hosted Chromium remains the required proof. Initial hosted checks, including Production UI, passed before these final follow-ups. No OpenAI, Supabase, production-data, deployment, or live clinical workflow ran. |
| 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. |
66 changes: 66 additions & 0 deletions docs/redesign/07-token-adoption-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
22 changes: 16 additions & 6 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2335,16 +2335,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;
Expand All @@ -2354,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: Canvas;
/* 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;
Expand Down
5 changes: 2 additions & 3 deletions src/app/privacy/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ const SECTIONS: Section[] = [
body: (
<>
Clinical KB is a knowledge base over clinical reference material. It is{" "}
<strong>not a patient-record system</strong>
and does not ask for patient data. The main privacy risk is incidental patient information entered into a
free-text question or uploaded document.
<strong>not a patient-record system</strong> and does not ask for patient data. The main privacy risk is
incidental patient information entered into a free-text question or uploaded document.
</>
),
},
Expand Down
4 changes: 2 additions & 2 deletions src/components/applications-launcher-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)}
>
<span
className={cn(
"grid place-items-center rounded-lg border shadow-[var(--shadow-inset)]",
appIconTone(app),
mobile ? "h-7 w-7" : "h-9 w-9",
mobile ? "h-7 w-7" : "h-8 w-8",
)}
>
<Icon className={mobile ? "h-4 w-4" : "h-5 w-5"} aria-hidden />
Expand Down
3 changes: 3 additions & 0 deletions tests/privacy-ui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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</strong> 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");
Expand Down
42 changes: 42 additions & 0 deletions tests/ui-accessibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,48 @@ test.describe("Clinical KB accessibility coverage", () => {
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);
Expand Down
Loading