From c88ceba1364b682e8cc983ed1130a1e708088966 Mon Sep 17 00:00:00 2001 From: A Charlwood Date: Fri, 13 Feb 2026 17:01:29 +0000 Subject: [PATCH] Update progress: Task 1 completed (design tokens) Also includes manual intervention files: updated CLAUDE.md, IMPLEMENTATION_PLAN.md, and ref files for GP System Dashboard redesign. Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 2 +- Ralph/IMPLEMENTATION_PLAN.md | 10 +- Ralph/progress.txt | 999 ++------------------------ Ralph/refs/ref-01-design-tokens.md | 21 + Ralph/refs/ref-04-dashboard-layout.md | 27 + Ralph/refs/ref-07-interactions.md | 17 +- Ralph/refs/ref-08-polish.md | 10 +- 7 files changed, 126 insertions(+), 960 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f39e0d5..efddce0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,7 +26,7 @@ No test framework is configured. ### Four-Phase UI Flow -`App.tsx` manages a `Phase` state (`'boot'` → `'ecg'` → `'login'` → `'dashboard'`). Each phase renders exclusively: +`App.tsx` manages a `Phase` state (`'boot'` → `'ecg'` → `'login'` → `'pmr'`). Each phase renders exclusively: 1. **BootSequence** — Terminal typing animation (~4s), green-on-black aesthetic. Fira Code font, matrix-green palette. **Locked — do not change.** 2. **ECGAnimation** — Canvas-based heartbeat animation with mask-based letter tracing. Background transitions from black to `#1E293B`. **Locked — do not change.** diff --git a/Ralph/IMPLEMENTATION_PLAN.md b/Ralph/IMPLEMENTATION_PLAN.md index f379feb..5c183f8 100644 --- a/Ralph/IMPLEMENTATION_PLAN.md +++ b/Ralph/IMPLEMENTATION_PLAN.md @@ -2,7 +2,7 @@ ## Project Overview -Replace the "CareerRecord PMR" sidebar-nav + view-switching interface with a tile-based GP System dashboard. Reference design: `References/GPSystemconcept.html`. +Replace the "CareerRecord PMR" sidebar-nav + view-switching interface with a tile-based GP System dashboard called "CVMIS" Reference design: `References/GPSystemconcept.html`. ## Quality Checks @@ -22,10 +22,10 @@ Replace the "CareerRecord PMR" sidebar-nav + view-switching interface with a til #### Task 1: Update design tokens and Tailwind config > Detail: `Ralph/refs/ref-01-design-tokens.md` -- [ ] Update CSS custom properties in `src/index.css` (new palette, shadows, layout vars) -- [ ] Update `tailwind.config.js` (colors, shadows, borders, radius) -- [ ] Keep boot/ECG/login tokens unchanged -- [ ] Run quality checks +- [x] Update CSS custom properties in `src/index.css` (new palette, shadows, layout vars) +- [x] Update `tailwind.config.js` (colors, shadows, borders, radius) +- [x] Keep boot/ECG/login tokens unchanged +- [x] Run quality checks #### Task 2: Create new data files and update types > Detail: `Ralph/refs/ref-02-data-types.md` diff --git a/Ralph/progress.txt b/Ralph/progress.txt index 3ac061d..fafa862 100644 --- a/Ralph/progress.txt +++ b/Ralph/progress.txt @@ -3,944 +3,59 @@ ## Codebase Patterns ### Project Structure -- Components in `src/components/`, views in `src/components/views/` -- Data files in `src/data/` — consultations.ts, medications.ts, problems.ts, investigations.ts, documents.ts, patient.ts +- Components in `src/components/`, tiles in `src/components/tiles/` +- Old views still in `src/components/views/` (to be removed in Task 21) +- Data files in `src/data/` — consultations.ts, medications.ts, problems.ts, investigations.ts, documents.ts, patient.ts + new files: profile.ts, tags.ts, alerts.ts, kpis.ts, skills.ts - Types in `src/types/pmr.ts` (PMR interfaces) and `src/types/index.ts` (Phase type) - Hooks in `src/hooks/` — useScrollCondensation.ts, useBreakpoint.ts -- Contexts in `src/contexts/` — AccessibilityContext.tsx +- Contexts in `src/contexts/` — AccessibilityContext.tsx (has 1 pre-existing ESLint warning — expected) +- Lib in `src/lib/` — search.ts (fuse.js integration) - Path alias: `@/` maps to `./src/` ### Phase Management - App.tsx controls phase: 'boot' -> 'ecg' -> 'login' -> 'pmr' -- BootSequence.tsx handles terminal animation -- ECGAnimation.tsx handles heartbeat + letter tracing + flatline exit -- LoginScreen.tsx bridges to PMRInterface.tsx +- Phase type defined in `src/types/index.ts` as `'boot' | 'ecg' | 'login' | 'pmr'` +- BootSequence.tsx handles terminal animation — LOCKED +- ECGAnimation.tsx handles heartbeat + letter tracing + flatline exit — LOCKED +- LoginScreen.tsx bridges to dashboard (was PMRInterface, now DashboardLayout) -### Data Architecture (CORRECT — do not modify) +### Data Architecture (CORRECT — do not modify existing files) - All data files are populated with accurate CV content from References/CV_v4.md -- 5 consultation entries (roles), 18 medications (skills), 11 problems (achievements), 6 investigations (projects), 5 documents (education) +- 5 consultation entries (roles), 18 medications (skills with prescribingHistory), 11 problems (achievements), 6 investigations (projects), 5 documents (education) - Types are properly defined in pmr.ts — Consultation, Medication, Problem, Investigation, Document, Patient, ViewId - -### Design System Requirements (from ref-design-system.md) -- Light-mode ONLY — no dark mode -- NHS blue: #005EB8 (primary interactive) -- Border radius: 4px for cards/inputs -- Borders: 1px solid #E5E7EB on tables and cards, combined with multi-layered shadows for depth -- Card shadows: 0 1px 2px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.03) -- Table row height: 40px, card padding: 16-24px, main content padding: 24px -- Fonts: [UI font] (Elvaro Grotesque or Blumir from Fonts/ dir), Geist Mono (coded entries, timestamps, data values) -- Base spacing unit: 4px — generous but structured, more whitespace than real clinical systems +- New types needed: Tag, Alert, KPI, SkillMedication (Task 2) ### Known Dependencies - React 18.3.1, TypeScript, Vite - Tailwind CSS for utility classes - Framer Motion 11.15.0 for animations - Lucide React 0.468.0 for icons -- fuse.js will need to be installed for Task 12 +- fuse.js 7.0.0 (already installed) for fuzzy search -### Sidebar Label Convention (IMPORTANT) -- Sidebar uses CV-friendly labels, NOT clinical jargon -- Summary (same), Experience (not Consultations), Skills (not Medications), Achievements (not Problems), Projects (not Investigations), Education (not Documents), Contact (not Referrals) -- The clinical metaphor is in the VIEW LAYOUT, not the navigation labels -- Each view should look like its clinical equivalent but the nav label tells the user what CV section they're looking at +### Typography +- Elvaro Grotesque (`font-ui`) — primary UI font, 7 weights (300-900), loaded from Fonts/ directory +- Blumir (`font-ui-alt`) — alternative, variable font (100-700) +- Geist Mono (`font-geist`) — timestamps, data values, coded entries +- Fira Code (`font-mono`) — boot/ECG terminal only +- Do NOT use Inter, Roboto, DM Sans, or system defaults +- DM Sans in the concept HTML is a PLACEHOLDER — use Elvaro Grotesque +- Font mapping was corrected in Task 1: Elvaro = font-ui (primary), Blumir = font-ui-alt (alternative) -### Visual Review (Playwright MCP) +### Design Tokens +- Dashboard background: use `--bg-dashboard` (#F0F5F4), NOT `--bg` (#FFFFFF which is for boot/ECG) +- Three-tier shadows: `--shadow-sm` (resting), `--shadow-md` (hover/interactive), `--shadow-lg` (overlays) +- Border tiers: `--border` (#D4E0DE, structural), `--border-light` (#E4EDEB, cards) +- Accent: `--accent` (#0D6E6E teal), `--accent-hover` (#0A8080), `--accent-light` (rgba 0.08), `--accent-border` (rgba 0.18) +- Status colors each have base + light + border variants (success, amber, alert, purple) +- Tailwind: `pmr-*` prefix for all dashboard colors (e.g., `bg-pmr-bg`, `text-pmr-accent`, `border-pmr-border-light`) +- Tailwind shadows: `shadow-pmr-sm`, `shadow-pmr-md`, `shadow-pmr-lg` +- Tailwind radius: `rounded-card` (8px), `rounded-card-sm` (6px), `rounded-login` (12px) + +### Visual Review - Dev server runs on `http://localhost:5173` throughout the loop -- Use Playwright MCP tools (`mcp__playwright__browser_navigate`, `mcp__playwright__browser_take_screenshot`, `mcp__playwright__browser_snapshot`) to verify visual output -- App has boot→ECG→login→PMR sequence (~15s on first load). Use `mcp__playwright__browser_wait_for` with `time: 15` before screenshotting. -- Once in PMR phase, navigate views via hash routes: `#summary`, `#experience`, `#skills`, `#achievements`, `#projects`, `#education`, `#contact` +- App has boot→ECG→login→dashboard sequence (~15s on first load) - If browser tools fail, skip visual review and note in iteration log — don't block progress -### Critical Styling Notes -- Design direction is **Clinical Luxury** — clinical structure, premium execution -- Premium UI font loaded from Fonts/ directory (Elvaro Grotesque or Blumir, NOT Inter/Roboto) -- Geist Mono for coded entries and timestamps (NOT Fira Code) -- Multi-layered shadows on cards — NOT flat/borderless -- Clinical alert uses spring animation (Framer Motion type: "spring"), not ease-out -- View switching INSTANT — no crossfade, no slide between views -- Login typing: 80ms/char username, 60ms/dot password. Button is USER-INTERACTIVE (not auto-triggered) -- Consultation expand/collapse: height animation ONLY, no opacity fade on content - -## Iteration Log - -### Iteration 1 — Task 1: Design system foundation and font setup -**Completed:** Task 1 -**Changes made:** -- Added Geist Mono font to Google Fonts import in index.html (replacing reliance on Fira Code for PMR components) -- Extended Tailwind config PMR color tokens: added card, text-primary, text-secondary, text-on-dark variants, border colors, selected-row, alert colors -- Fixed borderRadius.card from 16px to 4px (clinical system requirement) -- Added borderRadius.login: 12px (exception for login card per spec) -- Added boxShadow.pmr: minimal clinical shadow -- Added PMR-specific CSS custom properties in index.css (--pmr-* variables) -- Added utility classes: .pmr-theme, .font-inter, .font-geist-mono - -**Codebase patterns discovered:** -- The project uses both legacy design tokens (for boot/ECG phases) AND new PMR tokens (for clinical interface) — both need to coexist -- Geist Mono is the correct font for coded entries, timestamps, and clinical codes in PMR (NOT Fira Code) -- Border radius convention: 4px default for clinical components, 12px for login card only, 16px for legacy components -- CSS custom properties namespaced with --pmr- prefix for clarity - -**Quality checks:** All passed (typecheck, lint, build) -**Visual review:** N/A (configuration task, no visual component) - -**Issues encountered:** None - -**Design decisions:** -- Kept legacy tokens in place to avoid breaking boot/ECG components -- Used --pmr- namespace for all PMR tokens to distinguish from legacy design system -- Extended Tailwind colors rather than replacing them — allows both themes to work simultaneously - -### IMPORTANT — Design Guidance is Pre-Baked -Do NOT invoke the `/frontend-design` skill at runtime — it was pre-run and the output is embedded in each ref file under "Design Guidance (from /frontend-design)". Previous iterations STALLED because the skill output consumed the entire context window. The guidance is now in the ref files — just read and implement. - -### ECG Reference Implementation -`ECGCombined.tsx` in the project root is a Remotion version of the ECG animation with a superior mask-based text reveal technique. Task 1b references this for the canvas implementation. - -### Iteration 2 — Task 1b: Rebuild boot sequence and ECG animation -**Completed:** Task 1b -**Changes made:** -- **BootSequence.tsx**: Completely refactored from hardcoded HTML strings to config-driven architecture - - Created type-safe `BootConfig`, `BootLine`, `BootLineType` interfaces - - Individual line components: `BootLineHeader`, `BootLineStatus`, `BootLineSeparator`, `BootLineField`, `BootLineModule`, `BootLineReady` - - Added CRT scanlines overlay during boot phase - - Cursor now captures its screen position via ref and passes to parent via `onCursorPositionReady` callback - - Cursor morph animation: block cursor scales down to 0 width over 300ms before ECG starts - - Reduced motion support: instant boot completion, skips to ECG immediately - -- **ECGAnimation.tsx**: Rebuilt with mask-based text reveal technique from ECGCombined.tsx - - Added `startPosition` prop to receive cursor position from BootSequence - - ECG trace now starts from cursor position (with `startOffsetX`) instead of x=0 - - Implemented offscreen canvas pre-rendering for text stroke - - Mask-based text reveal: clipping region follows trace head, revealing pre-rendered text - - Added connector lines between letters at baseline using `CONNECTOR_PROFILES` - - Letter profiles define connector insets for natural-looking baseline connections - - Multi-layer neon glow: outer (6px, 25% opacity), inner (2px solid) - - Flatline draw phase extends to right edge after text completion - - Background transitions from black to `#1E293B` (login background) - - Reduced motion support: instant transition to PMR phase - -- **App.tsx**: Updated to pass cursor position between BootSequence and ECGAnimation - - Added `cursorPosition` state - - `handleCursorPositionReady` captures position from BootSequence - - Passed to ECGAnimation as `startPosition` prop - -**Codebase patterns discovered:** -- Canvas animation performance: pre-render text to offscreen canvas, then drawImage through clip region -- Cursor-to-dot transition requires DOM ref position capture, not just CSS animation -- World-space coordinates (headWX) vs screen-space coordinates (headSX) separation is critical -- Viewport scrolling logic: offset calculated as `headWX - headSX` keeps trace visible -- Connector profiles per character (C, O, D, L, E have special insets) make letter connections look natural -- Background color transition handled via CSS transition on container, not canvas fill - -**Quality checks:** All passed (typecheck, lint, build) -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 388KB bundle - -**Visual review:** N/A (animation component — visual verification would require browser screenshot) - -**Issues encountered:** None - -**Design decisions:** -- Kept Fira Code for terminal/boot phase (it's the authentic clinical terminal aesthetic) -- Used ECGCombined.tsx's mask technique but adapted for canvas API (not SVG like the Remotion version) -- Beat amplitudes: 0.3 → 0.55 → 0.85 → 1.0 (same as original implementation) -- Letter spacing: LETTER_W 72px, LETTER_G 10px, SPACE_W 30px (matches original, tighter than ECGCombined) -- Morph animation uses Framer Motion scaleX/width/opacity for smooth cursor-to-dot transition - -**Next task:** Task 2 — Set up premium font - -## Manual Intervention — 2026-02-12 -### Reason: Design direction changed from "Clinical Utilitarian" to "Clinical Luxury" -### Changes made: -- Rewrote IMPLEMENTATION_PLAN.md — leaner format, tasks point to ref files for detail -- Rewrote guardrails.md — updated shadow rules, font rules, login pacing, luxury direction -- Updated all Ralph/refs/*.md files to align with Clinical Luxury direction -- Updated CLAUDE.md with new login typing spec (80ms/char, user-interactive button) -- Updated ref-design-system.md login typing speed -### Tasks reset: Task 2 (LoginScreen — needs new typing speed + interactive button + premium font) -### Tasks added: New Task 2 (font setup) inserted before LoginScreen rebuild (now Task 3) -### Context for next iteration: -- The design direction is "Clinical Luxury" — clinical STRUCTURE, premium EXECUTION -- All ref files now say "Clinical Luxury" not "Clinical Utilitarian" or "faithful reproduction" -- Cards get multi-layered shadows (not flat/borderless) -- Premium font from Fonts/ directory replaces Inter (see CLAUDE.md Typography section) -- Login screen typing is slower (80ms/char, 60ms/dot) and the button is USER-CLICKED -- Sidebar labels are CV-friendly (Experience, Skills, etc.) — clinical metaphor is in the LAYOUT -### New guardrails added: -- Shadow guardrail updated: multi-layered shadows required (was: "no shadows") -- Font guardrail added: use [UI font] from Fonts/, not Inter/Roboto -- Login guardrail added: 80ms/char typing, user-interactive button - -### Iteration 3 — Task 2: Set up premium font and update Tailwind config -**Completed:** Task 2 -**Changes made:** -- Added @font-face declarations in src/index.css for both premium font candidates: - - Elvaro Grotesque: 7 weights (Light 300 → Black 900) loaded from WOFF2/WOFF files in Fonts/ directory - - Blumir: Variable font (100-700 weight range) loaded from WOFF2/WOFF files -- Updated CSS variables: --font-ui (Elvaro Grotesque), --font-ui-alt (Blumir) -- Removed --font-inter, replaced with --font-ui in CSS variables -- Updated Tailwind config fontFamily: - - Added font-ui: ['Elvaro Grotesque', 'system-ui', 'sans-serif'] - - Added font-ui-alt: ['Blumir', 'system-ui', 'sans-serif'] - - Removed font-inter references - - Kept font-geist for monospace data (Geist Mono) - - Kept font-mono for boot/ECG phases (Fira Code) -- Enhanced Tailwind boxShadow tokens for Clinical Luxury: - - pmr: '0 1px 2px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.03)' (multi-layered card shadow) - - pmr-hover: '0 2px 4px rgba(0,0,0,0.06), 0 8px 16px rgba(0,0,0,0.04)' (hover lift) - - pmr-banner: '0 2px 8px rgba(0,0,0,0.12)' (patient banner drop shadow) -- Updated utility class .pmr-theme to use var(--font-ui) instead of var(--font-inter) -- Added .font-ui and .font-ui-alt utility classes -- Fixed ESLint errors in ECGAnimation.tsx (viewOff and headSX should be const, not let) - -**Codebase patterns discovered:** -- Font loading strategy: Load both candidate fonts now, switch between them by changing CSS variable only -- Elvaro Grotesque chosen as primary (institutional credibility, slightly condensed, data-dense UI) -- Blumir available as alternative (more refined/luxurious) via font-ui-alt -- Font files are correctly bundled by Vite (visible in build output: 14 Elvaro files + 2 Blumir files) -- Shadow system: pmr (default card), pmr-hover (interactive lift), pmr-banner (separation) — layered depth -- font-display: swap ensures text remains visible during font load - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 391KB bundle (includes all font files) - -**Visual review:** N/A (configuration task, no visual component — fonts will be visible in Task 3 LoginScreen onwards) - -**Issues encountered:** None - -**Design decisions:** -- Started with Elvaro Grotesque as primary (per task instructions: "start with Elvaro, can be swapped later") -- Loaded all 7 weights for Elvaro (300/400/500/600/700/800/900) for full weight hierarchy control -- Loaded Blumir as variable font (single file supports 100-700 range) for efficiency -- Used font-display: swap for optimal loading behavior (text visible immediately, swaps to custom font when loaded) -- Enhanced shadow tokens in Tailwind config (was only single pmr shadow, now includes hover and banner variants) - -**Next task:** Task 3 — Rebuild LoginScreen with new typing speed, user-interactive button, and premium font - -### Iteration 4 — Task 3: Rebuild LoginScreen -**Completed:** Task 3 -**Changes made:** -- Rebuilt LoginScreen.tsx with all spec requirements: - - Typing speed changed from 30ms/char → 80ms/char for username, 20ms/dot → 60ms/dot for password - - Pause between username and password changed from 150ms → 300ms - - Start delay changed from 200ms → 400ms (more breathing room for card entrance) - - Login button is now user-interactive — disabled/dimmed (opacity 0.6) while typing, fully interactive after typing completes - - Added `typingComplete` state to track when typing is done - - Added `buttonHovered` state for hover visual feedback (#004D9F on hover) - - Button `onClick` handler only fires when `typingComplete` is true - - Reduced motion: typing completes instantly, button immediately available for user click (NOT auto-triggered) - - `activeField` state expanded to include `'done'` value for post-typing state -- Font references updated from `'Inter'` → `var(--font-ui)` (Elvaro Grotesque) throughout -- Card shadow upgraded from single-layer `0 1px 2px rgba(0,0,0,0.03)` → multi-layered `0 1px 2px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.03)` per design system -- Timer cleanup improved: all `setTimeout` calls tracked via `timeoutRefs` ref array and cleared on unmount -- Fixed ESLint warning about `timeoutRefs.current` in cleanup by capturing to local variable - -**Codebase patterns discovered:** -- `addTimeout` helper pattern: wraps `setTimeout` and tracks IDs in a ref array for cleanup — useful for components with multiple sequential timeouts -- Button hover state should use a slightly darker shade than the base (#004D9F between #005EB8 and #004494 pressed) -- `var(--font-ui)` is the correct way to reference the premium font in inline styles (not the font name directly) - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 392KB bundle - -**Visual review:** Skipped — browser tools (Claude in Chrome) not available in this environment - -**Issues encountered:** None - -**Design decisions:** -- Kept the Geist Mono + Fira Code monospace fallback chain for input fields (authentic clinical terminal feel) -- Button hover color (#004D9F) is a midpoint between NHS blue (#005EB8) and pressed (#004494) — subtle but noticeable -- 400ms start delay gives the card entrance animation time to complete before typing begins -- Button uses `disabled` attribute plus visual opacity change for clear affordance - -**Next task:** Task 4 — Rebuild PatientBanner - -## Iteration 4 (Task 4: Rebuild PatientBanner) - -**Task completed:** Task 4 — Rebuild PatientBanner with premium fonts, tooltip, animations - -**Changes made:** -- Rebuilt `src/components/PatientBanner.tsx`: - - Replaced all `font-inter` references with `font-ui` (Elvaro Grotesque) - - Added `NHSNumberWithTooltip` component — custom styled tooltip with Framer Motion, 300ms hover delay, arrow indicator (replaces native `title` attribute) - - Added `AnimatePresence` for smooth crossfade between full/condensed banner states - - Mobile overflow menu now uses `AnimatePresence` for animated enter/exit - - Replaced fixed backdrop click handler with `useRef` + `useCallback` + document event listener pattern (click-outside) - - Mobile banner now uses `patient.nhsNumber` and `patient.status` data instead of hardcoded values - - DOB value rendered in `font-geist` (monospace) for clinical data texture - - Badge changed from `rounded-sm` to `rounded-full` (pill shape) - - Added `shadow-pmr-banner` drop shadow to header - - Added focus ring styles on action buttons (`focus:ring-2 focus:ring-pmr-nhsblue/40`) - - `prefers-reduced-motion` support for banner crossfade -- Added `SkipButton` component to `src/App.tsx` — appears after 1.5s during boot/ECG phases, skips to login - -**Quality checks:** All passed (typecheck, lint, build — 394.81 KB bundle) - -**Visual review:** Completed via Playwright MCP. Banner renders correctly with premium font, NHS blue action buttons, shadow, monospace NHS number, status dot with text label. - -**Known issue (pre-existing):** Banner sentinel element has `absolute top-0` positioning inside a non-positioned parent, causing the IntersectionObserver to always report "not intersecting" — banner shows condensed state even at scroll position 0. The full 3-row banner (with DOB, address, phone, email) never displays. This is NOT a regression from Task 4 — the sentinel placement was unchanged. Should be addressed in a future task. - -**Codebase patterns discovered:** -- `AnimatePresence mode="wait"` is the right pattern for crossfading between two states (full/condensed banner) -- Custom tooltip with Framer Motion + `onMouseEnter`/`onMouseLeave` with 300ms delay is more styleable than native `title` -- Click-outside pattern: `useRef` on container + `useCallback` for handler + `useEffect` to add/remove document listener - -## Manual Intervention — 2025-02-12 -### Reason: Replaced Claude in Chrome browser integration with Playwright MCP for visual validation -### Changes made: -- `RALPH_PROMPT.md`: Updated visual review step (step 7) to use Playwright MCP tools (`mcp__playwright__browser_navigate`, `mcp__playwright__browser_take_screenshot`, `mcp__playwright__browser_snapshot`, `mcp__playwright__browser_wait_for`) instead of Chrome extension tools (`tabs_context_mcp`, `computer` screenshot) -- `guardrails.md`: Updated visual review guardrail to reference Playwright MCP tools -- `progress.txt`: Updated "Visual Review" codebase pattern section with Playwright MCP tool names -- `ralph.ps1`: Updated comments referencing Chrome to reference Playwright MCP -### Tasks reset: None -### Tasks added: None -### Context for next iteration: -- Visual review is now done via **Playwright MCP** tools, NOT Claude in Chrome -- Key tools: `mcp__playwright__browser_navigate` (go to URL), `mcp__playwright__browser_take_screenshot` (visual capture), `mcp__playwright__browser_snapshot` (accessibility tree), `mcp__playwright__browser_wait_for` (wait for time/text) -- The dev server still runs on `http://localhost:5173` — workflow is the same, just different tool names -- Previous iterations skipped visual review because Chrome tools weren't available — Playwright MCP should now work -### New guardrails added: None - -### Iteration 5 — Task 4b: Fix PatientBanner scroll condensation -**Completed:** Task 4b -**Changes made:** -- **Root cause identified:** The sentinel element (`absolute top-0 h-0`) was positioned at the viewport top inside a non-positioned parent. The IntersectionObserver with `-100px` rootMargin immediately reported "not intersecting", so the banner was permanently stuck in condensed state. -- **PMRInterface.tsx:** Restructured layout from document-scroll (`min-h-screen`) to flex container (`flex h-screen overflow-hidden`). Sidebar and content column are siblings. Content column is `flex-1 flex flex-col min-w-0` with banner (flex-shrink-0) above scrollable main (`overflow-y-auto`). -- **PatientBanner.tsx:** Now accepts `isCondensed` prop from parent instead of managing its own scroll detection. Removed sentinel element, removed `useScrollCondensation` import, removed `sticky top-0`. Banner is positioned above the scroll container, so it stays fixed naturally. -- **ClinicalSidebar.tsx:** Changed `h-screen sticky top-0` to `h-full` — parent flex container handles sizing. -- **useScrollCondensation.ts:** Replaced IntersectionObserver with scroll event listener. Accepts `scrollContainer` element directly (not a ref). Uses callback ref pattern in PMRInterface to handle Framer Motion mounting timing. - -**Codebase patterns discovered:** -- **Callback ref pattern for Framer Motion:** `motion.main` elements may not be in the DOM when `useEffect` first runs. Using `useState` + callback ref (`setScrollContainer` via `useCallback`) triggers a re-render when the element mounts, ensuring the scroll listener attaches correctly. -- **Flex h-screen overflow-hidden layout:** The recommended clinical system layout: sidebar + content column in a viewport-height flex container. Content column has banner (flex-shrink-0) + scrollable main (flex-1 overflow-y-auto). No sticky positioning needed — elements above the scroll container stay fixed. -- **Scroll event vs IntersectionObserver:** For scroll-position-based condensation in a contained scroll area, a simple scroll event listener is more reliable than IntersectionObserver with rootMargin tricks. - -**Quality checks:** All passed (typecheck, lint, build — 394.61 KB bundle) - -**Visual review:** Completed via Playwright MCP at 1280x800. -- Full banner (80px, 3 rows) displays correctly on page load at scrollTop=0 -- Condensed banner (48px, single row) activates after scrolling 100px+ -- Banner returns to full state when scrolling back to top -- Layout: sidebar fixed, banner fixed, only main content scrolls - -**Issues encountered:** -- First attempt placed sentinel in `
` but kept IntersectionObserver with default root (viewport) — failed because `overflow-y-auto` on main creates a separate scroll context -- Second attempt used IntersectionObserver with `root: scrollContainerRef.current` — failed due to timing: Framer Motion hadn't mounted the element when the effect ran, so `ref.current` was null -- Final solution: replaced IntersectionObserver with scroll event listener + callback ref pattern for reliable element access - -**Design decisions:** -- Chose scroll event listener over IntersectionObserver for simplicity and reliability -- Used `{ passive: true }` on scroll listener for performance -- Removed min-height calculations from main (`min-h-[calc(100vh-48px)]` etc.) — flex-1 handles sizing naturally - -**Next task:** Task 5 — Rebuild ClinicalSidebar - -### Iteration 6 — Task 5: Rebuild ClinicalSidebar -**Completed:** Task 5 -**Changes made:** -- **ClinicalSidebar.tsx**: Rebuilt with all ref spec requirements: - - Replaced clinical jargon labels with CV-friendly terms: Experience (not Consultations), Skills (not Medications), Achievements (not Problems), Projects (not Investigations), Education (not Documents), Contact (not Referrals) - - Replaced all `font-inter` references with `font-ui` (Elvaro Grotesque) - - Fixed Tailwind opacity syntax: `bg-white/12` → `bg-white/[0.12]`, `bg-white/8` → `bg-white/[0.08]`, `bg-white/5` → `bg-white/[0.05]`, `bg-white/10` → `bg-white/[0.10]` - - Added right edge border (`border-r border-[#334155]`) per design system (sidebar depth) - - Added `focus-visible:ring-2 focus-visible:ring-pmr-nhsblue/40 focus-visible:ring-inset` on all nav buttons - - Set explicit `h-[44px]` and `text-[14px]` per spec (was `h-11` which is equivalent, but explicit is clearer) - - Active state: `font-semibold`, inactive state: `font-medium` per spec - - Added `border-l-[3px] border-transparent` on inactive items to prevent layout shift when switching to active - - Icon container uses `w-[18px] h-[18px] flex items-center justify-center` for consistent alignment - - Footer text color changed to `text-[#64748B]` per spec - - Tablet tooltip uses `font-ui` with `shadow-lg` and `pointer-events-none` -- **MobileBottomNav.tsx**: Updated all labels to CV-friendly terms and `font-inter` → `font-ui` -- **PMRInterface.tsx**: Updated `viewLabels` record from clinical names to CV-friendly names for screen reader consistency - -**Codebase patterns discovered:** -- Tailwind arbitrary opacity syntax: `bg-white/[0.12]` not `bg-white/12` (the latter works in newer Tailwind but the bracket syntax is more explicit and universally supported) -- `border-l-[3px] border-transparent` on inactive items prevents layout shift when active state adds `border-pmr-nhsblue` — the 3px border is always present, only color changes -- Navigation labels MUST be consistent across ClinicalSidebar, MobileBottomNav, and PMRInterface viewLabels — all three need updating when label convention changes - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 395.07 KB bundle - -**Visual review:** Completed via Playwright MCP at default viewport -- Sidebar renders with correct CV-friendly labels: Summary, Experience, Skills, Achievements, Projects, Education, Contact -- 220px width, dark #1E293B background with right edge border visible -- Header branding "CareerRecord PMR / v1.0.0" in premium font at 50% opacity -- Search input with magnifying glass icon, properly styled -- Separator line between Summary and Experience — correct position -- Active state (tested on Summary and Experience): white text, NHS blue left border, bg-white/[0.12] highlight, font-semibold -- Default state: white text at 70% opacity, transparent background -- Footer: "Session: A.CHARLWOOD / Logged in: [time]" in #64748B -- View switching instant — no animation between views -- URL hash routing works (#summary, #consultations) - -**Issues encountered:** None - -**Design decisions:** -- Used `border-r border-[#334155]` instead of a shadow for the sidebar right edge — cleaner, more clinical -- Kept the existing search functionality (local filter) as-is — Task 13 will replace it with fuse.js -- Used `pointer-events-none` on tablet tooltips to prevent tooltip interfering with clicks - -**Next task:** Task 6 — Rebuild PMRInterface layout + Breadcrumb - -### Iteration 7 — Task 6: Rebuild PMRInterface layout + Breadcrumb -**Completed:** Task 6 -**Changes made:** -- **Breadcrumb.tsx**: Created new component with Patient Record > [View] > [Expanded Item] navigation pattern - - Accepts `currentView`, `expandedItem`, `onNavigateToView`, `onCollapseItem` props - - Uses `viewLabels` record mapping ViewId to CV-friendly names (Summary, Experience, Skills, etc.) - - "Patient Record" root is clickable, navigates to summary view - - Current view is clickable when an item is expanded, collapses the expanded item - - Expanded item name (if present) appears as third breadcrumb segment - - Styling: 13px font-ui, gray-400 text for clickable items, gray-600 for current location - - ChevronRight icons (14px, gray-300) as separators - - Hover state: text-pmr-nhsblue on clickable segments -- **PMRInterface.tsx**: Integrated Breadcrumb component and updated font references - - Added Breadcrumb import - - Integrated Breadcrumb between screen reader heading and content (desktop/tablet only, not mobile) - - Breadcrumb receives `activeView`, `expandedItemId` from AccessibilityContext, navigation callbacks - - Replaced all `font-inter` references with `font-ui` (4 locations: default view placeholder, mobile search input, mobile back button) - - Added `shadow-pmr` to default view placeholder card for visual consistency - - Layout unchanged from Task 4b fix (flex h-screen overflow-hidden pattern already correct) - -**Codebase patterns discovered:** -- Breadcrumb navigation pattern: root (Patient Record) → section (view label) → detail (expanded item) -- Breadcrumb should be hidden on mobile (mobile has "Back to Summary" button instead) -- `expandedItemId` from AccessibilityContext is a string (item name), needs to be wrapped in object `{ name, type }` for Breadcrumb -- ViewId mapping must be consistent across ClinicalSidebar, MobileBottomNav, PMRInterface viewLabels, and Breadcrumb viewLabels - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 396.39 KB bundle - -**Visual review:** Completed via Playwright MCP at default viewport -- Breadcrumb renders correctly on Summary view: "Patient Record > Summary" -- Breadcrumb updates on navigation to Experience: "Patient Record > Experience" -- Styling correct: 13px font-ui (Elvaro Grotesque), gray-400 clickable text, gray-300 chevrons -- Positioned correctly above view content with mb-6 spacing -- Layout verified: fixed 220px sidebar, sticky banner, scrollable content area -- View switching instant — no animation between Summary → Experience -- Interface materialization animations work (banner → sidebar → content stagger on initial PMR load) - -**Issues encountered:** None - -**Design decisions:** -- Breadcrumb shows on desktop/tablet only — mobile uses "Back to Summary" button instead (simpler UX on small screens) -- "Patient Record" root always navigates to Summary (the logical "home" view) -- When an item is expanded, clicking the current view name collapses it (returns to list view) -- Used `onNavigateToView` callback pattern for breadcrumb navigation (consistent with existing PMRInterface pattern) - -**Next task:** Task 7 — Rebuild SummaryView + Clinical Alert - -### Iteration 8 — Task 7: Rebuild SummaryView + Clinical Alert -**Completed:** Task 7 -**Changes made:** -- **SummaryView.tsx**: Complete rebuild from ref-summary-alert.md spec: - - **ClinicalAlert**: Replaced CSS transition-based animation with Framer Motion spring animation - - State machine: `'visible' | 'acknowledging' | 'dismissed'` (was: 3 separate boolean states) - - Entrance: `type: 'spring', stiffness: 300, damping: 25` — creates subtle overshoot effect - - Dismiss sequence: icon crossfade (AlertTriangle → CheckCircle, 200ms) → hold beat (200ms) → height collapse (200ms ease-out) - - `AnimatePresence` wraps alert for exit animation (height → 0, opacity → 0) - - Button disables during acknowledging state, text changes to "Acknowledged" - - `prefers-reduced-motion`: instant appear/dismiss, no animation - - **DemographicsCard**: Full-width (`lg:col-span-2`), 2-column key-value layout - - Labels: `font-ui font-medium text-[13px] text-gray-500`, right-aligned, min-width 100px - - Values: `font-ui text-sm text-gray-900` (or `font-geist` for coded data like DOB, registration number) - - Proper spacing: `gap-x-12 gap-y-2` between columns and rows - - **ActiveProblemsCard**: Traffic light dots now include text labels (was: dot-only, guardrail violation) - - Green dot + "Active" text, amber dot + "In Progress" text - - Hover state: `bg-[#EFF6FF]` blue tint (was: `bg-gray-50`) - - **QuickMedsCard**: Proper semantic `` with hover states - - Row height: 40px, alternating `#FFFFFF` / `#F9FAFB` backgrounds - - Hover: `bg-[#EFF6FF]` on rows - - Status dots with text labels in each cell - - **LastConsultationCard**: Full-width, proper typography hierarchy - - Date: `font-geist text-[12px]` (monospace), separator: `text-gray-300` - - Role title: `font-ui font-semibold text-[15px]` - - History: `leading-relaxed line-clamp-3` - - **All cards**: `shadow-pmr` (multi-layered), `border border-[#E5E7EB]`, `rounded` (4px) - - **All fonts**: `font-inter` → `font-ui` throughout (Elvaro Grotesque) - - **CardHeader**: Extracted reusable component — `bg-[#F9FAFB]`, `border-b border-[#E5E7EB]`, uppercase title - - **Grid layout**: `grid grid-cols-1 lg:grid-cols-2 gap-6` — demographics + last consultation span full, problems + meds side-by-side - - **Types**: Props now use proper imported types (`Problem[]`, `Medication[]`, `Consultation`) instead of `typeof` references - -**Codebase patterns discovered:** -- Framer Motion `AnimatePresence` + `motion.div` with `exit` prop is the right pattern for elements that animate out (alert collapse) -- Alert state machine with 3 states is cleaner than 3 separate boolean states — eliminates impossible state combinations -- `CardHeader` component is reusable across all summary cards and potentially other views -- `TrafficLight` component with mandatory text labels should be reused wherever status dots appear -- Hover color `#EFF6FF` (blue tint) is more refined than `gray-50` for interactive rows in a clinical context - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 396.05 KB bundle - -**Visual review:** Completed via Playwright MCP at default viewport -- Clinical Alert: Amber banner with spring entrance, icon visible, Acknowledge button styled correctly -- Alert dismiss: Clicked Acknowledge → icon crossfade → hold → collapse. Content slides up smoothly -- Demographics: Full-width card, 2-column layout, proper label-value alignment -- Active Problems: 3 items with green/amber dots AND text labels (Active, In Progress) -- Quick Medications: 5-row table with alternating backgrounds, status column with dots + text -- Last Consultation: Full-width, Geist Mono date, NHS blue org name, role title, truncated history -- All cards have visible multi-layered shadows and #E5E7EB borders -- Grid layout correct: demographics full-width → problems + meds side-by-side → last consultation full-width - -**Issues encountered:** None - -**Design decisions:** -- Extracted `CardHeader` as shared component for consistent card headers across all summary cards -- Alert dismiss sequence uses `setTimeout(400)` for the acknowledging→dismissed transition (200ms icon crossfade + 200ms hold) -- Used `AnimatePresence mode="wait"` for icon crossfade inside alert (not just opacity toggle) -- Button text changes from "Acknowledge" to "Acknowledged" during dismissal for clear user feedback -- Demographics card uses `gap-x-12` (48px) between columns for generous spacing per Clinical Luxury direction - -**Next task:** Task 8 — Rebuild ConsultationsView (Experience view) - -### Iteration 9 — Task 8: Rebuild ConsultationsView (Experience view) -**Completed:** Task 8 -**Changes made:** -- **ConsultationsView.tsx**: Complete rebuild from ref-consultations.md spec: - - **Framer Motion expand/collapse**: Replaced custom CSS height transition with `AnimatePresence` + `motion.div` for proper height-only animation (200ms ease-out). No opacity fade on content (guardrail compliance). - - **Chevron rotation**: Now uses `motion.div` with `animate={{ rotate: isExpanded ? 180 : 0 }}` instead of CSS class toggle. - - **Font updates**: All `font-inter` references replaced with `font-ui` (Elvaro Grotesque). Section headers, body text, labels all use `font-ui`. - - **Font sizes per spec**: Dates `text-[13px]`, organization `text-[13px]`, role title `text-[15px]`, body/bullets `text-[13px]`, section headers `text-[12px]`, coded entries `text-[12px]`. - - **Coded entries**: Full line in `font-geist` (Geist Mono) — `[CODE] Description` on a single div, not split into separate spans. - - **Section headers**: `font-ui font-semibold text-[12px] uppercase tracking-[0.05em] text-gray-400` — matches clinical system divider style. - - **Hover state**: Changed from `bg-gray-50` to `bg-[#EFF6FF]` (blue tint) for interactive rows. - - **Card styling**: Added `shadow-pmr` (multi-layered shadow), `border border-[#E5E7EB]`, with `overflow-hidden`. - - **3px left border**: Color-coded by employer via inline style (NHS blue `#005EB8` or Tesco teal `#00897B`). - - **Accessibility**: Added `focus-visible:ring-2 focus-visible:ring-pmr-nhsblue/40 focus-visible:ring-inset` on entry buttons, `aria-expanded` attribute, descriptive `aria-label` with role + org + date. - - **Status dot**: Green for current, gray for historical, with `aria-label`. - - **Single-expand accordion**: Only one entry expanded at a time. - - **Reduced motion**: All animations skip to final state (Framer Motion `duration: 0`). - - **Removed unused imports**: Cleaned up `useEffect`, `useRef` — no longer needed with Framer Motion approach. - -**Codebase patterns discovered:** -- `AnimatePresence initial={false}` + `motion.div` with `initial/animate/exit` on `height` is the cleanest pattern for height-only expand/collapse — cleaner than the previous custom CSS transition approach with `useRef` + `useEffect` + `setTimeout`. -- Coded entries render cleaner as a single `font-geist` div with the full `[CODE] Description` text, rather than splitting code and description into separate styled spans. -- Framer Motion chevron rotation via `motion.div` is simpler and more consistent than CSS class toggle with `transition-transform`. - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 395.72 KB bundle - -**Visual review:** Completed via Playwright MCP at default viewport -- Collapsed state: All 5 entries visible with date (Geist Mono), organization (employer color), role title (semibold), key coded entry -- 3px left border visible: NHS blue for first 3 entries, teal for Tesco entries -- Green status dot on Deputy Head (current role), gray on all others -- Expanded first entry: H/E/P sections with proper section header styling, bulleted lists, coded entries in Geist Mono -- Accordion behavior: Expanding second entry collapsed first (re-shows Key coded entry) -- Chevron rotated 180° when expanded -- Multi-layered shadows visible on cards -- No opacity fade during expand/collapse — height-only animation confirmed - -**Issues encountered:** None - -**Design decisions:** -- Used `AnimatePresence initial={false}` to prevent animation on first render (entries should appear without animation) -- Kept coded entries as simple single-line divs in Geist Mono for clean, scannable output -- Status dots use `aria-label` for screen readers but no visible text label — the ref spec specifies dots only for consultations (text labels required for traffic lights in problems/medications views) -- View heading says "Consultation Journal" (clinical metaphor in content) while sidebar says "Experience" (CV-friendly nav) - -**Next task:** Task 9 — Rebuild MedicationsView (Skills view) - -### Iteration 10 — Task 9: Rebuild MedicationsView (Skills view) -**Completed:** Task 9 -**Changes made:** -- **MedicationsView.tsx**: Complete rebuild from ref-medications.md spec: - - **Font updates**: All `font-inter` references replaced with `font-ui` (Elvaro Grotesque) - - **Card styling**: Added `shadow-pmr` (multi-layered shadow), `border border-[#E5E7EB]`, `overflow-hidden` - - **Category tabs**: Three tabs (Active Medications, Clinical Medications, PRN) with count badges showing item count per category. Active tab: white bg, NHS blue bottom border, blue text. Inactive: `#F9FAFB` bg, gray text, hover to white. - - **Semantic table**: Proper `
`, ``, `` markup per guardrails - - **Sortable columns**: ChevronsUpDown (neutral), ChevronUp/ChevronDown (active, NHS blue) sort indicators. Three-state cycle: asc → desc → none. - - **Column borders**: `border-r border-[#E5E7EB]` between columns for clinical authenticity - - **Row height**: `h-[40px]` per design system spec - - **Alternating rows**: `bg-white` / `bg-[#F9FAFB]` via index-based alternation - - **Hover state**: `hover:bg-[#EFF6FF]` (subtle blue tint) on rows and sort buttons - - **Status dots**: 6px green circles with "Active" text label (guardrail: dots must always have text) - - **Framer Motion expand/collapse**: `AnimatePresence initial={false}` + `motion.tr`/`motion.div` for height-only animation (no opacity fade per guardrail). 200ms ease-out. - - **Chevron rotation**: `motion.div` with `animate={{ rotate: isExpanded ? 180 : 0 }}` — consistent with ConsultationsView pattern - - **Prescribing history**: Vertical timeline with NHS blue dots (`bg-[#005EB8]` with white ring), connecting line (`bg-[#E5E7EB]`). Year markers in `font-geist font-semibold text-[12px]`, descriptions in `font-geist text-[12px]`. - - **Mobile card layout**: Stacked key-value pairs with expand/collapse, same Framer Motion animation - - **Accessibility**: `role="tablist"`, `role="tab"`, `aria-selected`, `aria-controls` on tabs. `aria-expanded` on rows. `tabIndex={0}` + keyboard handler on table rows. `focus-visible:ring-2` on mobile buttons. - - **AccessibilityContext integration**: `setExpandedItem` called on expand/collapse to update breadcrumb - - **prefers-reduced-motion**: All Framer Motion animations use `duration: 0` when reduced motion preferred - - **Tab panel**: Proper `id` and `role="tabpanel"` with `aria-labelledby` - - **Font sizes per spec**: Headers `text-[13px]`, data cells `text-[13px]`, drug names `text-[14px]`, prescribing history `text-[12px]` - -**Codebase patterns discovered:** -- `AnimatePresence initial={false}` on table rows prevents animation when switching tabs (content should appear instantly) -- Count badges on tabs provide scannable category sizes — computed once outside component via `categoryCounts` record -- Column borders between `
`, `
`/`` cells (via `border-r border-[#E5E7EB] last:border-r-0`) add clinical authenticity -- Framer Motion `motion.tr` works for table row expand but needs a nested `motion.div` for reliable height animation (table row height can't animate directly) -- The `SortIndicator` component pattern (ChevronsUpDown → ChevronUp/ChevronDown) is reusable for any sortable table - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 395.01 KB bundle - -**Visual review:** Completed via Playwright MCP at default viewport -- Collapsed state: All 8 Active Medications visible with correct data in all 5 columns -- Tab switching: Active → Clinical (6 items) → PRN (4 items), all instant with correct count badges -- Expanded Python row: Prescribing history with vertical timeline, NHS blue dots, Geist Mono year markers, descriptions -- Accordion behavior: Expanding SQL collapsed Python (single-expand) -- Sort indicators: ChevronsUpDown visible in all column headers -- Alternating rows: White/gray-50 alternation visible -- Column borders: Vertical separators between all columns -- Status dots: Green dots with "Active" text label in every row -- Card shadow: Multi-layered shadow visible around container -- Footer: "8 medications in this category. Click a row to view prescribing history." - -**Issues encountered:** None - -**Design decisions:** -- Moved `prefersReducedMotion` check to module scope (computed once, not per render) -- Used `ChevronsUpDown` from lucide-react for neutral sort state per ref spec (was using `ArrowUpDown`) -- Drug Name column includes the expand chevron inline (saves a dedicated column, cleaner layout) -- Timeline dots use `ring-2 ring-white` to create white gap between dot and timeline line -- Tab count badges use `bg-[#005EB8]/10 text-[#005EB8]` for active tab, `bg-gray-200 text-gray-500` for inactive - -**Next task:** Task 10 — Rebuild ProblemsView (Achievements view) - -### Iteration 11 — Task 10: Rebuild ProblemsView (Achievements view) -**Completed:** Task 10 -**Changes made:** -- **ProblemsView.tsx**: Complete rebuild from ref-problems.md spec: - - **Font updates**: All `font-inter` → `font-ui` (Elvaro Grotesque), `font-mono` → `font-geist` for codes/dates - - **Card styling**: Added `shadow-pmr` multi-layered shadows to both Active and Resolved Problems containers - - **Framer Motion expand/collapse**: Replaced CSS transition with `AnimatePresence` + `motion.tr`/`motion.div` for height-only animation (200ms ease-out, no opacity fade per guardrail) - - **Chevron rotation**: `motion.div` with `animate={{ rotate: isExpanded ? 180 : 0 }}` — consistent with ConsultationsView/MedicationsView pattern - - **Hover colors**: Changed from `bg-blue-50` → `bg-[#EFF6FF]` (subtle blue tint) for row hover states - - **Font sizes per spec**: Headers `text-[13px]`, problem descriptions `text-[14px]`, codes/dates `text-xs`, narrative `text-[14px]` - - **TrafficLight component**: Added `font-ui` to text labels for WCAG-compliant status indicators (dot + text) - - **Two semantic tables**: Active Problems (4 columns: Status, Code, Problem, Since) and Resolved Problems (6 columns: + Resolved, Outcome) - - **Expandable rows**: Full-width sub-row with `bg-gray-50` background, narrative text, and linked consultations section - - **Linked consultations**: `ExternalLink` icon + clickable links in NHS blue with `focus-visible:ring-2` for keyboard nav - - **AccessibilityContext integration**: `setExpandedItem` called on expand/collapse to update breadcrumb with problem description - - **Mobile cards**: Updated to use `font-ui`/`font-geist`, added `shadow-pmr`, Framer Motion expand animation, `focus-visible` rings - - **Reduced motion support**: All Framer Motion animations use `duration: 0` when `prefersReducedMotion` is true - - **Module-scope `prefersReducedMotion`**: Computed once at module load, not per render - -**Codebase patterns discovered:** -- `AnimatePresence initial={false}` + `motion.tr` for table row expand is consistent across all expandable table views -- Traffic light dots (8px circles) MUST always have text labels per WCAG guardrail — never color-only indicators -- `font-geist` is used for all coded entries (SNOMED-style codes like `[MGT001]`, `[EFF002]`) and dates -- Linked consultations pattern: `ExternalLink` icon + clickable link that calls `onNavigate('consultations', consultationId)` -- Breadcrumb updates via AccessibilityContext: pass expanded item name (string) to `setExpandedItem`, not an object - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 395.86 KB bundle - -**Visual review:** Completed via Playwright MCP at default viewport -- Both tables render correctly: Active Problems (3 items) and Resolved Problems (8 items) -- Traffic lights visible with text labels: Green "Active", Amber "In Progress", Green "Resolved" -- Codes display in Geist Mono: `[MGT001]`, `[TRN001]`, `[EFF001]`, etc. -- Multi-layered shadows visible on both card containers -- Expanded first resolved problem row: narrative text displays in Elvaro 14px, `bg-gray-50` background -- Linked Consultations section: "LINKED CONSULTATIONS:" header + NHS blue clickable link with ExternalLink icon -- Chevron rotation animation smooth (180° when expanded) -- Column borders: `1px solid #E5E7EB` visible between all table cells -- Table headers: uppercase, `text-[13px]`, gray-400 color - -**Issues encountered:** None - -**Design decisions:** -- Moved `prefersReducedMotion` to module scope (computed once) for performance — pattern from MedicationsView -- Used `useCallback` for `handleToggle` to prevent unnecessary re-renders when passing to child components -- Breadcrumb receives problem description string (not object) — AccessibilityContext stores simple string IDs -- Traffic light text labels use `text-xs` and `text-gray-600` for subtle but readable status indicators -- Expandable content wraps in `motion.div` with `overflow: hidden` for smooth height animation - -**Next task:** Task 11 — Rebuild InvestigationsView + DocumentsView (Projects + Education views) - -### Iteration 12 — Task 11: Rebuild InvestigationsView + DocumentsView (Projects + Education) -**Completed:** Task 11 -**Changes made:** -- **InvestigationsView.tsx**: Complete rebuild from ref-investigations-documents.md spec: - - **Framer Motion expand/collapse**: Replaced CSS height transition with `AnimatePresence initial={false}` + `motion.tr`/`motion.div` for height-only animation (200ms ease-out, no opacity fade per guardrail) - - **Chevron rotation**: `motion.div` with `animate={{ rotate: isExpanded ? 180 : 0 }}` — consistent with all other expandable views - - **Font updates**: All `font-inter` → `font-ui` (Elvaro Grotesque), `font-mono` → `font-geist` for dates, tree content - - **StatusBadge component**: Pill-styled badges with colored dots and text labels. Three statuses: Complete (emerald), Ongoing (amber), Live (emerald with `animate-ping` pulse) - - **Tree-indented expanded content**: Box-drawing characters (`├─`, `└─`) in Geist Mono 12px. `TreeLine` and `TreeBranch` helper components for consistent rendering. Results field uses nested sub-tree structure - - **Color-coded left borders**: Expanded panels have `border-l-4` colored by status (#10B981 for Complete/Live, #F59E0B for Ongoing) - - **View Results button**: NHS blue (#005EB8) button with ExternalLink icon, only appears for PharMetrics (the only project with `externalUrl`) - - **Card styling**: `shadow-pmr` multi-layered shadow, `border border-[#E5E7EB]`, `overflow-hidden` - - **Table improvements**: Column borders (`border-r border-[#E5E7EB]`), alternating rows (`bg-white`/`bg-[#F9FAFB]`), `hover:bg-[#EFF6FF]`, row height `h-[40px]` - - **Removed separate expand column**: Chevron integrated into Test Name column (saves a column, cleaner layout) - - **Accessibility**: `tabIndex={0}`, keyboard handler (Enter/Space), `aria-expanded`, descriptive `aria-label` - - **AccessibilityContext**: `setExpandedItem` updates breadcrumb with investigation name - - **Mobile cards**: Framer Motion animation, StatusBadge, focus-visible rings, tree-indented expanded content - - **Reduced motion**: All Framer Motion animations use `duration: 0` when `prefersReducedMotion` is true - -- **DocumentsView.tsx**: Complete rebuild from ref-investigations-documents.md spec: - - Same Framer Motion expand/collapse pattern as InvestigationsView - - **Document type icons**: `FileText` (Certificate), `Award` (Registration), `GraduationCap` (Results), `FlaskConical` (Research) - - **Color-coded left borders by document type**: NHS blue (#005EB8) for Certificate, emerald (#10B981) for Registration, indigo (#6366F1) for Results, violet (#8B5CF6) for Research - - **Tree-indented expanded content**: Dynamic field rendering — only shows fields that exist in the data (institution, classification, duration, research, notes) - - **Font updates**: All `font-inter` → `font-ui`, `font-mono` → `font-geist` - - **Card styling**: `shadow-pmr`, `border border-[#E5E7EB]`, alternating rows, hover states - - **Accessibility**: Same pattern as InvestigationsView — tabIndex, keyboard, aria-expanded, AccessibilityContext - -**Codebase patterns discovered:** -- `TreeLine` and `TreeBranch` helper components create a reusable tree-indented display pattern — could be extracted to a shared component for use in other views -- StatusBadge with pill styling (bg + border + dot + text) is more visually refined than the simple dot+text TrafficLight pattern used in ProblemsView -- Dynamic field list pattern (build an array of `{ label, value }` objects from optional props, then iterate with `TreeLine`) is cleaner than a chain of conditional JSX for optional fields -- Removing the dedicated expand column (integrating chevron into the first data column) saves horizontal space — applicable to any expandable table -- Color-coded left borders on expanded panels provide visual connection between the row and its detail panel - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 395.31 KB bundle - -**Visual review:** Completed via Playwright MCP at default viewport -- **InvestigationsView (Projects):** - - 5 rows visible with correct data in all 4 columns - - StatusBadge pills: "Live" with pulsing dot (PharMetrics), "Complete" in emerald (all others) - - Expanded PharMetrics: tree-indented Geist Mono content with `├─`/`└─` characters - - Green left border on expanded panel - - "View Results" button in NHS blue with ExternalLink icon - - Results sub-tree with nested `├─`/`└─` - - Column borders between all columns - - Alternating row backgrounds visible -- **DocumentsView (Education):** - - 5 rows with document type icons (FileText, Award, GraduationCap, FlaskConical) - - Expanded MPharm: all 7 fields rendered with tree indentation - - Blue left border (Certificate type) - - Research field shows detail + grade on second line - - Notes field uses `└─` (last item indicator) - - Dates in Geist Mono, titles in Elvaro Grotesque - -**Issues encountered:** None - -**Design decisions:** -- Integrated chevron into first data column (Test Name / Document) rather than separate expand column — saves space, cleaner -- Used `TreeLine` / `TreeBranch` helper components for consistent tree structure rather than inline JSX -- Document type colors chosen for visual distinction: blue (Certificate), green (Registration), indigo (Results), violet (Research) -- Status border colors match the badge colors for visual consistency -- Module-scope `prefersReducedMotion` (computed once) for performance — established pattern - -**Next task:** Task 13 — Fuzzy search with fuse.js - -### Iteration 13 — Task 12: Rebuild ReferralsView (Contact) -**Completed:** Task 12 -**Changes made:** -- **ReferralsView.tsx**: Updated all font references from `font-inter` to `font-ui` (Elvaro Grotesque) and `font-mono` to `font-geist`: - - **Module-scope `prefersReducedMotion`**: Moved from `useRef` pattern to module-level constant (consistent with other views) - - **PriorityOption component**: Updated label colors from `text-red-700`/`text-amber-700` to `text-red-600`/`text-amber-600` per ref spec. Added `font-ui` to label and tooltip text. - - **ContactMethodOption component**: Added `font-ui` to label text - - **FormField component**: All labels and error messages now use `font-ui` - - **DirectContactTable**: - - Card styling: `border-[#E5E7EB]`, `shadow-pmr` multi-layered shadow, `bg-[#F9FAFB]` header - - Row hover state: `hover:bg-[#EFF6FF]` (blue tint) for interactive rows - - Font updates: `font-ui` for labels, `font-geist` for contact values (email, phone, LinkedIn) - - Accessibility: `focus-visible:ring-2 focus-visible:ring-pmr-nhsblue/40` on all links - - **Form inputs**: Updated border color to `#D1D5DB`, focus ring to `ring-pmr-nhsblue/15`, transition to `transition-all duration-200`, `font-ui` for input text - - **Success state**: - - Card styling: `border-[#E5E7EB]`, `shadow-pmr`, `bg-[#F9FAFB]` header - - Reference number in `font-geist` (monospace) - - Button hover: `hover:bg-[#004D9F]` (darker NHS blue) - - Added `focus-visible:ring-2` on button - - **Form buttons**: Updated hover colors and added focus rings for accessibility - -**Codebase patterns discovered:** -- Module-scope `prefersReducedMotion` constant is now the established pattern across all views (simpler than `useRef` approach) -- Direct Contact table uses same hover color (`#EFF6FF`) as other interactive table rows for consistency -- Priority radio buttons with colored dots (red/blue/amber) + text labels follow the traffic light pattern from other views -- Form validation pattern: required fields check on submit, email regex validation, error display below fields -- Reference number generation uses `REF-YYYY-MMDD-NNN` format with random sequence number -- Success state pattern: CheckCircle icon → heading → reference (monospace) → description → action button - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 395.94 KB bundle - -**Visual review:** Completed via Playwright MCP at default viewport -- Form state: All priority radio buttons render with correct colors (red/blue/amber dots), tooltips visible on hover -- Input styling: Correct border color (#D1D5DB), NHS blue focus rings visible -- Direct Contact table: Multi-layered shadow visible, hover states work (blue tint on rows) -- Tested form submission: filled name + email, clicked "Send Referral" -- Success state: Green checkmark icon, "Referral sent successfully" heading, reference number in Geist Mono (REF-2026-0213-527), "Send Another Referral" button in NHS blue -- All fonts: Elvaro Grotesque for UI text, Geist Mono for NHS number and reference number -- Card styling: `shadow-pmr` visible, `#E5E7EB` borders correct - -**Issues encountered:** None - -**Design decisions:** -- Used module-scope `prefersReducedMotion` constant instead of `useRef` pattern (established convention from previous tasks) -- Priority label colors: `text-red-600`/`text-amber-600` (was `text-red-700`/`text-amber-700`) per ref spec for better contrast balance -- Direct Contact table rows use `hover:bg-[#EFF6FF]` (blue tint) consistent with other interactive tables -- Input focus ring uses `/15` opacity (was `/20`) per design system spec for subtler glow - -**Next task:** Task 13 — Fuzzy search with fuse.js - - - -### Iteration 14 — Task 13: Fuzzy search with fuse.js -**Completed:** Task 13 -**Changes made:** -- **Installed fuse.js** (npm install fuse.js) — version 7.0.0 -- **Created src/lib/search.ts**: - - `buildSearchIndex()` function — builds unified Fuse search index from all PMR data - - Search index includes: consultations (5), medications (18), problems (11), investigations (6), documents (5) — total 45 searchable items - - Each item has: id, title, section (ViewId), sectionLabel (CV-friendly), highlight (full text preview) - - Fuse.js config: threshold 0.3, weighted keys (title: 2, highlight: 1), minMatchCharLength: 2 - - `groupResultsBySection()` — groups search results by sectionLabel for organized dropdown - - Export types: `SearchResult`, `FuseResult` from fuse.js -- **Updated ClinicalSidebar.tsx**: - - Replaced simple `filter` search with `searchIndex.search()` (fuzzy matching) - - Added `useMemo(() => buildSearchIndex(), [])` — index built once on mount - - Search requires minimum 2 characters, returns top 10 results - - Results grouped by section using `groupResultsBySection()` - - Dropdown UI: section headers with icon + label + count, result rows with title + highlight (line-clamp-1) - - `handleSearchResultClick()` — navigates to section, calls `setExpandedItem(result.item.id)`, clears search - - Integrated with AccessibilityContext for breadcrumb updates - - Section headers show section icon from navItems - - Dropdown styling: `max-h-[400px] overflow-y-auto`, `bg-pmr-sidebar`, `border border-white/10`, `shadow-lg` - - Result hover: `hover:bg-white/[0.10]` - - TypeScript: imported `FuseResult` type, typed map callback parameter - -**Codebase patterns discovered:** -- Fuse.js search index pattern: build once in `useMemo`, search on every query change in separate `useMemo` -- Grouped results display: `Map` from grouping function, iterate with `Array.from(grouped.entries())` -- Search result navigation: change view + hash + call `setExpandedItem()` to auto-expand matching item -- Minimum query length (2 chars) prevents noise from single-character searches -- Top 10 result limit keeps dropdown manageable -- Section icon lookup: `navItems.find(item => item.label === sectionLabel)?.icon` - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 416.25 KB bundle (fuse.js adds ~21 KB) - -**Visual review:** Completed via Playwright MCP at http://localhost:5173 -- Searched "python": dropdown shows "Skills (1)" with Python medication, "Projects (3)" with 3 Python-related projects -- Section headers render with correct icons (Pill for Skills, Flask for Projects) and item counts -- Clicked Python result: navigated to Skills view (#medications hash), Python row expanded with prescribing history visible -- Searched "budget": dropdown shows "Skills (1)" with Budget Management, "Achievements (1)" with £220M budget problem -- Fuzzy matching works: partial matches, case-insensitive -- Clear search button (X icon) visible when query present -- Dropdown styling: dark sidebar background, white text, section headers at 50% opacity, result highlights at 50% opacity -- Line-clamp-1 on highlight text truncates long descriptions cleanly - -**Issues encountered:** None - -**Design decisions:** -- Used `useMemo` for search index (built once) and search results (recomputed on query change) — performance optimization -- Minimum 2 characters required — prevents overly broad results from single letters -- Top 10 results limit — prevents overwhelming dropdown, encourages more specific queries -- Section grouping preserves the clinical navigation structure — users see results organized by PMR section -- Highlight text uses `line-clamp-1` for clean truncation — full text visible on hover isn't needed (title is enough to identify) -- Search index includes both title (weight: 2) and full text (weight: 1) — prioritizes title matches but allows content searches - -**Next task:** Task 14 — Responsive design audit - -### Iteration 15 — Task 14: Responsive design audit -**Completed:** Task 14 -**Changes made:** -- **No code changes** — this was an audit/testing task only -- Tested all three responsive breakpoints using Playwright MCP browser tools: - - **Desktop (1280x800)**: 220px sidebar with full labels, full 3-row patient banner, 2-column grid layouts, semantic tables with proper columns - - **Tablet (800x600)**: 56px icon-only sidebar, single-row condensed banner, single-column card layouts - - **Mobile (375x667)**: Bottom navigation bar (56px height) with 7 icons, minimal top banner with overflow menu, search at top of each view, tables converted to card layouts -- Verified all responsive features per ref-interactions.md spec: - - ✅ Sidebar: Desktop full labels → Tablet icons only → Mobile bottom nav bar - - ✅ Patient banner: Desktop full (80px) → Tablet condensed (48px) → Mobile minimal with overflow menu - - ✅ Tables: Desktop full columns → Tablet horizontal scroll if needed → Mobile card layout (Skills, Achievements, Projects, Education all confirmed) - - ✅ Search: Desktop/Tablet in sidebar header → Mobile at top of each view - - ✅ Back navigation: Mobile has "Back to Summary" button on all non-Summary views - - ✅ Touch targets: Bottom nav buttons, card expand buttons, search input, Acknowledge button all appear adequately sized for touch interaction -- Verified table → card conversions on mobile: - - Skills (MedicationsView): Stacked cards with drug name, proficiency, frequency, status dot + text, expand chevron - - Achievements (ProblemsView): Stacked cards with traffic light dot + text, code, description, "Since" date, expand chevron - - Visual confirmation via screenshots captured at all three breakpoints - -**Codebase patterns discovered:** -- All responsive layout work was already complete from previous tasks (Tasks 5-12) — each view component already includes mobile card layouts alongside desktop table layouts -- Breakpoint switching handled by Tailwind `lg:` and `md:` prefixes throughout components -- `MobileBottomNav.tsx` component provides the mobile navigation bar (appears <768px only) -- `useBreakpoint` hook used in some components to conditionally render desktop vs mobile layouts -- Touch-friendly card buttons on mobile use adequate padding and min-height for 48px+ touch targets - -**Quality checks:** All passed -- TypeScript: No errors -- ESLint: 1 pre-existing warning in AccessibilityContext.tsx (not our changes) -- Build: Successful, 416.25 KB bundle - -**Visual review:** Completed via Playwright MCP at three breakpoints (1280x800, 800x600, 375x667) -- Desktop: Full sidebar, full banner, 2-column grids, semantic tables — all correct -- Tablet: Icon-only sidebar, condensed banner, single-column cards — all correct -- Mobile: Bottom nav bar visible, minimal banner with overflow menu, search at top, tables converted to cards — all correct -- All screenshots saved: desktop-summary-1280.png, tablet-summary-800.png, mobile-summary-375.png, mobile-skills-cards-375.png, mobile-achievements-final-375.png, desktop-skills-table-1280.png, mobile-touch-targets-375.png, mobile-summary-final-375.png - -**Issues encountered:** None — all responsive features already implemented correctly in previous tasks - -**Design decisions:** -- No changes needed — audit confirmed existing responsive implementation meets all spec requirements -- All three breakpoints render correctly with appropriate layout adaptations -- Touch targets on mobile appear adequate (visual assessment — formal measurement would require additional tooling) - -**Next task:** Task 15 — Accessibility audit + final polish - -### Iteration 16 — Task 15: Accessibility audit + final polish -**Completed:** Task 15 -**Changes made:** -- **ClinicalSidebar.tsx**: Replaced `