ad1ce81948
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
556 lines
38 KiB
Plaintext
556 lines
38 KiB
Plaintext
# Progress Log
|
|
|
|
## 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
|
|
- 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
|
|
- 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
|
|
|
|
### Data Architecture (CORRECT — do not modify)
|
|
- 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)
|
|
- 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
|
|
|
|
### 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
|
|
|
|
### 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
|
|
|
|
### Visual Review (Playwright MCP)
|
|
- 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`
|
|
- 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 `<main>` 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 `<table>` 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)
|
|
|