merge codex/kpi (prefer codex/kpi on conflicts)

This commit is contained in:
2026-02-16 10:52:24 +00:00
49 changed files with 7708 additions and 2508 deletions
File diff suppressed because it is too large Load Diff
-201
View File
@@ -1,201 +0,0 @@
# Implementation Plan — GP System Dashboard Overhaul
## Project Overview
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
- `npm run typecheck` — zero errors
- `npm run lint` — pass (pre-existing AccessibilityContext warning OK)
- `npm run build` — must succeed
## Important
**This file is for progress tracking only.** For implementation detail on any task, read the referenced file in `Ralph/refs/`. Do NOT bloat this file with implementation notes — keep it lean.
---
## Tasks
### Phase 0: Foundation
#### Task 1: Update design tokens and Tailwind config
> Detail: `Ralph/refs/ref-01-design-tokens.md`
- [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`
- [x] Create `src/data/profile.ts` (personal statement)
- [x] Create `src/data/tags.ts` (sidebar tags)
- [x] Create `src/data/alerts.ts` (sidebar alert flags)
- [x] Create `src/data/kpis.ts` (Latest Results metrics)
- [x] Create `src/data/skills.ts` (skills with medication frequency + years)
- [x] Update `src/types/pmr.ts` (new interfaces)
- [x] Run quality checks
#### Task 3: Update CLAUDE.md for new architecture
- [x] Already completed during project setup (manual intervention 2026-02-13)
### Phase 1: Core Layout
#### Task 4: Build TopBar component
> Detail: `Ralph/refs/ref-03-topbar-sidebar.md` (TopBar section)
- [x] Create `src/components/TopBar.tsx`
- [x] Brand section (icon + name + version tag)
- [x] Search bar (triggers command palette, not inline search)
- [x] Session info (mono font, pill badge)
- [x] Fixed position, 48px height, white bg, bottom border
- [x] Run quality checks
#### Task 5: Build new Sidebar — PersonHeader
> Detail: `Ralph/refs/ref-03-topbar-sidebar.md` (Sidebar PersonHeader section)
- [x] Create `src/components/Sidebar.tsx`
- [x] Avatar circle (52px, teal gradient, initials)
- [x] Name, title, status badge with pulse dot
- [x] Details grid (GPhC, Education, Location, Phone, Email, Registered)
- [x] 272px width, light background, right border
- [x] Run quality checks
#### Task 6: Build new Sidebar — Tags + Alerts
> Detail: `Ralph/refs/ref-03-topbar-sidebar.md` (Tags and Alerts section)
- [x] Section title component (uppercase, divider line)
- [x] Tags section (flex wrap pills, color variants)
- [x] Alerts section (colored flag items with icons)
- [x] Run quality checks
#### Task 7: Build DashboardLayout and wire up App.tsx
> Detail: `Ralph/refs/ref-04-dashboard-layout.md`
- [x] Create `src/components/DashboardLayout.tsx`
- [x] Three-zone layout: TopBar (fixed) + Sidebar (fixed) + Main (scrollable card grid)
- [x] Card grid: 2 columns desktop, 1 column <900px
- [x] Framer Motion entrance animations (topbar → sidebar → content)
- [x] Update App.tsx: replace PMRInterface with DashboardLayout in PMR phase
- [x] Verify boot → ECG → login → dashboard transition works
- [x] Run quality checks
### Phase 2: Dashboard Tiles
#### Task 8: Build reusable Card component
> Detail: `Ralph/refs/ref-05-card-and-top-tiles.md` (Card section)
- [x] Create `src/components/Card.tsx`
- [x] Base card styling (white, border, radius 8px, shadow-sm, hover shadow-md)
- [x] `full` variant (spans both grid columns)
- [x] CardHeader sub-component (dot + title + optional right text)
- [x] Run quality checks
#### Task 9: Build PatientSummary tile
> Detail: `Ralph/refs/ref-05-card-and-top-tiles.md` (PatientSummary section)
- [x] Create `src/components/tiles/PatientSummaryTile.tsx`
- [x] Full-width card, first in grid
- [x] Personal statement from `src/data/profile.ts`
- [x] Run quality checks
#### Task 10: Build LatestResults tile
> Detail: `Ralph/refs/ref-05-card-and-top-tiles.md` (LatestResults section)
- [x] Create `src/components/tiles/LatestResultsTile.tsx`
- [x] Half-width card, 2x2 metric grid
- [x] Four KPI metric cards with colored values
- [x] Data from `src/data/kpis.ts`
- [x] Run quality checks
#### Task 11: Build CoreSkills tile ("Repeat Medications")
> Detail: `Ralph/refs/ref-05-card-and-top-tiles.md` (CoreSkills section)
- [x] Create `src/components/tiles/CoreSkillsTile.tsx`
- [x] Half-width card, next to LatestResults
- [x] Skills listed as medications with frequency + years
- [x] Data from `src/data/skills.ts`
- [x] Run quality checks
#### Task 12: Build LastConsultation tile
> Detail: `Ralph/refs/ref-06-bottom-tiles.md` (LastConsultation section)
- [x] Create `src/components/tiles/LastConsultationTile.tsx`
- [x] Full-width card
- [x] Header info row (Date, Org, Type, Band)
- [x] Role title + achievement bullet list
- [x] Data from first entry in `src/data/consultations.ts`
- [x] Run quality checks
#### Task 13: Build CareerActivity tile
> Detail: `Ralph/refs/ref-06-bottom-tiles.md` (CareerActivity section)
- [x] Create `src/components/tiles/CareerActivityTile.tsx`
- [x] Full-width card, two-column activity grid
- [x] Merge roles + projects + certs + education into timeline
- [x] Color-coded dots by entry type
- [x] Run quality checks
#### Task 14: Build Education tile
> Detail: `Ralph/refs/ref-06-bottom-tiles.md` (Education section)
- [x] Create `src/components/tiles/EducationTile.tsx`
- [x] Full-width card, below Career Activity
- [x] Education entries from documents data
- [x] Run quality checks
#### Task 15: Build Projects tile
> Detail: `Ralph/refs/ref-06-bottom-tiles.md` (Projects section)
- [x] Create `src/components/tiles/ProjectsTile.tsx`
- [x] Full-width card, prominent presentation
- [x] Status badges, project names, years, descriptions
- [x] Data from `src/data/investigations.ts`
- [x] Run quality checks
### Phase 3: Interactions
#### Task 16: Tile expansion system
> Detail: `Ralph/refs/ref-07-interactions.md` (Tile Expansion section)
- [x] CareerActivity items expand to show full role detail
- [x] Projects items expand to show methodology, tech stack, results
- [x] CoreSkills items expand to show prescribing history
- [x] Height-only animation (200ms, no opacity fade)
- [x] Single-expand accordion
- [x] Keyboard: Enter/Space to expand, Escape to collapse
- [x] Run quality checks
#### Task 17: KPI flip card interaction
> Detail: `Ralph/refs/ref-07-interactions.md` (KPI Flip section)
- [x] LatestResults metrics flip on click
- [x] Front: value + label. Back: explanation text
- [x] CSS perspective flip (400ms) or instant swap with reduced motion
- [x] One card flipped at a time
- [x] Run quality checks
#### Task 18: Build Command Palette
> Detail: `Ralph/refs/ref-07-interactions.md` (Command Palette section)
- [x] Create `src/components/CommandPalette.tsx`
- [x] Ctrl+K trigger + search bar click trigger
- [x] Overlay with backdrop blur, ESC to close
- [x] Fuzzy search via fuse.js (adapt `src/lib/search.ts`)
- [x] Grouped results by section + Quick Actions
- [x] Keyboard navigation (arrows, Enter, Escape)
- [x] Run quality checks
### Phase 4: Polish
#### Task 19: Responsive design
> Detail: `Ralph/refs/ref-08-polish.md` (Responsive section)
- [x] Desktop (>1024px): full sidebar + 2-column grid
- [x] Tablet (7681024px): collapsed/hidden sidebar + adapted grid
- [x] Mobile (<768px): no sidebar, single-column tiles, simplified topbar
- [x] Touch-friendly targets (48px+)
- [x] Run quality checks
#### Task 20: Accessibility audit
> Detail: `Ralph/refs/ref-08-polish.md` (Accessibility section)
- [x] Semantic HTML (header, nav, main, article, section)
- [x] Keyboard navigation (Tab, Enter/Space, Escape, Ctrl+K, arrows)
- [x] ARIA (expanded, controls, labels, live regions, dialog)
- [x] Focus management (trap in palette, visible rings, return focus)
- [x] `prefers-reduced-motion` on all animations
- [x] Color contrast verification
- [x] Run quality checks
#### Task 21: Clean up and final polish
> Detail: `Ralph/refs/ref-08-polish.md` (Cleanup section)
- [ ] Remove unused old components (PatientBanner, ClinicalSidebar, Breadcrumb, etc.)
- [ ] Remove unused hooks (useScrollCondensation if unused)
- [ ] Verify no dead imports
- [ ] Final visual review against concept HTML
- [ ] Run quality checks (clean build)
-145
View File
@@ -1,145 +0,0 @@
# Ralph Wiggum Loop - Iteration Prompt
You are operating inside an automated loop. Each iteration you receive fresh context - you have NO memory of previous iterations. Your only persistence is the filesystem.
You are implementing **a GP System Dashboard** — a tile-based clinical record interface that presents Andy's CV as a GP surgery would display a patient record. The clinical metaphor lives in the structure (tiles as record sections, skills as "medications" with frequency, alerts, KPI metrics, career timeline) while the visual execution is modern and premium.
**The Concept:**
The "patient" is Andy's career. After a theatrical boot → ECG → login sequence, users see a dashboard with a light sidebar (person details, tags, alert flags) and a scrollable grid of tiles (Patient Summary, Latest Results, Repeat Medications/Skills, Last Consultation, Career Activity, Education, Projects). Tiles can be expanded for detail. A command palette (Ctrl+K) provides search. The reference design is `References/GPSystemconcept.html`.
## Your Task This Iteration
1. **Read the reference file for your task** (REQUIRED): Each task in `IMPLEMENTATION_PLAN.md` references a detail file in `Ralph/refs/`. You MUST read this file before writing code — it provides the full specification, CSS values, data sources, and component structure.
2. **Read the plan**: Open `IMPLEMENTATION_PLAN.md` and find the highest-priority unchecked item (`- [ ]`). Items are listed in dependency order — pick the first unchecked one. **The plan is for tracking only** — all implementation detail is in the referenced `Ralph/refs/` file.
3. **Read accumulated learnings**: Open `progress.txt` and read the "Codebase Patterns" section AND the most recent manual intervention entry. These contain critical context about the architecture, established patterns, and decisions from previous iterations.
4. **Read guardrails**: Open `guardrails.md` and read ALL guardrails. These are hard rules you MUST follow. Key guardrails include:
- Light-mode only
- Teal accent `#0D6E6E` (not NHS Blue) for interactive elements
- 8px border-radius for cards (not 4px)
- Three-tier shadow system (sm/md/lg)
- Height-only tile expansion (no opacity fade)
- Skills frequency: user-specified values (Data Analysis="Twice daily", etc.)
- Sidebar contains ONLY PersonHeader + Tags + Alerts
- Elvaro Grotesque font (not DM Sans, Inter, or Roboto)
- Geist Mono for data/timestamps (not Fira Code in dashboard)
5. **Implement the item**: Complete the single task you selected. Keep changes focused — one task per iteration. Write production-quality React/TypeScript code.
**IMPORTANT — Ref files are the source of truth.** If existing code contradicts the ref file, rebuild from the ref spec.
6. **Run quality checks**: Execute `npm run typecheck`, `npm run lint`, `npm run build`. Fix any issues before proceeding.
7. **Visual Review** (for visual tasks): After quality checks pass, verify your work in the browser using Playwright MCP:
a. Navigate to `http://localhost:5173` using `mcp__playwright__browser_navigate`.
b. **First load only**: The app plays boot→ECG→login (~15s). Use `mcp__playwright__browser_wait_for` with `time: 15`, then click the Log In button to reach the dashboard. On subsequent navigations, the app stays in dashboard phase.
c. Take a screenshot and compare against `References/GPSystemconcept.html` (open it in a separate tab if needed).
d. Check: colors match spec, correct font, proper spacing, borders, shadows, layout alignment, teal accent.
e. Fix discrepancies, re-run quality checks, re-screenshot.
f. Note the visual review outcome in progress.txt.
8. **Commit your changes**: Stage and commit all changes with a descriptive message referencing the task.
9. **Mark the item complete**: In `IMPLEMENTATION_PLAN.md`, change the item from `- [ ]` to `- [x]`.
10. **Update progress.txt**: Append to the "Iteration Log" section with:
- Which task you completed
- Any learnings or codebase patterns discovered (add to "Codebase Patterns" section too)
- Any issues encountered
- Design decisions made
- Visual review outcome
11. **Commit the progress update**: Stage and commit the updated `IMPLEMENTATION_PLAN.md` and `progress.txt`.
12. **Recommend model for next iteration**: Look at the NEXT unchecked task. Output a model recommendation:
```
<next-model>sonnet</next-model>
```
or
```
<next-model>opus</next-model>
```
**Decision framework:**
- **Use `sonnet`** for: configuration tasks, data files, simple wiring, accessibility audits, tasks with very prescriptive specs
- **Use `opus`** for: visual component builds, complex animation work, tasks requiring aesthetic judgment, command palette, interaction design
- **Default to `sonnet`** if unsure
13. **Determine if another iteration is needed**: The project needs another iteration if ANY task is unchecked, quality checks fail, or there are uncommitted changes.
14. **Send completion signal ONLY if truly complete**: If ALL tasks are verified done, quality checks pass, and no further work is needed:
```
<promise>COMPLETE</promise>
```
DO NOT output this string if there's any chance another iteration is needed.
## Critical Rules
- **ALWAYS read the ref file for your task before writing code**
- **Only work on ONE task per iteration**
- **Always read progress.txt AND guardrails.md before starting**
- **Ref files are the spec — existing code is not**
- **The plan file is for tracking only** — do not add detail to it
- **Use TypeScript strictly** — no `any` types, proper interfaces
- **Follow project structure** — components in `src/components/`, tiles in `src/components/tiles/`, data in `src/data/`
- **Respect prefers-reduced-motion** — all animations must have instant fallbacks
- **Keep commits atomic and well-described**
- **If quality checks fail, fix before committing**
- **If a task is blocked**, document why in progress.txt and move to next
## Reference Files
Each task references a specific detail file in `Ralph/refs/`:
| Tasks | Reference File |
|-------|---------------|
| Task 1 | `Ralph/refs/ref-01-design-tokens.md` |
| Task 2 | `Ralph/refs/ref-02-data-types.md` |
| Tasks 4-6 | `Ralph/refs/ref-03-topbar-sidebar.md` |
| Task 7 | `Ralph/refs/ref-04-dashboard-layout.md` |
| Tasks 8-11 | `Ralph/refs/ref-05-card-and-top-tiles.md` |
| Tasks 12-15 | `Ralph/refs/ref-06-bottom-tiles.md` |
| Tasks 16-18 | `Ralph/refs/ref-07-interactions.md` |
| Tasks 19-21 | `Ralph/refs/ref-08-polish.md` |
Also reference:
- `References/GPSystemconcept.html` — Visual/structural target for the dashboard
- `References/CV_v4.md` — Source CV content (roles, achievements, numbers, dates)
- `CLAUDE.md` — Project architecture, design direction, styling conventions
Read ONLY the referenced file(s) for your current task. Do not read all ref files at once.
## Design Highlights
**Color Palette (Light-mode only):**
- Background: `#F0F5F4` (warm sage)
- Surface/cards: `#FFFFFF`
- Sidebar: `#F7FAFA` (very light)
- Accent: `#0D6E6E` (teal)
- Borders: `#D4E0DE` (structural), `#E4EDEB` (cards)
- Text: `#1A2B2A` (primary), `#5B7A78` (secondary), `#8DA8A5` (tertiary)
- Status: `#059669` (success), `#D97706` (amber), `#DC2626` (alert)
**Typography:**
- Elvaro Grotesque (`font-ui`) for UI text
- Geist Mono (`font-geist`) for data, timestamps, coded entries
- Fira Code for boot/ECG terminal only
**Layout:**
- TopBar: fixed, 48px, white, bottom border
- Sidebar: 272px, light, person header + tags + alerts
- Main: scrollable card grid, 2 columns desktop, 1 column mobile
- Cards: 8px radius, shadow-sm, border-light
**Key Interactions:**
- Tile expansion: height-only animation, 200ms, accordion
- KPI flip: CSS perspective 400ms, click to flip/unflip
- Command palette: Ctrl+K, fuzzy search, keyboard navigation
- Entrance: staggered topbar → sidebar → content
-134
View File
@@ -1,134 +0,0 @@
# Guardrails
Hard rules that MUST be followed in every iteration. Violating these will produce incorrect output.
## Design Direction
### When: Making ANY aesthetic decision
**Rule:** The direction is **GP System Dashboard** — a tile-based clinical record system with a light, modern aesthetic. Teal accent (#0D6E6E), light sidebar (#F7FAFA), warm sage background (#F0F5F4), white card surfaces. The clinical metaphor lives in the STRUCTURE (tiles as "record sections", status indicators, medication-style skill entries, coded entries) — not in dark chrome or heavy clinical styling.
**Why:** The previous dark-sidebar PMR interface is being replaced with the lighter, tile-based GP System concept (`References/GPSystemconcept.html`).
## Design System Guardrails
### When: Writing ANY visual component
**Rule:** Light-mode only. Do NOT add dark mode classes, `dark:` prefixes, or theme toggles.
**Why:** The design direction is light-mode only.
### When: Setting border-radius on cards and tiles
**Rule:** Use 8px border-radius (var(--radius)) for cards and tiles. Use 6px (var(--radius-sm)) for inner elements (metric cards, activity items, tags). The only exception is the LoginScreen card which uses 12px, and the command palette which uses 12px.
**Why:** The GP System concept uses 8px radius, slightly more rounded than the old 4px clinical style, reflecting the lighter aesthetic.
### When: Using monospace/code font
**Rule:** Use Geist Mono (`font-family: 'Geist Mono', monospace`) for timestamps, session info, dates, GPhC number, and coded data values. Fira Code is used in boot/ECG phases only.
**Why:** Geist Mono is the specified monospace font for the dashboard interface.
### When: Choosing the UI text font
**Rule:** Use Elvaro Grotesque (font-ui) as primary, Blumir (font-ui-alt) as alternative. Do NOT use Inter, Roboto, or system defaults. DM Sans appears in the concept HTML but is NOT the production font — use Elvaro Grotesque.
**Why:** Premium typography is the primary vehicle for the luxury feel. The concept HTML uses DM Sans as a placeholder; the production build uses the licensed premium fonts.
### When: Adding shadows to cards or tiles
**Rule:** Use the three-tier shadow system:
- `--shadow-sm`: `0 1px 2px rgba(26,43,42,0.05)` (default card state)
- `--shadow-md`: `0 2px 8px rgba(26,43,42,0.08)` (hover / interactive)
- `--shadow-lg`: `0 8px 32px rgba(26,43,42,0.12)` (command palette, overlays)
**Why:** Shadows create depth hierarchy. sm=resting, md=interactive, lg=overlay.
### When: Styling borders
**Rule:** Use `1px solid var(--border-light)` (#E4EDEB) for card and tile borders. Use `1px solid var(--border)` (#D4E0DE) for structural borders (sidebar right edge, topbar bottom, section dividers).
**Why:** Two-tier border system: lighter for cards, slightly stronger for structural elements.
### When: Choosing accent/interactive colors
**Rule:** Use teal `#0D6E6E` (var(--accent)) for interactive elements: links, active states, avatar gradient, dots, hover highlights. Hover: `#0A8080`. Accent-light: `rgba(10,128,128,0.08)` for subtle backgrounds.
**Why:** Teal is the primary accent in the GP System concept. It replaces NHS Blue as the interactive color.
### When: Using status colors
**Rule:** Status colors: success=`#059669`, amber=`#D97706`, alert=`#DC2626`, purple=`#7C3AED` (education). Each with matching light/border variants. Always pair colored indicators with text labels.
**Why:** Traffic light convention. Color-only indicators violate WCAG.
## Layout Guardrails
### When: Building the dashboard layout
**Rule:** Three-zone layout: TopBar (fixed, 48px) + Sidebar (fixed left, 272px) + Main content (scrollable card grid). Main content has 24px-28px padding and card grid with 16px gap. Grid: 2 columns on desktop, 1 column below 900px.
**Why:** Matches the GP System concept layout structure.
### When: Ordering tiles in the card grid
**Rule:** Tile order: Patient Summary (full) → Latest Results (half) + Repeat Medications (half) → Last Consultation (full) → Career Activity (full) → Education (full) → Projects (full). Full-width tiles span both columns.
**Why:** This ordering follows the concept layout with the user's addition of Patient Summary at the top.
## Sidebar Guardrails
### When: Building the sidebar
**Rule:** Sidebar contains ONLY: PersonHeader (avatar, name, title, status, details) → Tags → Alerts/Highlights. Active Projects, Core Skills, and Education are in the MAIN CONTENT as tiles, NOT in the sidebar.
**Why:** User explicitly requested moving Projects, Skills, and Education from sidebar to main dashboard tiles.
## Interaction Guardrails
### When: Expanding/collapsing tile content
**Rule:** Height animation ONLY (200ms, ease-out). Do NOT fade opacity on content. Single-expand accordion — only one item expanded at a time within a tile.
**Why:** Consistent expand/collapse behavior. Opacity fade was explicitly prohibited.
### When: Building the command palette
**Rule:** Trigger via Ctrl+K or search bar click. Overlay with backdrop blur. ESC to close. Arrow key navigation. Fuzzy search via fuse.js.
**Why:** Matches concept interaction pattern.
### When: Building KPI flip cards
**Rule:** Click to flip metric card (front=value, back=explanation). 400ms CSS perspective flip or instant swap with reduced motion. Only one card flipped at a time.
**Why:** User requested interactive KPI exploration with explanation text.
## Login Screen Guardrails
### When: Building the login typing animation
**Rule:** Username types at 80ms/char. Password dots at 60ms/dot. After typing completes, the "Log In" button becomes interactive — the user clicks it. It is NOT auto-triggered.
**Why:** The natural pace lets users absorb what's happening. The interactive button creates a moment of user agency.
## Component Guardrails
### When: Displaying traffic light status indicators
**Rule:** Colored dots must ALWAYS have text labels. Never use color as the sole indicator.
**Why:** WCAG — color cannot be the only means of communicating information.
### When: Writing table or list markup inside tiles
**Rule:** Use semantic markup. Tables use `<table>`, `<thead>`, `<th scope="col">`, `<tbody>`, `<tr>`, `<td>`. Lists use `<ul>`/`<ol>` with `<li>`. No div-based tables.
**Why:** Screen readers require native semantics.
### When: Using icons
**Rule:** Use `lucide-react` icons only. No unicode symbols, no inline SVG copied from external sources. Exception: the concept's SVG icons should be converted to their lucide-react equivalents (e.g., concept's house icon → `Home` from lucide-react).
**Why:** Consistent icon system, tree-shakeable, accessible.
## Data Guardrails
### When: Displaying CV content
**Rule:** All data must come from `src/data/*.ts` files. Do NOT hardcode content in components or change any numbers/dates. New data files (profile.ts, tags.ts, alerts.ts, kpis.ts, skills.ts) must be accurate to CV_v4.md.
**Why:** Data has been validated against CV_v4.md. Single source of truth.
### When: Building the "Repeat Medications" (skills) tile
**Rule:** Use the exact frequencies specified by the user: Data Analysis="Twice daily", Power BI="Once weekly", Python="Daily", SQL="Daily", JavaScript/TypeScript="When required". Include "years of experience" like "length of time on medication".
**Why:** User explicitly specified these frequency values for the medication metaphor.
## Visual Review Guardrails
### When: Completing any visual task
**Rule:** After quality checks, open `http://localhost:5173` via Playwright MCP tools, take a screenshot, and compare against `References/GPSystemconcept.html`. Fix visual discrepancies. If browser tools are unavailable, note in progress.txt and proceed.
**Why:** Code review alone cannot catch visual issues.
### When: Browser tools fail
**Rule:** Skip visual review, note it in progress.txt, continue. Do NOT retry more than twice.
**Why:** Visual review is valuable but not blocking.
## Technical Guardrails
### When: Writing TypeScript
**Rule:** No `any` types. All props must have typed interfaces.
**Why:** Strict typing prevents runtime errors.
### When: Adding animations
**Rule:** All animations must respect `prefers-reduced-motion`. With reduced motion: all animations skip to final state instantly.
**Why:** Accessibility requirement.
### When: Running quality checks
**Rule:** Run `npm run typecheck`, `npm run lint`, and `npm run build` after EVERY task. Fix all errors before committing.
**Why:** Build failures compound across iterations.
### When: Referencing the concept design
**Rule:** The reference design is `References/GPSystemconcept.html`. Open it in a browser or read the HTML to understand the visual target. The concept is the LAYOUT reference; production fonts and some colors differ (see font and color guardrails).
**Why:** The concept HTML is the single source of truth for layout and spatial composition.
-190
View File
@@ -1,190 +0,0 @@
# Progress Log — Career Constellation Refinement
# Branch: ralph/constellation-refinement
# Started: 2026-02-16
## Codebase Patterns
- CareerConstellation.tsx (~868 lines) is a D3 force-directed graph with React overlay buttons for accessibility
- D3 simulation uses forceSimulation with charge, link, x, y, and collide forces
- Module-level window.matchMedia reads for prefersReducedMotion and supportsCoarsePointer
- DashboardLayout manages constellation state: highlightedNodeId, pinnedNodeId via callbacks
- Work experience data in src/data/consultations.ts, constellation-specific data in src/data/constellation.ts
- CSS layout: .pathway-columns grid — first column is .chronology-stream (work experience), second is .pathway-graph-sticky (constellation graph)
- Current grid: minmax(0, 1.85fr) minmax(0, 1fr) at desktop — work experience ~65%, graph ~35%
- containerHeight prop drives graph height on desktop; on mobile (viewport < 1024px) uses MOBILE_FALLBACK_HEIGHT (360px)
- Use window.innerWidth for breakpoint checks, not container.clientWidth — the SVG container overflows on mobile
- Design tokens in index.css :root — use var(--accent), var(--border-light), var(--text-tertiary), etc.
- SVG shadows: use <filter> with <feDropShadow> in <defs>, apply to <g> groups via .attr('filter', 'url(#filter-id)')
- Role nodes are pill-shaped rects (ROLE_WIDTH=104, ROLE_HEIGHT=32, ROLE_RX=16) with orgColor badge styling
- Skill nodes use SKILL_RADIUS_DEFAULT (7) resting, SKILL_RADIUS_ACTIVE (11) highlighted — D3 transitions, not CSS
- Link lines are <path> elements with quadratic bezier curves — tick handler sets d attr
- Accessibility buttons are React <button> elements overlaid on SVG at opacity 0, container pointerEvents 'none', buttons 'auto'
- callbacksRef pattern prevents stale closures — use for all D3→React callbacks
- Bidirectional highlighting: highlightedNodeId (timeline→graph) and highlightedRoleId (graph→timeline)
- Force simulation: role forceY ~0.98, charge -120/-55, link distance 72, collision ~52-65px roles
- applyGraphHighlight is the single source of truth for all visual states (resting, highlighted, dimmed)
- Resting state values (US-003): skill fill-opacity 0.35, skill label opacity 0.5, link stroke-opacity 0.15, dimmed node opacity 0.15, active skill fill-opacity 0.9
- Initial D3 rendering values MUST match applyGraphHighlight resting values — initial stroke-opacity, fill-opacity, label opacity are set during node/link creation AND in the highlight function
- Viewport-proportional scaling: dimensions state includes { width, height, scaleFactor }. D3 effect uses `const sf = isMobile ? 1 : scaleFactor`. All desktop pixel values scaled via Math.round(value * sf)
- scaleFactor formula: Math.max(1, Math.min(1.6, viewportWidth / 1440)) — 1.0x at ≤1440px, 1.6x at ≥2560px. Only active at ≥1024px viewport
- Use the d3-viz skill for all D3 rendering stories
- Consultation entries ordered reverse-chronologically (newest first) — new entries go at the end of the array
- Constellation role nodes, skill mappings, and links are in constellation.ts — adding nodes there automatically extends yScale domain and screen reader description
- Mobile accordion (coarse pointer): pinnedNodeId drives both graph highlight AND accordion visibility. Accordion only shows for role-type nodes (not skills)
- SVG background rect has class `.bg-rect` — used for "tap elsewhere to close" handler on touch devices
- consultation.orgColor is the source of per-employer colour for cards, dots, borders, and coded entries. Use hexToRgba(orgColor, opacity) for tinted variants
- hexToRgba(hex, opacity) helper exists in both WorkExperienceSubsection.tsx and DashboardLayout.tsx for converting hex to rgba
## 2026-02-16 - US-001
- Added Duty Pharmacy Manager (2016-2017, Tesco PLC) and Pre-Registration Pharmacist (2015-2016, Paydens Pharmacy) role nodes to constellation.ts
- Added roleSkillMappings entries for both new roles (5 skills for Duty Pharm Mgr, 3 for Pre-Reg)
- Added constellationLinks with strength values for both new roles
- Added consultation entries for both new roles to consultations.ts with examination, plan, and codedEntries
- Fixed Pharmacy Manager orgColor from '#00897B' (teal) to '#E53935' (Tesco red) in both constellation.ts and consultations.ts
- Updated role count comment from "4 roles" to "6 roles"
- Files changed: src/data/constellation.ts, src/data/consultations.ts
- **Learnings for future iterations:**
- buildScreenReaderDescription() iterates constellationNodes dynamically — no manual update needed when adding roles
- The #00897B teal colour in index.css (:root --teal) is a generic design token, NOT the Tesco-specific colour — don't change it
- Consultation.orgColor must match the constellation node orgColor for visual consistency between graph and cards
---
## 2026-02-16 - US-002
- Added UEA MPharm (2011-2015, University of East Anglia, orgColor #7B2D8E) education node to constellation.ts
- Added Highworth A-Levels (2009-2011, Highworth Grammar School, orgColor #9C27B0) education node to constellation.ts
- Added roleSkillMappings: UEA → medicines-optimisation + data-analysis; Highworth → data-analysis
- Added constellationLinks with strength values (0.5, 0.3 for UEA; 0.2 for Highworth)
- Added consultation entries for both education entries to consultations.ts (at end of array, maintaining reverse-chronological order)
- Education nodes use type 'role' — treated identically by the constellation layout engine
- Updated role count comment to "6 roles + Education nodes (2)"
- Files changed: src/data/constellation.ts, src/data/consultations.ts
- **Learnings for future iterations:**
- Education entries use type 'role' and follow exact same data shape — no special handling needed
- yScale domain auto-extends from min/max startYear of role-type nodes, so adding 2009 entries extends the timeline automatically
- Education entries have deliberately few skill connections (2 for UEA, 1 for Highworth) per design to keep lower timeline clean
- Consultation entries go at end of array (reverse-chronological: newest first → oldest last)
---
## 2026-02-16 - US-003
- Increased default skill node fill-opacity from 0.2 to 0.35 (initial render + applyGraphHighlight resting state)
- Increased default skill label opacity from 0 to 0.5 (labels now partially visible at rest)
- Increased default link stroke-opacity from 0.08 to 0.15
- Increased active/highlighted skill fill-opacity from 0.85 to 0.9
- Changed unconnected node dimming from 0.06 to 0.15 opacity
- Updated non-active link stroke-opacity in highlighted branch from 0.08 to 0.15
- Changed .pathway-columns desktop grid from 'minmax(0, 1.15fr) minmax(0, 1.5fr)' to 'minmax(0, 1.85fr) minmax(0, 1fr)' — work experience column now ~65%, constellation ~35%
- Files changed: src/components/CareerConstellation.tsx, src/index.css
- Browser verified: skills recognisable at a glance without hovering; work experience column visibly wider; constellation adapts to narrower container without clipping
- **Learnings for future iterations:**
- Initial D3 rendering attributes (set during node/link creation) must stay in sync with applyGraphHighlight resting values — there are TWO places to update for each visual property
- The highlighted branch also has a fallback opacity for non-active links/labels — remember to update those too (3 places total: initial render, resting branch, highlighted branch fallback)
- The constellation ResizeObserver + containerHeight system handles narrower columns automatically — no explicit graph resize code needed
---
## 2026-02-16 - US-004
- Added viewport-proportional scaling: scaleFactor = Math.max(1, Math.min(1.6, viewportWidth / 1440))
- scaleFactor stored in dimensions state alongside width/height, computed in resize useEffect
- Created local `sf` variable in D3 effect (isMobile ? 1 : scaleFactor) to bypass scaling on mobile
- Scaled node sizes: ROLE_WIDTH (104→~166), ROLE_HEIGHT (32→~51), ROLE_RX (16→~26), SKILL_RADIUS_DEFAULT (7→~11), SKILL_RADIUS_ACTIVE (11→~18)
- Scaled font sizes: year labels (10→11 base, scales to ~18), role labels (11→12 base, scales to ~19), skill labels (10→11 base, scales to ~18)
- Scaled spacing: topPadding, bottomPadding, sidePadding, timelineX, roleGap, skillGap, centroid offsets, seeding radius, rightMargin, skillBottomPadding, label dy offset
- Scaled force simulation: charge (-120→~-192 role, -55→~-88 skill), link distance (72→~115), collision radius offset (10→~16 role, 16→~26 skill)
- Scaled accessibility button sizes to match scaled SVG nodes
- Mobile (< 640px) completely bypasses scaling (sf=1), uses MOBILE_ constants unchanged
- Files changed: src/components/CareerConstellation.tsx, Ralph/prd.json, Ralph/progress.txt
- Browser verified at 1440px (sf=1.0, identical to pre-change) and 2560px (sf=1.6, all elements clearly larger and well-proportioned)
- **Learnings for future iterations:**
- Store scaleFactor in the dimensions state object, not a separate ref — keeps it synced with width/height changes
- Use `const sf = isMobile ? 1 : scaleFactor` at top of D3 effect to avoid repeating the mobile guard everywhere
- Every hardcoded pixel value in the D3 effect that relates to element sizing, spacing, or force params needs sf multiplication on desktop path
- Math.round() wraps all scaled values to avoid sub-pixel rendering artifacts
- Accessibility overlay buttons in the React JSX also need scaling — they use base constants directly, not the D3-scoped variables
---
## 2026-02-16 - US-005
- Changed mouseenter handler: on desktop (supportsCoarsePointer === false), calls applyGraphHighlight(d.id) + onNodeHover(d.id) for hover-to-highlight
- Changed mouseleave handler: resets to highlightedNodeId ?? null (external timeline state or resting), NOT pinnedNodeId
- Changed click handler: desktop clicks only fire detail callbacks (onRoleClick/onSkillClick), no pin toggle
- Touch (coarse pointer) retains tap-to-pin toggle unchanged inside click handler
- pinnedNodeId state only set/cleared for touch interactions
- Files changed: src/components/CareerConstellation.tsx
- Browser verified: hover on "Interim Head" → 12 connected skills at fill-opacity 0.9, 9 dimmed at opacity 0.15; hover off → all reset to resting (fill-opacity 0.35, label opacity 0.5); desktop click → no pin state
- **Learnings for future iterations:**
- D3 mouseenter/mouseleave events require dispatchEvent() in Playwright headless — native page.hover() on SVG <g> elements doesn't reliably trigger D3 handlers
- Role rect fill-opacity 0.12 IS the resting state (initialized at line 384), not a dimmed state — don't confuse with skill resting at 0.35
- mouseleave should reset to highlightedNodeId (external prop) not pinnedNodeId — on desktop there is no pin, so fallback is null (resting)
- The supportsCoarsePointer guard at top of each handler cleanly separates desktop/touch paths without duplicating the handler
---
## 2026-02-16 - US-006
- Added mobile accordion expansion below constellation SVG for role details on tap
- Accordion shows role title, organisation, duration, and top 3 examination items by default
- "Show more" button reveals full examination and plan arrays (only appears when >3 examination items)
- Tapping a different role switches accordion content and auto-collapses "show more" (via useEffect on pinnedNodeId)
- Tapping the same role again or tapping empty SVG background collapses accordion and resets highlights
- Added click handler on SVG background rect (`.bg-rect`) to clear pinnedNodeId on coarse pointer
- Accordion uses Framer Motion AnimatePresence with height 0→auto, 200ms ease-out (matches tile expansion pattern)
- Accordion hidden entirely on desktop (fine pointer) via supportsCoarsePointer guard
- Skill node taps do not open accordion — only role nodes (filtered by `n.type === 'role'`)
- Legend hint text changes to "Tap to explore connections" on coarse pointer devices
- Files changed: src/components/CareerConstellation.tsx, Ralph/prd.json, Ralph/progress.txt
- **Learnings for future iterations:**
- The SVG background rect must have a class (`.bg-rect`) for later selection — D3 event handlers on SVG elements created early in the useEffect can reference functions defined later by selecting the element after the function is defined
- pinnedNodeId is local to CareerConstellation — it's not passed to DashboardLayout. The accordion relies on this internal state
- Framer Motion `key` prop on motion.div enables smooth exit→enter transitions when switching between different roles (AnimatePresence exits the old key, enters the new)
- `accordionShowMore` state must reset on pinnedNodeId change to auto-collapse "show more" when switching roles
- Not all consultations have >3 examination items — the "Show more" button only renders conditionally, and plan items are only shown when expanded
- Browser testing for coarse pointer features requires touch emulation — Playwright's default Chromium reports fine pointer, so the accordion won't appear without explicit touch device emulation
---
## 2026-02-16 - US-007
- Created hexToRgba(hex, opacity) helper function in both WorkExperienceSubsection.tsx and DashboardLayout.tsx
- WorkExperienceSubsection.tsx: replaced all hardcoded teal/accent colour references with consultation.orgColor:
- Dot indicator: '#0D6E6E' → consultation.orgColor
- Highlight background: 'rgba(10,128,128,0.03)' → hexToRgba(orgColor, 0.03)
- Expanded/highlighted border: 'var(--accent-border)' → hexToRgba(orgColor, 0.2)
- Hover border: 'var(--accent-border)' → hexToRgba(orgColor, 0.2)
- Left border on expanded detail: 'var(--accent)' → orgColor
- Bullet dots: 'var(--accent)' → orgColor at 0.5 opacity
- Coded entry tags: bg hexToRgba(orgColor, 0.08), text orgColor, border hexToRgba(orgColor, 0.2)
- "View full record" link: 'var(--accent)' → orgColor, hover uses opacity 0.7 instead of accent-hover
- DashboardLayout.tsx LastConsultationSubsection: same pattern applied:
- Highlight border/bg, hover bg, role title, bullet dots, "View full record" link all use consultation.orgColor
- CardHeader dot for "WORK EXPERIENCE" section title remains teal (unchanged)
- Files changed: src/components/WorkExperienceSubsection.tsx, src/components/DashboardLayout.tsx, Ralph/prd.json, Ralph/progress.txt
- Browser verified: NHS roles show blue dots/borders, Tesco roles show red, Paydens shows green, education shows purple. Expanded Tesco card shows red left border, red bullet dots, and red-tinted coded entries
- **Learnings for future iterations:**
- consultation.orgColor exists on every Consultation object — it's the single source for per-employer colour throughout the UI
- hexToRgba(hex, opacity) is needed in both WorkExperienceSubsection.tsx and DashboardLayout.tsx — not extracted to a shared utility since it's a small helper and only used in two files
- For hover effects on org-coloured links, use opacity change (0.7) instead of a separate --accent-hover variable, since each employer has a different base colour
- The hover mouseenter/mouseleave pattern using parentElement!.style is used for border/shadow effects — it directly mutates the parent wrapper's inline styles
---
## 2026-02-16 - US-008
- Re-tuned force simulation parameters for 8 entries (6 roles + 2 education) spanning 2009-2025 in ~35% column
- Increased MOBILE_FALLBACK_HEIGHT from 380 to 520 — 8 entries over 17 years need more vertical space on mobile
- Reduced desktop sidePadding from 56*sf to 36*sf — frees horizontal space for skill nodes in narrow column
- Reduced desktop roleGap from 80*sf to 56*sf — roles sit closer to timeline, more room for skills
- Reduced desktop skillGap from 40*sf to 28*sf — skills start sooner after role pills
- Reduced skill centroid offset from 60*sf to 40*sf — skills pulled closer to avoid right-edge overflow
- Reduced skill seed radius from 50*sf to 35*sf — tighter initial positioning
- Increased mobile charge: roles -80→-100, skills -35→-45 — stronger repulsion for better separation
- Increased mobile link distance from 48 to 56 — more space between connected nodes
- Increased mobile collision padding: roles 6→8, skills 10→14 — better overlap prevention
- Increased collision iterations from 2 to 3 — more passes for cleaner overlap resolution
- Increased skill forceX strength from 0.18 to 0.25 — pulls skills more towards center of available space
- Increased desktop rightMargin from 40*sf to 32*sf — moderate boundary for skill labels
- Added width-aware skill label truncation: maxLen 12 when SVG width < 500px (vs 16 at wider)
- Increased mobile topPadding 32→36, bottomPadding 32→40 — breathing room at edges
- Files changed: src/components/CareerConstellation.tsx, Ralph/prd.json, Ralph/progress.txt
- Browser verified at 375px: all 8 entries visible, correct chronological order, acceptable overlap for mobile
- Browser verified at 430px: better horizontal distribution, roles well-positioned
- Browser verified at 1440px: roles cleanly positioned along timeline, skill labels slightly clipped at right edge (container overflow:hidden), circles fully visible
- Browser verified at 2560px: excellent distribution, all labels visible, education nodes cleanly isolated at bottom
- **Learnings for future iterations:**
- MOBILE_FALLBACK_HEIGHT must scale with the number of timeline entries — 380px was adequate for 4 entries but not for 8
- At 1440px, the ~340px column is fundamentally narrow for 21 skill nodes + labels. Some label clipping via overflow:hidden is an acceptable trade-off — circles are visible and labels show fully on hover
- Mobile role positioning drifts 1-2 years from exact position due to collision forces pushing close entries apart (2015-2017 has 3 entries). Chronological order is maintained, which is the priority
- collision.iterations(3) significantly improves overlap prevention over iterations(2) with 29 total nodes
- Skill forceX strength 0.25 (up from 0.18) keeps skills more centred in available space without over-constraining them
- The width < 500 check for skill label truncation targets the narrow desktop column specifically — mobile already uses its own 12-char max
---
+53
View File
@@ -0,0 +1,53 @@
# Significant Interventions Carousel (Ralph Prompt)
## Goal
Replace the current one-column **Active Projects** list with a **Significant Interventions** carousel that supports thumbnail cards and auto-scroll behavior (Embla-based), while preserving panel-open behavior on card click.
## Scope
- Rename all relevant UI/content references from **Active Projects** to **Significant Interventions**.
- Replace `ProjectsTile` list layout with an Embla carousel.
- Use auto-scroll as the default carousel behavior.
- Keep room for thumbnails now; real thumbnail assets will be added later.
## Implementation Task List
- [ ] Install carousel dependencies:
- `embla-carousel-react`
- `embla-carousel-autoplay`
- [ ] Update tile heading in `src/components/tiles/ProjectsTile.tsx`:
- `ACTIVE PROJECTS` -> `SIGNIFICANT INTERVENTIONS`
- [ ] Refactor `ProjectsTile` in `src/components/tiles/ProjectsTile.tsx`:
- Replace vertical list container with Embla viewport/container/slides
- Convert each project item to a carousel slide card
- Add thumbnail region in each slide (use placeholder block/image container for now)
- Keep keyboard activation (`Enter`/`Space`) and click-to-open detail panel
- [ ] Implement auto-scroll behavior:
- Use Embla autoplay plugin with sensible defaults (continuous feel, pauses on hover/focus)
- Respect reduced motion (`prefers-reduced-motion`) by disabling autoplay
- [ ] Responsive behavior:
- Mobile: single-card view
- Tablet/Desktop: multi-card visible area (based on available width)
- Ensure overflow clipping and smooth transitions
- [ ] Update navigation/search labels to match naming:
- `src/components/SubNav.tsx`: `Projects` -> `Significant Interventions`
- `src/lib/search.ts`: `Active Projects` -> `Significant Interventions` (section type and related labels/comments)
- [ ] Keep detail panel integration unchanged:
- Clicking a carousel card still calls `openPanel({ type: 'project', investigation: project })`
- [ ] Styling pass:
- Align with current dashboard tokens (`--surface`, `--border-light`, `--accent`, etc.)
- Ensure cards remain readable without thumbnails
## Acceptance Criteria
- The dashboard section title displays **Significant Interventions**.
- The old one-column projects list is replaced by a working carousel.
- Carousel auto-scrolls by default and pauses appropriately on interaction.
- In reduced-motion environments, carousel does not auto-scroll.
- Clicking or keyboard-activating a card opens the existing project detail panel.
- Layout works on mobile and desktop without overflow bugs.
- Search/navigation language no longer references **Active Projects**.
## Notes for Implementation
- Thumbnail assets are intentionally deferred; implement with placeholders now.
- Keep the component name `ProjectsTile` for this pass to minimize refactor risk; rename component/file in a later cleanup task if desired.