# Progress Log — Clinical Record PMR Phase ## Phase Transition **Previous phase completed:** React conversion of ECG Heartbeat CV (all 12 tasks done) **New phase started:** Clinical Record PMR System — Design 7 implementation **Date:** 2026-02-11 This is a complete redesign of the CV presentation, moving from the ECG animation concept to a Patient Medical Record system interface. All previous components (Hero, Skills, Experience, etc.) will be replaced with PMR-specific views and components. ## Codebase Patterns ### PMR Design System - **Source of truth**: `designs/07-the-clinical-record.md` — Complete specification for the PMR interface - **Color palette (light-mode only)**: - Main content: `#F5F7FA` (cool light gray) - Cards: `#FFFFFF` (white) - Sidebar: `#1E293B` (dark blue-gray) - Patient banner: `#334155` (lighter blue-gray) - NHS blue: `#005EB8` (primary interactive) - Green: `#22C55E` (active/resolved) - Amber: `#F59E0B` (alerts/in-progress) - Red: `#EF4444` (urgent) - Borders: `#E5E7EB` (gray-200) - **Typography**: Inter for general text, Geist Mono for coded entries/data values - **Spacing**: 4px base unit, tighter than previous design (clinical system density) - **Borders**: 1px solid gray-200, 4px radius (clinical systems use minimal rounding) - **Table rows**: 40px height, alternating colors ### Data Architecture - All PMR content lives in `src/data/` as typed arrays - Separation of data from components enables easy CV updates - Types defined in `src/types/pmr.ts` ### Animation Approach - **Login typing**: `setInterval` with character-by-character reveal (30ms/char username, 20ms/dot password) - **View switching**: Instant (no animation) — matches clinical system behavior - **Consultation expand**: Height 0→auto, 200ms, ease-out - **Alert entrance**: Slide down with spring, 250ms - **Alert dismiss**: Icon → checkmark (200ms) → collapse (200ms) - **Patient banner condensation**: Smooth height transition, 200ms - **Reduced motion**: Typing instant, slides become fades, expand instant ### Clinical System Authenticity - Navigation is instant — no crossfade - Tables use explicit borders on all cells - Traffic lights are 8px circles with text labels (never sole indicator) - Consultation format: History / Examination / Plan (clinical SOAP note structure) - Medications table mimics actual prescribing lists - Coded entries use [XXX000] format (SNOMED-style) ### Responsive Breakpoints - Desktop (>1024px): 220px sidebar, full tables - Tablet (768-1024px): 56px icon-only sidebar, scrollable tables - Mobile (<768px): Bottom nav bar, card layouts instead of tables ### Accessibility Requirements - Tables must be proper `` markup with `scope="col"` - Clinical alert uses `role="alert"` and `aria-live="assertive"` - Keyboard shortcuts: Alt+1-7 for navigation - Focus management after view changes and expansions - Screen reader announces views and table structure ## Iteration Log ### Iteration 1 — Task 1: Create PMR data layer and TypeScript types - **Completed**: Task 1 - Created PMR data layer with TypeScript interfaces and data files - **Files created**: - `src/types/pmr.ts` - All PMR TypeScript interfaces (Patient, Consultation, Medication, Problem, Investigation, Document, etc.) - `src/data/consultations.ts` - 5 roles mapped to consultation format with History/Examination/Plan structure - `src/data/medications.ts` - 18 skills mapped to medication format across 3 categories (Active, Clinical, PRN) - `src/data/problems.ts` - 11 problems with traffic light status (3 Active, 2 In Progress, 6 Resolved) - `src/data/investigations.ts` - 5 projects as investigations with methodology/results - `src/data/documents.ts` - 5 education/certification documents - `src/data/patient.ts` - Patient demographic data - **Design decisions**: - Used SNOMED-style codes for coded entries (EFF001, ALG001, AUT001, etc.) - Mapped employer colors: NHS blue (#005EB8) for ICB, Teal (#00897B) for Tesco - Proficiency percentages estimated from CV skill descriptions - Prescribing history for each skill shows progression over time - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - CV has 5 roles but only 4 explicitly listed dates - inferred Duty Pharmacy Manager from GPhC registration date (Aug 2016) - Key numbers verified: £14.6M efficiency, 14,000 patients, £2.6M savings, 70% reduction, 200 hours, £1M revenue, £220M budget - Skills categorized into Active (technical), Clinical (healthcare domain), PRN (strategic/leadership) ### Iteration 2 — Task 2: Modify ECGAnimation for PMR flatline transition - **Completed**: Task 2 - Modified ECGAnimation exit phase for clinical flatline → login transition - **Files modified**: - `src/components/ECGAnimation.tsx` - Changed exit from fade-to-white to flatline → black → login background - **Changes made**: - Replaced HOLD_TIME (0.75s) and EXIT_TIME (0.8s) with precise phase timings: - FLATLINE_HOLD: 300ms (hold after name trace) - FLATLINE_DRAW: 300ms (horizontal line extending rightward) - FADE_TO_BLACK: 200ms (canvas opacity fade) - BG_TRANSITION: 200ms (background to #1E293B login color) - New timing phases: isFlatlinePhase, isFadePhase, isBgTransitionPhase - Background now transitions to login screen color (#1E293B) instead of white - Flatline drawn from final name position to right edge of viewport - Scanline head dot hidden during fade/bg phases - **Design decisions**: - Flatline visually reads as patient monitor flatline (deliberate metaphor) - Total ECG phase still ~5-6 seconds, exit adds ~1 second - Background transition uses CSS transition for smooth handoff to LoginScreen - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - Canvas fade must complete before background transition for clean visual - The flatline extension needs to go slightly past viewport edge (+50px) for smooth visual ### Iteration 3 — Task 3: Build LoginScreen component with typing animation - **Completed**: Task 3 - Created LoginScreen component with authentic clinical login typing animation - **Files created/modified**: - `src/components/LoginScreen.tsx` - New component with typing animation - `src/App.tsx` - Added 'login' phase between 'ecg' and 'content' - `src/types/index.ts` - Added 'login' to Phase type - `index.html` - Added Inter font family - `tailwind.config.js` - Added PMR colors (sidebar, banner, nhsblue, etc.) and fonts (inter, geist) - **Design decisions**: - Username types at 30ms per character (A.CHARLWOOD = 11 chars + space = ~350ms) - Password fills 8 dots at 20ms per dot (~160ms) - Button shows pressed state (darker, scale) before onComplete callback - Blinking cursor at 530ms interval during typing - Uses Fira Code as monospace font (Geist Mono not available via Google Fonts) - NHS blue shield icon for clinical system branding - White login card: 320px wide, 12px radius, subtle shadow - **Accessibility**: - Respects prefers-reduced-motion: instant text appearance, ~500ms total - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - Geist Mono not available via Google Fonts, using Fira Code as fallback - Total login animation timing: ~1.2s (350ms username + 150ms pause + 160ms password + 150ms pause + 100ms button + 200ms hold) ### Iteration 4 — Task 4: Build PatientBanner component with full and condensed modes - **Completed**: Task 4 - Created PatientBanner component with scroll-based condensation - **Files created**: - `src/hooks/useScrollCondensation.ts` - IntersectionObserver hook for scroll detection - `src/components/PatientBanner.tsx` - Full (80px) and condensed (48px) banner modes - **Design decisions**: - IntersectionObserver with rootMargin -100px to detect scroll past threshold - Smooth height transition using CSS `transition-all duration-200 ease-out` - Sticky positioning with z-40 for persistent visibility - Status dot: 8px circle, green for Active - Badge: NHS blue background, white text, small pill shape - Action buttons: outlined with NHS blue, fill on hover - GPhC number formatted with spaces like NHS number (221 181 0) - Tooltip on NHS No field explaining it's GPhC Registration Number - **Accessibility**: - `role="banner"` on header element - `aria-label` on status dot - Proper link semantics for phone and email - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - IntersectionObserver with rootMargin provides cleaner scroll detection than scroll event listeners - Sentinel element at top of viewport triggers condensation when it leaves view - Sticky positioning requires no JavaScript for the sticky behavior itself ### Iteration 5 — Task 5: Build ClinicalSidebar component with navigation and search - **Completed**: Task 5 - Created ClinicalSidebar with navigation and search functionality - **Files created**: - `src/components/ClinicalSidebar.tsx` - Sidebar navigation with 7 items - `src/components/PMRInterface.tsx` - Main PMR layout container - **Files modified**: - `src/App.tsx` - Changed from 'content' to 'pmr' phase, uses PMRInterface - `src/types/index.ts` - Updated Phase type: 'content' → 'pmr' - **Design decisions**: - 220px fixed width sidebar with dark blue-gray background (#1E293B) - Header: "CareerRecord PMR v1.0.0" in 50% opacity white - 7 navigation items with Lucide icons (ClipboardList, FileText, Pill, AlertTriangle, FlaskConical, FolderOpen, Send) - Separator line between Summary and Consultations - Active state: 3px NHS blue left border, white text, background rgba(255,255,255,0.12) - Hover state: white text at 100%, background rgba(255,255,255,0.08) - Search input in header with basic filtering (fuse.js not installed yet) - Footer with "Session: A.CHARLWOOD" and current time (updates every minute) - URL hash routing (#summary, #consultations, etc.) - Keyboard shortcuts: Alt+1-7 for navigation, / to focus search, Escape to clear search - **Navigation behavior**: - Instant view switching (no animation) — matches clinical system authenticity - Click updates URL hash and activeView state simultaneously - On page load, hash is read to set initial view - **Accessibility**: - `role="navigation"` and `aria-label` on sidebar - `aria-current="page"` on active nav item - Keyboard navigation with Alt+1-7 shortcuts - Search has escape key to clear and blur - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - Lucide React already installed, provides all required icons - fuse.js not installed — basic search filtering implemented, can enhance later - Sticky positioning with `h-screen sticky top-0` keeps sidebar fixed while content scrolls - PMRInterface wraps PatientBanner + sidebar + main content layout ### Iteration 6 — Task 6: Build SummaryView component with clinical alert - **Completed**: Task 6 - Created SummaryView with Clinical Alert and summary cards - **Files created**: - `src/components/views/SummaryView.tsx` - Full Summary view with Clinical Alert and 4 cards - **Files modified**: - `src/components/PMRInterface.tsx` - Updated to render SummaryView when activeView is 'summary' - **Design decisions**: - Clinical Alert: amber background (#FEF3C7), 4px amber left border, AlertTriangle icon - Alert animates in with max-height transition (300ms delay after view loads) - Acknowledge button: on click, icon cross-fades to green Check (200ms), then alert collapses (200ms) - Patient Demographics card: full width, two-column key-value layout with right-aligned labels - Active Problems card: shows 3 active/in-progress problems with traffic light dots and dates - Current Medications Quick View: 4-column table (Drug, Dose, Freq, Status), top 5 Active meds - Last Consultation card: shows most recent role with truncated history text - Traffic lights: 8px circles, green for Active/Resolved, amber for In Progress - All tables use proper semantic `
` markup with `scope="col"` - "View Full List" / "View Full Record" links navigate to corresponding views - **Accessibility**: - `role="alert"` and `aria-live="assertive"` on Clinical Alert - `aria-label` on main content area with current view name - Proper table semantics for medications table - Traffic lights always accompanied by text labels (never sole indicator) - Respects `prefers-reduced-motion`: alert appears instantly, no animations - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - Alert animation uses max-height transition for smooth expand/collapse - Clinical Alert text uses amber-800 (#92400E) for contrast against amber-100 background - Grid layout: demographics full width, problems/medications side-by-side, last consultation full width - `line-clamp-2` and `line-clamp-3` utilities work well for truncating text in cards ### Iteration 7 — Task 7: Build ConsultationsView with History/Examination/Plan structure - **Completed**: Task 7 - Created ConsultationsView with expandable consultation entries - **Files created**: - `src/components/views/ConsultationsView.tsx` - Full Consultations view with 5 expandable entries - **Files modified**: - `src/components/PMRInterface.tsx` - Added ConsultationsView to renderView switch - **Design decisions**: - Each entry has 3px left border color-coded by employer: NHS blue (#005EB8) for ICB, Teal (#00897B) for Tesco - Collapsed state shows: status dot, date, organization (colored), role title, key coded entry summary - Status dot: green for current roles, gray for historical - Expanded state shows: Duration, HISTORY (paragraph), EXAMINATION (bullets), PLAN (bullets), CODED ENTRIES - Section headers styled in Inter 600, 12px, uppercase, tracking-wider, gray-400 - Coded entries use [XXX000] format in Geist Mono, gray-400 - Only one entry expanded at a time (accordion behavior) - Expand animation: height 0→auto (200ms, ease-out) - Chevron icon rotates 180° when expanded - **Accessibility**: - `aria-expanded` on toggle buttons - Status dots have `aria-label` describing current vs historical - Respects `prefers-reduced-motion`: expand is instant - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - Height animation uses `height: auto` which requires setting height to undefined after animation - Content inside expanded area uses separate opacity transition for smooth appearance - Border-left styling with explicit width/color in style prop for dynamic org colors ### Iteration 8 — Task 8: Build MedicationsView with sortable table and prescribing history - **Completed**: Task 8 - Created MedicationsView with sortable table and category tabs - **Files created**: - `src/components/views/MedicationsView.tsx` - Full Medications view with 3 category tabs - **Files modified**: - `src/components/PMRInterface.tsx` - Added MedicationsView to renderView switch - `src/index.css` - Added fadeIn animation for expanded content - **Design decisions**: - Three category tabs: Active Medications (technical), Clinical Medications (healthcare), PRN (strategic) - Tabs have descriptive subtitles to explain the mapping - Sortable columns: clicking header toggles asc/desc/null with visual indicators - Sort icons: ArrowUpDown (unsorted), ArrowUp (asc), ArrowDown (desc) in NHS blue - Table columns: Drug Name, Dose (%), Frequency, Start (year), Status - Row height: ~40px with 10px py padding - Alternating row colors: white / gray-50 - Hover state: blue-50 (#EFF6FF) tint - Traffic light status dots: 8px circles (green=Active, gray=Historical) with text labels - Expandable rows: click to show "Prescribing History" mini-timeline - Prescribing history: year + description, styled in Geist Mono - fadeIn animation (200ms ease-out) for expanded content - **Accessibility**: - Proper semantic `
` markup with `scope="col"` on headers - `role="tablist"` and `aria-selected` on tab buttons - `aria-expanded` on expandable rows - Traffic lights always accompanied by text labels - Respects `prefers-reduced-motion`: fadeIn disabled - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - Case block lexical declarations need curly braces wrapping to satisfy ESLint - Sort state with three values (null/asc/desc) provides intuitive toggle behavior - Frequency sort uses custom order object mapping ### Iteration 9 — Task 9: Build ProblemsView with traffic light system - **Completed**: Task 9 - Created ProblemsView with two tables and expandable narrative - **Files created**: - `src/components/views/ProblemsView.tsx` - Full Problems view with Active and Resolved sections - **Files modified**: - `src/components/PMRInterface.tsx` - Added ProblemsView to renderView switch - **Design decisions**: - Two sections: Active Problems (3 items) and Resolved Problems (8 items) - Traffic light component: 8px circles with text labels (green=Active/Resolved, amber=In Progress) - Active Problems table: Status, Code, Problem, Since columns - Resolved Problems table: Status, Code, Problem, Resolved, Outcome columns - Code column: [XXX000] format in Geist Mono, gray-500 - Expandable rows: click to show narrative and linked consultations - Linked consultations: clickable buttons navigate to Consultations view with item ID - Height animation: 200ms ease-out for expand/collapse - Hover state: blue-50 (#EFF6FF) background tint - Accordion behavior: only one row expanded at a time (per section? globally? went with global) - **Accessibility**: - Proper semantic `
` markup with `scope="col"` on headers - `aria-expanded` on clickable rows - Traffic lights have `aria-label` with status text - Expand button has `aria-label` for screen readers - Screen reader-only column header for expand button - Respects `prefers-reduced-motion`: height transition disabled - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - `problem.linkedConsultations` needed null coalescing since it's optional in the type - Height animation uses refs to measure content height for smooth expansion - Linked consultations use external link icon to indicate navigation ### Iteration 10 — Task 10: Build InvestigationsView with results panel - **Completed**: Task 10 - Created InvestigationsView with expandable rows and results panel - **Files created**: - `src/components/views/InvestigationsView.tsx` - Full Investigations view with 5 project entries - **Files modified**: - `src/components/PMRInterface.tsx` - Added InvestigationsView to renderView switch - **Design decisions**: - Status badges: Complete (green dot), Ongoing (amber dot), Live (pulsing green dot with ping animation) - Table columns: Test Name, Requested, Status, Result - Expandable rows: click to show tree-indented results panel - Results panel uses key-value layout with fixed-width labels (w-40) for alignment - Methodology and Results sections display multi-line content - Tech Stack displayed as comma-separated list - PharMetrics has "View Results" button linking to medicines.charlwood.xyz - Height animation: 200ms ease-out for expand/collapse - Accordion behavior: only one row expanded at a time (global) - Hover state: blue-50 (#EFF6FF) background tint - **Accessibility**: - Proper semantic `
` markup with `scope="col"` on headers - `aria-expanded` on clickable rows - Status badges have `aria-label` with status text - Traffic lights always accompanied by text labels - External link has proper target="_blank" rel="noopener noreferrer" - Respects `prefers-reduced-motion`: height transition disabled - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - Live status uses Tailwind animate-ping for pulsing effect (requires relative container) - Tree-indented structure uses flex with fixed-width labels for clean alignment - Results as bullet list provides better readability than comma-separated text ### Iteration 11 — Task 11: Build DocumentsView for education/certifications - **Completed**: Task 11 - Created DocumentsView with expandable rows and preview panel - **Files created**: - `src/components/views/DocumentsView.tsx` - Full Documents view with 5 document entries - **Files modified**: - `src/components/PMRInterface.tsx` - Added DocumentsView to renderView switch - **Design decisions**: - Document type icons: FileText (Certificate), Award (Registration), GraduationCap (Results), FlaskConical (Research) - Table columns: Type (icon), Document, Date, Source - Expandable rows: click to show tree-indented preview panel - Preview panel uses key-value layout with fixed-width labels (w-40) for alignment - Shows: Type, Date Awarded, Institution, Classification, Duration, Research (with grade), Notes - Height animation: 200ms ease-out for expand/collapse - Accordion behavior: only one row expanded at a time (global) - Hover state: blue-50 (#EFF6FF) background tint - Consistent with InvestigationsView expanded view style - **Accessibility**: - Proper semantic `
` markup with `scope="col"` on headers - `aria-expanded` on clickable rows - Screen reader-only column header for expand button - Respects `prefers-reduced-motion`: height transition disabled - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - Document type icons from Lucide match design spec exactly - Research documents show both detail and grade in same field with line break - Tree-indented structure consistency across Investigations and Documents views ### Iteration 12 — Task 12: Build ReferralsView with clinical referral form - **Completed**: Task 12 - Created ReferralsView with clinical referral form and direct contact section - **Files created**: - `src/components/views/ReferralsView.tsx` - Full Referrals view with form and contact table - **Files modified**: - `src/components/PMRInterface.tsx` - Added ReferralsView to renderView switch - **Design decisions**: - Form layout: two-column grid for patient info and referrer fields - Pre-filled patient info: CHARLWOOD, Andrew (Mr); NHS Number: 221 181 0 - Priority radio buttons: Urgent (red), Routine (NHS blue, default), Two-Week Wait (amber) - Tongue-in-cheek tooltips on priority options - Form fields: Referrer Name (required), Referrer Email (required, validated), Referrer Org (optional), Reason (textarea) - Contact method radio: Email, Phone, LinkedIn - Submit button: NHS blue (#005EB8), loading spinner on submit - Success state: green checkmark, reference number (REF-YYYY-MMDD-NNN), 24-48hr response message - Direct Contact table below form: Email (mailto), Phone (tel), LinkedIn (external link), Location - Form validation with inline error messages - Input styling: 1px border-gray-300, 4px radius, 8px 12px padding, NHS blue focus ring - **Accessibility**: - Proper form labels with `htmlFor` associations - Required field indicators (red asterisk) - Error messages announced - Radio buttons properly grouped with hidden inputs and visible styled indicators - Respects `prefers-reduced-motion`: no animations - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓, `npm run build` ✓ - **Learnings**: - Form validation uses simple state-based approach with error objects - Radio buttons styled with hidden `` and custom styled `` for visual control - Reference number uses current date plus random sequence for uniqueness - Direct Contact table uses same key-value layout as Patient Demographics card ### Iteration 13 — Task 13: Implement keyboard shortcuts and accessibility - **Completed**: Task 13 - Implemented keyboard shortcuts and accessibility features across the PMR interface - **Files created**: - `src/contexts/AccessibilityContext.tsx` - Global context for focus management and expanded item state - **Files modified**: - `src/components/ClinicalSidebar.tsx` - Added roving tabindex with Up/Down arrow navigation, Enter/Space activation, Home/End support - `src/components/PMRInterface.tsx` - Added focus management after view change, integrated with AccessibilityContext - `src/components/LoginScreen.tsx` - Triggers focus on first sidebar item after login completion - `src/components/App.tsx` - Wrapped with AccessibilityProvider - `src/components/views/ConsultationsView.tsx` - Added focus management when expanding consultation entries - `src/components/views/SummaryView.tsx` - Added scope="col" to table headers - **Accessibility features implemented**: - **Roving tabindex**: Sidebar navigation supports Up/Down arrows, Home/End, Enter/Space - **Focus management**: Focus moves to sidebar after login, focus moves to view heading after view change, focus moves to expanded content - **Global Escape**: Closes any expanded item across all views - **ARIA attributes**: role="menu" on nav, role="menuitem" on nav items, aria-current="page" on active, tabIndex management - **Table accessibility**: scope="col" on all table headers across all views - **Design decisions**: - Created AccessibilityContext to manage global expanded state and focus targets - Focus moves to expanded content immediately after expansion (not waiting for animation) - Screen reader announces view changes via heading focus - First nav button always receives tabindex=0 (roving tabindex pattern) - **Quality checks**: `npm run typecheck` ✓, `npm run lint` ✓ (1 warning about fast refresh), `npm run build` ✓ - **Learnings**: - Roving tabindex requires explicit tabIndex management based on focus state - useRef array for button refs enables dynamic nav item count - Global Escape handler in context prevents need for per-view handlers - Focus after login requires ref forwarding from sidebar to context