Supporting info for login screen rework
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
{
|
||||
"project": "Portfolio — Dashboard Restructure & Graph Improvements",
|
||||
"branchName": "ralph/dashboard-restructure",
|
||||
"description": "Restructure the dashboard into two parent sections (Patient Summary, Patient Pathway), improve constellation graph clarity, add hover-highlighting between experience/skills and graph, remove inaccurate CV data, and explore parent header typography.",
|
||||
"userStories": [
|
||||
{
|
||||
"id": "US-001",
|
||||
"title": "Skip boot/login sequence for dev iteration",
|
||||
"description": "As a developer, I want to skip the boot/ECG/login animation during this feature branch so I can iterate on the dashboard quickly.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/App.tsx, change the initial Phase state from 'boot' to 'pmr' so the app loads directly to the dashboard",
|
||||
"The boot, ECG, and login phases are still present in code — only the initial state changes",
|
||||
"App loads directly to the dashboard layout on refresh",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 1,
|
||||
"passes": true,
|
||||
"notes": "Temporary — final story reverts this."
|
||||
},
|
||||
{
|
||||
"id": "US-002",
|
||||
"title": "Remove inaccurate CV data from consultations and constellation",
|
||||
"description": "As Andy, I want only real career entries so the portfolio doesn't contain fabricated content.",
|
||||
"acceptanceCriteria": [
|
||||
"Remove the 'duty-pharmacist-2016' entry from src/data/consultations.ts — this role is not in References/CV_v4.md",
|
||||
"Remove the corresponding role node for duty-pharmacist-2016 from src/data/constellation.ts constellationNodes array",
|
||||
"Remove all links referencing duty-pharmacist-2016 from constellationLinks array",
|
||||
"Remove duty-pharmacist-2016 from roleSkillMappings",
|
||||
"Verify 4 roles remain in consultations.ts: interim-head-2025, deputy-head-2024, high-cost-drugs-2022, pharmacy-manager-2017",
|
||||
"Verify 4 role nodes remain in constellation.ts",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 2,
|
||||
"passes": true,
|
||||
"notes": "Check References/CV_v4.md for the accurate role list. The CV has 4 roles: Interim Head, Deputy Head, High-Cost Drugs, Pharmacy Manager (Tesco)."
|
||||
},
|
||||
{
|
||||
"id": "US-003",
|
||||
"title": "Fix inaccurate timeline entries in CareerActivityTile",
|
||||
"description": "As Andy, I want the career timeline to only show real certifications and remove fabricated project entries.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/components/tiles/CareerActivityTile.tsx buildTimeline() function, remove the 'Power BI Data Analyst Associate' certification entry — not in CV",
|
||||
"Remove the 'Clinical Pharmacy Diploma' certification entry — not in CV",
|
||||
"Add 'NHS Leadership Academy — Mary Seacole Programme' as a certification entry with date '2018' and meta 'NHS leadership qualification'",
|
||||
"Remove the 'SQL Analytics Transformation' project entry — not a standalone project in the CV",
|
||||
"Remove the 'Budget Oversight' project entry — budget management is a skill, not a project",
|
||||
"Add A-Levels entry: title 'A-Levels: Mathematics (A*), Chemistry (B), Politics (C)', meta 'Highworth Grammar School', date '2009–2011', type 'edu'",
|
||||
"Verify remaining timeline matches CV_v4.md: 4 roles + GPhC Registration + MPharm + Mary Seacole + A-Levels",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 3,
|
||||
"passes": true,
|
||||
"notes": "Reference CV at References/CV_v4.md. The role entries in the timeline reference consultations by consultationId — since duty-pharmacist-2016 was removed in US-002, also remove any timeline entry referencing it."
|
||||
},
|
||||
{
|
||||
"id": "US-004",
|
||||
"title": "Create ParentSection component for hierarchical layout",
|
||||
"description": "As a developer, I need a parent section wrapper component that visually distinguishes top-level sections from child subsections.",
|
||||
"acceptanceCriteria": [
|
||||
"Create src/components/ParentSection.tsx with props: title (string), children (ReactNode), optional className",
|
||||
"Parent section renders as a Card (using existing Card component) spanning full width",
|
||||
"Header text is large and prominent — at minimum 2.4rem (36px at 1920px) — clearly a top-level section marker, not a small label",
|
||||
"Header uses font-ui (Elvaro Grotesque) at weight 600-700",
|
||||
"Header text color is pmr-text-primary (#1A2B2A)",
|
||||
"No colored dot on parent headers (dots are for subsections only)",
|
||||
"20px (1.333rem) padding below header before children content",
|
||||
"Component accepts children which will be the subsections",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 4,
|
||||
"passes": true,
|
||||
"notes": "This is a new component — not modifying Card.tsx. ParentSection wraps Card and adds the large header treatment. Subsection headers continue to use the existing CardHeader style (12px uppercase with colored dot)."
|
||||
},
|
||||
{
|
||||
"id": "US-005",
|
||||
"title": "Restructure Patient Summary as parent section with Latest Results subsection",
|
||||
"description": "As a visitor, I want Patient Summary to contain the profile and Latest Results as a subsection.",
|
||||
"acceptanceCriteria": [
|
||||
"In DashboardLayout.tsx, replace the standalone PatientSummaryTile and LatestResultsTile with a single ParentSection titled 'Patient Summary'",
|
||||
"Inside the ParentSection, render the profile text (from PatientSummaryTile) first",
|
||||
"Remove the 4 headline metric figures (9+ Years, 1.2M, £220M, £14.6M+) that are currently in PatientSummaryTile — these are redundant with the KPIs",
|
||||
"Below the profile text, render Latest Results content as a subsection with its own CardHeader-style header ('LATEST RESULTS' with teal dot)",
|
||||
"KPI flip cards retain their existing click-to-detail behaviour",
|
||||
"The standalone LatestResultsTile import is removed from DashboardLayout",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 5,
|
||||
"passes": true,
|
||||
"notes": "The Latest Results subsection should look like a section within the parent card — use a CardHeader for 'LATEST RESULTS' and render the KPI grid below it. The parent ParentSection header 'Patient Summary' should be visually dominant."
|
||||
},
|
||||
{
|
||||
"id": "US-006",
|
||||
"title": "Create Patient Pathway parent section with constellation graph",
|
||||
"description": "As a visitor, I want a 'Patient Pathway' parent section that contains the constellation graph at the top.",
|
||||
"acceptanceCriteria": [
|
||||
"In DashboardLayout.tsx, replace the standalone CareerActivityTile with a ParentSection titled 'Patient Pathway'",
|
||||
"The constellation graph (CareerConstellation component) renders at the top of the Patient Pathway section",
|
||||
"The CareerConstellation receives onRoleClick and onSkillClick handlers (same as current CareerActivityTile)",
|
||||
"The standalone CareerActivityTile import is removed from DashboardLayout",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 6,
|
||||
"passes": true,
|
||||
"notes": "This story just sets up the parent section with the graph. The Last Consultation, experience/skills columns, and education are added in subsequent stories."
|
||||
},
|
||||
{
|
||||
"id": "US-007",
|
||||
"title": "Move Last Consultation into Patient Pathway as subsection",
|
||||
"description": "As a visitor, I want the most recent role details to appear within Patient Pathway below the graph.",
|
||||
"acceptanceCriteria": [
|
||||
"Inside the Patient Pathway ParentSection (below the constellation graph), add a 'LAST CONSULTATION' subsection with green dot CardHeader",
|
||||
"Render the last consultation content: date, organisation, type, band, role title, examination bullets — same content as the current LastConsultationTile",
|
||||
"The standalone LastConsultationTile is removed from DashboardLayout grid",
|
||||
"The LastConsultationTile.tsx file can be deleted (content is now inline in the Patient Pathway section)",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 7,
|
||||
"passes": true,
|
||||
"notes": "Reuse the rendering logic from LastConsultationTile — either inline it or extract a shared sub-component. The important thing is it lives inside the Patient Pathway ParentSection now."
|
||||
},
|
||||
{
|
||||
"id": "US-008",
|
||||
"title": "Add two-column experience and skills layout in Patient Pathway",
|
||||
"description": "As a visitor, I want work experience on the left and skills on the right within Patient Pathway.",
|
||||
"acceptanceCriteria": [
|
||||
"Below the Last Consultation subsection in Patient Pathway, add a two-column CSS grid layout",
|
||||
"Left column: subsection header 'WORK EXPERIENCE' (teal dot), lists all 4 roles from consultations.ts with accordion expand (one at a time)",
|
||||
"Right column: subsection header 'REPEAT MEDICATIONS' (amber dot), shows categorised skills with expand — same content as CoreSkillsTile",
|
||||
"Each role entry shows: role title, organisation, date range. Click to expand shows examination bullets and coded entries",
|
||||
"On mobile (below md/768px), columns stack vertically: experience above skills",
|
||||
"Grid gap matches the dashboard grid gap (16px / 1.067rem)",
|
||||
"The standalone CoreSkillsTile is removed from DashboardLayout grid",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 8,
|
||||
"passes": true,
|
||||
"notes": "Reuse expansion patterns from existing CareerActivityTile and CoreSkillsTile. The two-column layout is within the Patient Pathway ParentSection card, not separate cards."
|
||||
},
|
||||
{
|
||||
"id": "US-009",
|
||||
"title": "Move Education into Patient Pathway as subsection",
|
||||
"description": "As a visitor, I want education entries at the bottom of Patient Pathway so all career-related info is in one place.",
|
||||
"acceptanceCriteria": [
|
||||
"Below the two-column experience/skills grid in Patient Pathway, add an 'EDUCATION' subsection with purple dot CardHeader",
|
||||
"Render education entries: MPharm, Mary Seacole Programme, A-Levels (same content as EducationTile plus A-Levels added in US-003)",
|
||||
"The standalone EducationTile is removed from DashboardLayout grid",
|
||||
"The EducationTile.tsx file can be deleted (content now in Patient Pathway)",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 9,
|
||||
"passes": true,
|
||||
"notes": "Education is the bottom-most subsection in Patient Pathway. Include A-Levels: Mathematics (A*), Chemistry (B), Politics (C) — Highworth Grammar School, 2009–2011."
|
||||
},
|
||||
{
|
||||
"id": "US-010",
|
||||
"title": "Clean up removed standalone tiles and verify layout",
|
||||
"description": "As a developer, I need to remove orphaned tile components and verify the dashboard grid has no gaps.",
|
||||
"acceptanceCriteria": [
|
||||
"Delete src/components/tiles/LastConsultationTile.tsx if not already deleted",
|
||||
"Delete src/components/tiles/CoreSkillsTile.tsx if not already deleted",
|
||||
"Delete src/components/tiles/LatestResultsTile.tsx if not already deleted",
|
||||
"Delete src/components/tiles/EducationTile.tsx if not already deleted",
|
||||
"Delete src/components/tiles/CareerActivityTile.tsx if not already deleted",
|
||||
"DashboardLayout grid contains only: PatientSummary ParentSection (full width) + Patient Pathway ParentSection (full width) + ProjectsTile (if it remains)",
|
||||
"No broken imports or unused imports remain",
|
||||
"No visual gaps in the dashboard grid",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 10,
|
||||
"passes": true,
|
||||
"notes": "ProjectsTile may remain as a standalone tile or be absorbed — check if it still makes sense as standalone. If so, keep it. The key outcome is that the deleted tiles have no remaining references."
|
||||
},
|
||||
{
|
||||
"id": "US-011",
|
||||
"title": "Improve constellation graph visual clarity",
|
||||
"description": "As a visitor, I want the graph to be clearer with better contrast, larger nodes, and an off-white background.",
|
||||
"acceptanceCriteria": [
|
||||
"Graph container has an off-white background (e.g. #F5F7F6 or similar warm neutral) — can use the existing radial gradient approach but with a more visible base colour",
|
||||
"Link lines use slightly thicker stroke (from 1px to 1.5-2px) and higher contrast colour (darker than current #D4E0DE at 0.3 opacity)",
|
||||
"Role node radius increased from 24px to at least 30px",
|
||||
"Skill node radius increased from 10px to at least 14px",
|
||||
"Skill label font size increased from 9px to 10-11px for readability",
|
||||
"Graph is initially scaled/zoomed so nodes fill the container at a comfortable viewing size — adjust force simulation parameters if needed (reduce charge repulsion, adjust link distance)",
|
||||
"All existing interactions preserved (hover dim/highlight, click, keyboard nav)",
|
||||
"Responsive height tiers still work (400/300/250px)",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 11,
|
||||
"passes": true,
|
||||
"notes": "The key issue is readability — the current graph is too sparse/faint. Larger nodes, thicker links, and stronger colours will help. The background should provide subtle contrast so the white card surface feels like the graph 'lives somewhere'."
|
||||
},
|
||||
{
|
||||
"id": "US-012",
|
||||
"title": "Add hover-highlighting between experience/skills and constellation graph",
|
||||
"description": "As a visitor, I want to hover over an experience or skill entry and see the corresponding node highlighted in the graph.",
|
||||
"acceptanceCriteria": [
|
||||
"Add a highlightedNodeId state (string | null) to the Patient Pathway parent component",
|
||||
"Pass highlightedNodeId as a prop to CareerConstellation",
|
||||
"CareerConstellation applies its existing hover highlight logic when highlightedNodeId changes (dim non-connected nodes, brighten connected links)",
|
||||
"When hovering a work experience entry, set highlightedNodeId to the corresponding consultation ID (which maps to a role node)",
|
||||
"When hovering a skill entry, set highlightedNodeId to the skill ID",
|
||||
"Highlight clears when mouse leaves the entry (set highlightedNodeId to null)",
|
||||
"On touch devices: tap to highlight, tap elsewhere to clear",
|
||||
"Highlighting feels immediate — no perceptible delay",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 12,
|
||||
"passes": true,
|
||||
"notes": "CareerConstellation already has hover logic that dims non-connected nodes. The new prop should trigger the same visual effect but from an external source. Use the existing adjacency map and opacity/stroke manipulation."
|
||||
},
|
||||
{
|
||||
"id": "US-013",
|
||||
"title": "Update command palette data for restructured dashboard",
|
||||
"description": "As a developer, I need the command palette search index to reflect the new section structure.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/lib/search.ts, update buildPaletteData() to reference new tile IDs (patient-summary, patient-pathway instead of old tile IDs)",
|
||||
"Scroll actions target the correct data-tile-id values for the new ParentSection components",
|
||||
"Skills palette items still open detail panels correctly",
|
||||
"Experience items still open detail panels correctly",
|
||||
"Remove any palette entries that reference deleted tiles (LastConsultation standalone, etc.)",
|
||||
"Remove palette entries for deleted certifications (Power BI, Clinical Diploma) and projects (SQL Analytics Transformation, Budget Oversight)",
|
||||
"Add palette entry for Mary Seacole Programme",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 13,
|
||||
"passes": true,
|
||||
"notes": "The palette data model uses tileId for scroll targeting — these need to match the new data-tile-id attributes on ParentSection components."
|
||||
},
|
||||
{
|
||||
"id": "US-014",
|
||||
"title": "Responsive verification and fixes",
|
||||
"description": "As a developer, I need to verify the restructured dashboard works at all viewport sizes.",
|
||||
"acceptanceCriteria": [
|
||||
"At 375px (mobile): single column, Patient Summary and Patient Pathway stack vertically, experience/skills columns stack, graph fits container, all text wraps",
|
||||
"At 768px (tablet): single column, comfortable spacing",
|
||||
"At 1024px (desktop): full layout, two-column experience/skills grid visible, sidebar inline",
|
||||
"At 1920px: layout visually balanced, graph nodes and labels readable",
|
||||
"No horizontal scrollbar at any tested width",
|
||||
"Parent section headers scale appropriately across breakpoints",
|
||||
"Fix any issues discovered during verification",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill at 375px, 768px, 1024px, and 1920px widths"
|
||||
],
|
||||
"priority": 14,
|
||||
"passes": true,
|
||||
"notes": "Use Playwright to resize browser and take snapshots at each breakpoint. Fix any overflow, wrapping, or spacing issues found."
|
||||
},
|
||||
{
|
||||
"id": "US-015",
|
||||
"title": "Explore parent header typography options",
|
||||
"description": "As a designer, I want to evaluate different font treatments for parent section headers to find the most visually striking option.",
|
||||
"acceptanceCriteria": [
|
||||
"Test Patient Summary and Patient Pathway headers with Elvaro Grotesque at weights 300, 400, 500, 600, 700, 900",
|
||||
"Test headers with Blumir variable font at weights 100, 300, 500, 700",
|
||||
"Test at sizes from 2rem (30px) up to 3.2rem (48px)",
|
||||
"Consider uppercase vs title case vs sentence case for parent headers",
|
||||
"Take screenshots of at least 3 different options for comparison",
|
||||
"Headers must complement the clinical/luxury aesthetic — premium and intentional, not generic",
|
||||
"Select the best option based on visual hierarchy, readability, and design coherence",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 15,
|
||||
"passes": true,
|
||||
"notes": "Explored 14 options across Elvaro (300-900) and Blumir (100-700), title case vs uppercase, sizes 1.4-3rem, various tracking. Selected: Elvaro Grotesque 600, 2.2rem, title case, -0.02em tracking. Rationale: font consistency, tight tracking feels premium, slightly reduced size is more data-dense/clinical, clear hierarchy above sub-headers."
|
||||
},
|
||||
{
|
||||
"id": "US-016",
|
||||
"title": "Apply chosen parent header typography",
|
||||
"description": "As a developer, I need to apply the selected font treatment to both parent section headers.",
|
||||
"acceptanceCriteria": [
|
||||
"Apply the chosen font family, weight, size, and case to the ParentSection component header",
|
||||
"Both Patient Summary and Patient Pathway headers use identical treatment",
|
||||
"Font scales appropriately across breakpoints (may need responsive size adjustments)",
|
||||
"Headers create clear visual hierarchy — unmistakably top-level sections",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 16,
|
||||
"passes": true,
|
||||
"notes": "Applied Elvaro Grotesque 600, -0.02em tracking with responsive scale: 1.375rem (mobile) → 1.6rem (sm) → 1.8rem (md) → 2.2rem (lg). Both headers identical. Verified at 375px (22px) and 1024px+ (35.2px)."
|
||||
},
|
||||
{
|
||||
"id": "US-017",
|
||||
"title": "Re-enable boot/login sequence",
|
||||
"description": "As a user, I want the full boot → ECG → login experience restored for production.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/App.tsx, change the initial Phase state back from 'pmr' to 'boot'",
|
||||
"Boot → ECG → Login → Dashboard sequence works end to end",
|
||||
"No other changes to App.tsx beyond reverting the initial state",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill: app starts at boot, progresses through ECG and login, arrives at restructured dashboard"
|
||||
],
|
||||
"priority": 17,
|
||||
"passes": true,
|
||||
"notes": "Reverted initial phase from 'pmr' to 'boot'. Full flow verified: boot → ECG → login (credentials auto-typed) → click Log In → restructured dashboard loads with all sections."
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,238 @@
|
||||
{
|
||||
"project": "Portfolio — Responsive Scaling & Mobile Layout",
|
||||
"branchName": "ralph/responsive-scaling",
|
||||
"description": "Make the GP system dashboard scale proportionally on large screens (1440p/4K) via fluid root font-size, and fix mobile layout with a sidebar drawer, text wrapping, and D3 chart responsiveness. Dashboard phase only.",
|
||||
"userStories": [
|
||||
{
|
||||
"id": "US-001",
|
||||
"title": "Skip boot/login sequence for dev iteration",
|
||||
"description": "As a developer, I want to skip the boot/ECG/login animation during this feature branch so I can iterate on the dashboard quickly without waiting 10+ seconds each reload.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/App.tsx, change the initial Phase state from 'boot' to 'pmr' so the app loads directly to the dashboard",
|
||||
"The boot, ECG, and login phases are still present in code — only the initial state changes",
|
||||
"App loads directly to the dashboard layout on refresh",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 1,
|
||||
"passes": true,
|
||||
"notes": "This is temporary — US-013 reverts this change."
|
||||
},
|
||||
{
|
||||
"id": "US-002",
|
||||
"title": "Add fluid root font-size scaling and convert CSS custom properties to rem",
|
||||
"description": "As a user on a high-resolution display, I want the dashboard to scale proportionally so text and UI elements are comfortably sized without manual browser zoom.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/index.css, add a font-size rule on the `html` element using clamp() that: stays at 15px below 1920px, scales linearly from 15px at 1920px to ~22px at 3840px",
|
||||
"The clamp formula should be: clamp(15px, calc(10px + 0.26vw), 22px) or equivalent that hits ~15px at 1920vw and ~22px at 3840vw — tune the exact values to hit these targets",
|
||||
"Remove or update the existing `body { font-size: 15px }` to use rem or inherit from html",
|
||||
"Convert CSS custom property --topbar-height from 48px to 3.2rem",
|
||||
"Convert CSS custom property --sidebar-width from 272px to 18.133rem",
|
||||
"Convert CSS custom property --subnav-height from 36px to 2.4rem",
|
||||
"Any other layout CSS custom properties using px should be converted to rem (divide px by 15)",
|
||||
"At 1920px viewport, computed values should match current px values exactly",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 2,
|
||||
"passes": true,
|
||||
"notes": "rem base: 15px = 1rem. Conversion: px / 15 = rem. Tailwind classes already use rem so they will auto-scale. Only inline px styles and CSS custom properties need manual conversion."
|
||||
},
|
||||
{
|
||||
"id": "US-003",
|
||||
"title": "Convert DashboardLayout, Card, SubNav, and DetailPanel to rem",
|
||||
"description": "As a developer, I need the layout skeleton components to use rem so they scale with the root font-size.",
|
||||
"acceptanceCriteria": [
|
||||
"In DashboardLayout.tsx, convert all inline style px values (padding, margin, gap, width, height, calc expressions) to rem",
|
||||
"In Card.tsx, convert padding, border-radius if inline, and any other px values to rem",
|
||||
"In SubNav.tsx, convert all inline px values to rem",
|
||||
"In DetailPanel.tsx, convert all inline px values (width, padding, transforms) to rem",
|
||||
"Tailwind classes (p-4, gap-4, etc.) can stay as-is — they already use rem",
|
||||
"Layout looks identical at 1920px viewport width",
|
||||
"Typecheck passes",
|
||||
"Use Playwright MCP browser_snapshot at 1920px width to verify layout matches current appearance"
|
||||
],
|
||||
"priority": 3,
|
||||
"passes": true,
|
||||
"notes": "Conversion: px / 15 = rem. e.g., 20px = 1.333rem, 16px = 1.067rem, 12px = 0.8rem. Border-radius can stay in px."
|
||||
},
|
||||
{
|
||||
"id": "US-004",
|
||||
"title": "Convert TopBar and Sidebar to rem",
|
||||
"description": "As a developer, I need the navigation chrome (TopBar + Sidebar) to scale with root font-size.",
|
||||
"acceptanceCriteria": [
|
||||
"In TopBar.tsx, convert all inline fontSize, padding, margin, gap, width, height, and minWidth/maxWidth px values to rem",
|
||||
"In Sidebar.tsx, convert all inline px values to rem",
|
||||
"Any child components rendered inline within Sidebar (PersonHeader section, Tags, Alerts) that use inline px styles should also be converted",
|
||||
"Search bar min-width/max-width converted to rem",
|
||||
"Layout looks identical at 1920px viewport width",
|
||||
"Typecheck passes",
|
||||
"Use Playwright MCP browser_snapshot at 1920px width to verify TopBar and Sidebar appearance"
|
||||
],
|
||||
"priority": 4,
|
||||
"passes": true,
|
||||
"notes": "Conversion: px / 15 = rem. Border-radius can stay in px."
|
||||
},
|
||||
{
|
||||
"id": "US-005",
|
||||
"title": "Convert PatientSummaryTile, LastConsultationTile, and CareerActivityTile to rem",
|
||||
"description": "As a developer, I need the full-width tile components to use rem for all inline px values so they scale with viewport.",
|
||||
"acceptanceCriteria": [
|
||||
"In PatientSummaryTile.tsx, convert all inline fontSize, padding, margin, gap, and dimension px values to rem",
|
||||
"In LastConsultationTile.tsx, convert all inline px values to rem",
|
||||
"In CareerActivityTile.tsx, convert all inline px values to rem",
|
||||
"In ConsultationDetail.tsx (detail panel for career), convert all inline px values to rem",
|
||||
"Tiles look identical at 1920px viewport width",
|
||||
"Typecheck passes",
|
||||
"Use Playwright MCP browser_snapshot at 1920px width to verify tile appearance"
|
||||
],
|
||||
"priority": 5,
|
||||
"passes": true,
|
||||
"notes": "Conversion: px / 15 = rem. Border-radius can stay in px. These are full-width tiles in the dashboard grid."
|
||||
},
|
||||
{
|
||||
"id": "US-006",
|
||||
"title": "Convert CoreSkillsTile, LatestResultsTile, EducationTile, and ProjectsTile to rem",
|
||||
"description": "As a developer, I need the remaining tile components and their detail panels to use rem for all inline px values.",
|
||||
"acceptanceCriteria": [
|
||||
"In CoreSkillsTile.tsx, convert all inline px values to rem",
|
||||
"In LatestResultsTile.tsx (KPI flip cards), convert all inline px values to rem",
|
||||
"In EducationTile.tsx, convert all inline px values to rem",
|
||||
"In ProjectsTile.tsx, convert all inline px values to rem",
|
||||
"In SkillDetail.tsx, SkillsAllDetail.tsx, KPIDetail.tsx, EducationDetail.tsx, and ProjectDetail.tsx — convert all inline px values to rem",
|
||||
"Tiles look identical at 1920px viewport width",
|
||||
"Typecheck passes",
|
||||
"Use Playwright MCP browser_snapshot at 1920px width to verify tile appearance"
|
||||
],
|
||||
"priority": 6,
|
||||
"passes": true,
|
||||
"notes": "Conversion: px / 15 = rem. Border-radius can stay in px. This story covers 4 tiles + 5 detail components — the work is mechanical/repetitive."
|
||||
},
|
||||
{
|
||||
"id": "US-007",
|
||||
"title": "Add mobile sidebar drawer with hamburger toggle",
|
||||
"description": "As a mobile user, I want to access the sidebar content (person details, tags, alerts) via a slide-out drawer triggered by a hamburger icon in the TopBar.",
|
||||
"acceptanceCriteria": [
|
||||
"A hamburger menu icon (lucide-react Menu icon) appears in the TopBar on screens below lg breakpoint (1024px)",
|
||||
"The hamburger icon is hidden on lg+ screens (sidebar is inline on desktop)",
|
||||
"Tapping the hamburger icon opens the Sidebar as a slide-out drawer overlay from the left",
|
||||
"Drawer includes a semi-transparent backdrop that closes the drawer when tapped",
|
||||
"Drawer contains the full Sidebar content (PersonHeader, Tags, Alerts)",
|
||||
"Drawer has a close button (lucide-react X icon) in its top-right corner",
|
||||
"Pressing Escape closes the drawer",
|
||||
"Drawer animation: translateX(-100%) to translateX(0), 200ms ease-out",
|
||||
"Drawer respects prefers-reduced-motion (skip animation, show/hide instantly)",
|
||||
"Sidebar remains inline (not a drawer) on lg+ screens — no desktop behavior change",
|
||||
"Drawer state managed via useState in DashboardLayout.tsx",
|
||||
"Typecheck passes",
|
||||
"Use Playwright MCP to verify: at 375px width, hamburger visible, click opens drawer with sidebar content, click backdrop closes it"
|
||||
],
|
||||
"priority": 7,
|
||||
"passes": true,
|
||||
"notes": "The drawer component can be built into DashboardLayout or as a separate MobileSidebarDrawer component. Keep it simple."
|
||||
},
|
||||
{
|
||||
"id": "US-008",
|
||||
"title": "Fix text wrapping and overflow across all tiles",
|
||||
"description": "As a mobile user, I want all text content to wrap properly within containers so nothing is cut off or requires horizontal scrolling.",
|
||||
"acceptanceCriteria": [
|
||||
"Add overflow-wrap: break-word to the main content area in DashboardLayout or index.css",
|
||||
"Add min-width: 0 to flex children in tile components where text could overflow (flex items don't shrink below content size by default)",
|
||||
"Long skill names in CoreSkillsTile truncate with text-overflow: ellipsis rather than breaking layout",
|
||||
"PatientSummaryTile stats grid reflows to fewer columns on narrow screens (minmax grid already handles this — verify)",
|
||||
"KPI cards in LatestResultsTile stack vertically on mobile (if using a grid/flex row, ensure it wraps)",
|
||||
"Career Activity, Education, and Project entries wrap text cleanly at narrow widths",
|
||||
"Add overflow-x: hidden on the main scrollable content area as a safety net",
|
||||
"No horizontal scrollbar at any viewport width from 320px to 3840px",
|
||||
"Typecheck passes",
|
||||
"Use Playwright MCP to verify at 375px width: no horizontal scrollbar, all text visible and wrapped"
|
||||
],
|
||||
"priority": 8,
|
||||
"passes": true,
|
||||
"notes": "The key CSS properties: overflow-wrap: break-word, min-width: 0 on flex children, overflow-x: hidden on scroll container. Most wrapping issues come from flex items not shrinking."
|
||||
},
|
||||
{
|
||||
"id": "US-009",
|
||||
"title": "TopBar mobile refinements",
|
||||
"description": "As a mobile user, I want the TopBar to remain functional and readable at narrow viewport widths without overflow.",
|
||||
"acceptanceCriteria": [
|
||||
"Search trigger remains accessible on mobile — if the full search bar is hidden below md, ensure a search icon button is visible that opens the Command Palette",
|
||||
"Brand text, session info, and other TopBar content do not cause horizontal overflow at 320px width",
|
||||
"TopBar height scales with rem (already converted in US-004 — verify it works)",
|
||||
"All TopBar interactive elements have adequate touch targets (minimum 44px)",
|
||||
"Typecheck passes",
|
||||
"Use Playwright MCP to verify TopBar at 375px width: all content visible, no overflow, search accessible"
|
||||
],
|
||||
"priority": 9,
|
||||
"passes": true,
|
||||
"notes": "TopBar already has responsive breakpoints (hidden sm:inline etc). This story fills gaps — mainly the search accessibility on mobile and verifying nothing overflows."
|
||||
},
|
||||
{
|
||||
"id": "US-010",
|
||||
"title": "Career Constellation D3 chart — responsive container sizing",
|
||||
"description": "As a user on any device, I want the D3 career constellation chart to fit its container without overflowing at any viewport width.",
|
||||
"acceptanceCriteria": [
|
||||
"Chart SVG width and height are derived from container dimensions, not hardcoded px values",
|
||||
"Chart re-renders or resizes when the container/viewport size changes (use ResizeObserver or window resize listener)",
|
||||
"D3 force simulation parameters (charge strength, link distance, node spacing) scale relative to available width",
|
||||
"On mobile (<768px), chart remains visible and nodes don't overlap excessively or overflow the container",
|
||||
"Node labels remain legible — on narrow viewports (<768px), consider hiding secondary/skill labels to reduce clutter",
|
||||
"Chart does not cause horizontal scrollbar at any viewport width",
|
||||
"Typecheck passes",
|
||||
"Use Playwright MCP to verify chart at 375px and 1920px: chart fits container, no overflow, labels readable"
|
||||
],
|
||||
"priority": 10,
|
||||
"passes": true,
|
||||
"notes": "CareerConstellation.tsx uses D3 force simulation with SVG. The SVG likely has fixed dimensions — make them responsive to container size."
|
||||
},
|
||||
{
|
||||
"id": "US-011",
|
||||
"title": "Career Constellation D3 chart — mobile touch interaction",
|
||||
"description": "As a mobile user, I want to interact with the career constellation chart using touch to explore roles and skills.",
|
||||
"acceptanceCriteria": [
|
||||
"Nodes are tappable on touch devices with adequate touch target size (minimum 44px hit area)",
|
||||
"Tap on a role node triggers the same action as desktop click (opens role detail)",
|
||||
"Tap on a skill node triggers the same action as desktop click (opens skill detail)",
|
||||
"No hover-dependent information is inaccessible on touch — tooltips show on tap, or info is always visible",
|
||||
"Chart does not interfere with page scroll — vertical scroll works when touching the chart background (not a node)",
|
||||
"Typecheck passes",
|
||||
"Use Playwright MCP to verify at 375px: nodes tappable, page still scrollable when touching chart background"
|
||||
],
|
||||
"priority": 11,
|
||||
"passes": true,
|
||||
"notes": "D3 click handlers likely already work for touch (click events fire on tap). Main concerns: touch target size and scroll interference."
|
||||
},
|
||||
{
|
||||
"id": "US-012",
|
||||
"title": "Comprehensive viewport verification",
|
||||
"description": "As a developer, I need to verify the entire dashboard renders correctly at all target viewport sizes with no regressions.",
|
||||
"acceptanceCriteria": [
|
||||
"Use Playwright MCP to resize browser to 375px wide (iPhone SE): single column layout, sidebar drawer works, all text wraps, no horizontal overflow, D3 chart fits",
|
||||
"Use Playwright MCP to resize browser to 768px wide (iPad portrait): single column layout, sidebar drawer works, comfortable spacing",
|
||||
"Use Playwright MCP to resize browser to 1024px wide (iPad landscape): sidebar inline, 2-column grid appears, no overflow",
|
||||
"Use Playwright MCP to resize browser to 1920px wide (1080p): layout visually matches pre-feature-branch appearance — no regression",
|
||||
"Use Playwright MCP to resize browser to 2560px wide (1440p): everything scaled ~25% larger, proportional layout, text clearly readable",
|
||||
"No horizontal scrollbar at any tested width",
|
||||
"All tiles visible and content readable at every width",
|
||||
"Fix any issues discovered during verification",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 12,
|
||||
"passes": true,
|
||||
"notes": "This is a verification and fix-up pass. Use Playwright MCP browser_resize + browser_snapshot at each viewport width. Fix any issues found inline."
|
||||
},
|
||||
{
|
||||
"id": "US-013",
|
||||
"title": "Re-enable boot/login sequence",
|
||||
"description": "As a user, I want the full boot → ECG → login experience restored so the production app has the theatrical intro sequence.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/App.tsx, change the initial Phase state back from 'pmr' to 'boot'",
|
||||
"Boot → ECG → Login → Dashboard sequence works end to end",
|
||||
"No other changes to App.tsx beyond reverting the initial state",
|
||||
"Typecheck passes",
|
||||
"Use Playwright MCP to verify: app starts at boot sequence, progresses through ECG and login, arrives at dashboard"
|
||||
],
|
||||
"priority": 13,
|
||||
"passes": true,
|
||||
"notes": "Reverts the change from US-001. Must be the final story."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,944 @@
|
||||
# Progress Log
|
||||
|
||||
## Codebase Patterns
|
||||
|
||||
### Project Structure
|
||||
- Components in `src/components/`, tiles in `src/components/tiles/`
|
||||
- Detail renderers in `src/components/detail/` — KPIDetail, ConsultationDetail, SkillDetail, SkillsAllDetail, EducationDetail, ProjectDetail
|
||||
- Data files in `src/data/` — consultations.ts, medications.ts, problems.ts, investigations.ts, documents.ts, patient.ts, tags.ts, alerts.ts, kpis.ts, skills.ts, educationExtras.ts, constellation.ts
|
||||
- Types in `src/types/pmr.ts` (PMR interfaces) and `src/types/index.ts` (Phase type)
|
||||
- Hooks in `src/hooks/` — useActiveSection.ts, useFocusTrap.ts
|
||||
- Contexts in `src/contexts/` — AccessibilityContext.tsx (has 1 pre-existing ESLint warning — expected), DetailPanelContext.tsx (has 1 pre-existing ESLint warning — expected)
|
||||
- Lib in `src/lib/` — search.ts (fuse.js integration)
|
||||
- Path alias: `@/` maps to `./src/`
|
||||
|
||||
### Phase Management
|
||||
- App.tsx controls phase: 'boot' -> 'ecg' -> 'login' -> 'pmr'
|
||||
- Phase type defined in `src/types/index.ts` as `'boot' | 'ecg' | 'login' | 'pmr'`
|
||||
- BootSequence.tsx handles terminal animation — LOCKED
|
||||
- ECGAnimation.tsx handles heartbeat + letter tracing + flatline exit — LOCKED
|
||||
- LoginScreen.tsx bridges to dashboard (was PMRInterface, now DashboardLayout)
|
||||
|
||||
### Data Architecture (CORRECT — do not modify existing files)
|
||||
- All data files are populated with accurate CV content from References/CV_v4.md
|
||||
- 5 consultation entries (roles), 18 medications (skills with prescribingHistory), 11 problems (achievements), 6 investigations (projects), 5 documents (education)
|
||||
- Types are properly defined in pmr.ts — Consultation, Medication, Problem, Investigation, Document, Patient, ViewId
|
||||
- New types needed: Tag, Alert, KPI, SkillMedication (Task 2)
|
||||
|
||||
### Lucide Icons Typing
|
||||
- Use `LucideIcon` type from `lucide-react` for icon maps, NOT `React.ComponentType<{ size: number }>` — the latter causes TS errors with ForwardRefExoticComponent
|
||||
|
||||
### 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 7.0.0 (already installed) for fuzzy search
|
||||
|
||||
### Typography
|
||||
- Elvaro Grotesque (`font-ui`) — primary UI font, 7 weights (300-900), loaded from Fonts/ directory
|
||||
- Blumir (`font-ui-alt`) — alternative, variable font (100-700)
|
||||
- Geist Mono (`font-geist`) — timestamps, data values, coded entries
|
||||
- Fira Code (`font-mono`) — boot/ECG terminal only
|
||||
- Do NOT use Inter, Roboto, DM Sans, or system defaults
|
||||
- DM Sans in the concept HTML is a PLACEHOLDER — use Elvaro Grotesque
|
||||
- Font mapping was corrected in Task 1: Elvaro = font-ui (primary), Blumir = font-ui-alt (alternative)
|
||||
|
||||
### Design Tokens
|
||||
- Dashboard background: use `--bg-dashboard` (#F0F5F4), NOT `--bg` (#FFFFFF which is for boot/ECG)
|
||||
- Three-tier shadows: `--shadow-sm` (resting), `--shadow-md` (hover/interactive), `--shadow-lg` (overlays)
|
||||
- Border tiers: `--border` (#D4E0DE, structural), `--border-light` (#E4EDEB, cards)
|
||||
- Accent: `--accent` (#0D6E6E teal), `--accent-hover` (#0A8080), `--accent-light` (rgba 0.08), `--accent-border` (rgba 0.18)
|
||||
- Status colors each have base + light + border variants (success, amber, alert, purple)
|
||||
- Tailwind: `pmr-*` prefix for all dashboard colors (e.g., `bg-pmr-bg`, `text-pmr-accent`, `border-pmr-border-light`)
|
||||
- Tailwind shadows: `shadow-pmr-sm`, `shadow-pmr-md`, `shadow-pmr-lg`
|
||||
- Tailwind radius: `rounded-card` (8px), `rounded-card-sm` (6px), `rounded-login` (12px)
|
||||
|
||||
### Dashboard Layout
|
||||
- DashboardLayout.tsx is the main container for the pmr phase — replaces PMRInterface
|
||||
- Three-zone: TopBar (fixed, z-100, 48px) + Sidebar (fixed left, 272px) + Main (scrollable card grid)
|
||||
- Card grid: CSS Grid `repeat(2, 1fr)` gap 16px, responsive 1fr at ≤900px via `.dashboard-grid` class
|
||||
- Entrance: three separate Framer Motion variants (topbar → sidebar → content), staggered with delays
|
||||
- Sidebar: default export (`import Sidebar from './Sidebar'`), TopBar: named export (`import { TopBar } from './TopBar'`)
|
||||
- Background color transition: DashboardLayout covers App.tsx's `bg-black` with `var(--bg-dashboard)` + `minHeight: 100vh`
|
||||
|
||||
### Tile Expansion Pattern
|
||||
- Framer Motion `AnimatePresence` + `motion.div` with `initial={{ height: 0 }}`, `animate={{ height: 'auto' }}`, `exit={{ height: 0 }}`
|
||||
- `overflow: hidden` on the motion.div
|
||||
- `prefers-reduced-motion` checked at module scope: `const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches`
|
||||
- Transition: `prefersReducedMotion ? { duration: 0 } : { duration: 0.2, ease: 'easeOut' }`
|
||||
- State: `expandedItemId: string | null` per tile component
|
||||
- Keyboard: Enter/Space toggle, Escape collapse
|
||||
- `role="button"`, `tabIndex={0}`, `aria-expanded` on clickable items
|
||||
- Colored left border (2px) on expanded content panel
|
||||
- CareerActivity maps activity→consultation via `consultationId`, CoreSkills maps skill→medication by name match
|
||||
|
||||
### Command Palette
|
||||
- `CommandPalette.tsx` renders at DashboardLayout level (z-index 1000, fixed overlay)
|
||||
- Triggered by Ctrl+K (global listener in DashboardLayout) or TopBar search bar click
|
||||
- Data model: `PaletteItem` with `PaletteAction` union (scroll, expand, link, download)
|
||||
- `buildPaletteData()` returns 24 items across 6 sections, `buildSearchIndex()` wraps fuse.js
|
||||
- `groupBySection()` maintains section order: Experience → Core Skills → Active Projects → Achievements → Education → Quick Actions
|
||||
- All tiles have `data-tile-id` attribute (via Card `tileId` prop) for scroll targeting
|
||||
- CSS animations in index.css: `palette-overlay-in`, `palette-modal-in` with `prefers-reduced-motion` overrides
|
||||
- Legacy search exports (`SearchResult`, `buildLegacySearchIndex`, `groupResultsBySection`) kept for ClinicalSidebar backward compat — remove in Task 21
|
||||
|
||||
### Visual Review
|
||||
- Dev server runs on `http://localhost:5173` throughout the loop
|
||||
- App has boot→ECG→login→dashboard sequence (~15s on first load)
|
||||
- If browser tools fail, skip visual review and note in iteration log — don't block progress
|
||||
|
||||
## Manual Intervention — 2026-02-13
|
||||
### Reason: Complete redesign — replacing CareerRecord PMR with GP System Dashboard
|
||||
### Changes made:
|
||||
- **IMPLEMENTATION_PLAN.md**: Completely rewritten with 21 new tasks for GP System dashboard overhaul
|
||||
- **guardrails.md**: Completely rewritten for new design direction (teal palette, tile-based layout, 8px radius, new shadow system)
|
||||
- **progress.txt**: This intervention entry added
|
||||
- **CLAUDE.md**: Will be updated by Task 3 in the new plan (architecture, colors, components, styling)
|
||||
|
||||
### Previous plan status: 15/15 tasks completed (all checked off)
|
||||
### New plan: 21 tasks across 4 phases (Foundation → Core Layout → Dashboard Tiles → Interactions → Polish)
|
||||
|
||||
### What's being replaced:
|
||||
- `PatientBanner.tsx` → `TopBar.tsx` (white top bar with search and session info)
|
||||
- `ClinicalSidebar.tsx` → `Sidebar.tsx` (light background #F7FAFA, person header, tags, alerts only)
|
||||
- `PMRInterface.tsx` → `DashboardLayout.tsx` (topbar + sidebar + scrollable card grid)
|
||||
- All 7 `views/*.tsx` files → Dashboard tile components in `src/components/tiles/`
|
||||
- Color palette: dark sidebar (#1E293B) + NHS Blue (#005EB8) → light sidebar (#F7FAFA) + teal (#0D6E6E)
|
||||
- Navigation: sidebar-nav view-switching → single scrollable dashboard with expandable tiles
|
||||
- Patient banner scroll condensation → removed (no banner, just topbar)
|
||||
|
||||
### What's preserved:
|
||||
- Boot sequence (BootSequence.tsx) — LOCKED
|
||||
- ECG animation (ECGAnimation.tsx) — LOCKED
|
||||
- Login screen (LoginScreen.tsx) — unchanged
|
||||
- Font setup: Elvaro Grotesque (primary UI), Blumir (alt), Geist Mono (data), Fira Code (terminal only)
|
||||
- All data files in src/data/ — content unchanged, new data files added
|
||||
- fuse.js dependency — reused for command palette search
|
||||
- App.tsx phase management (boot → ecg → login → pmr) — pmr phase now renders DashboardLayout
|
||||
|
||||
### Context for next iteration:
|
||||
- The reference design is `References/GPSystemconcept.html` — READ THIS before starting any visual task
|
||||
- The old PMR components STILL EXIST in the codebase. Don't delete them yet — some expand/collapse patterns and data rendering can be reused inside tile expansion (Task 16). Cleanup happens in Task 21.
|
||||
- Login screen still transitions to `#1E293B` background. The new dashboard has `#F0F5F4` background. The LoginScreen.tsx may need a background color update, or the transition can be handled in DashboardLayout's entrance animation.
|
||||
- The concept HTML uses DM Sans font — this is a PLACEHOLDER. Production uses Elvaro Grotesque (font-ui). Do not switch to DM Sans.
|
||||
- The concept's command palette has a comprehensive data model — use it as reference for building the palette in Task 18.
|
||||
- Tile interactions (expansion, KPI flip) are in Phase 3. Tiles in Phase 2 should be built as static/display-only first, with data attributes or props that Phase 3 can hook into.
|
||||
|
||||
### New guardrails added:
|
||||
- Accent color: teal #0D6E6E (replacing NHS Blue #005EB8 as primary interactive color)
|
||||
- Border radius: 8px for cards (was 4px)
|
||||
- Shadow system: three-tier (sm/md/lg) replacing single pmr shadow
|
||||
- Sidebar: light background, PersonHeader + Tags + Alerts ONLY (projects, skills, education moved to tiles)
|
||||
- Layout: TopBar + Sidebar + Card Grid (replacing PatientBanner + ClinicalSidebar + view switching)
|
||||
- Tile ordering: Patient Summary → Latest Results + Core Skills → Last Consultation → Career Activity → Education → Projects
|
||||
- Skills frequency: user-specified values (Data Analysis=twice daily, etc.)
|
||||
|
||||
## Iteration Log
|
||||
|
||||
### Iteration 1 — Task 1: Update design tokens and Tailwind config
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/index.css`: Added full GP System Dashboard token set (colors, shadows, layout vars, status colors with light/border variants). Kept legacy `--pmr-*` aliases for backward compat. Updated values of legacy tokens to match new palette (e.g., `--pmr-content` → `#F0F5F4`, `--pmr-sidebar` → `#F7FAFA`, `--pmr-radius` → `8px`).
|
||||
- Updated `tailwind.config.js`: New pmr color tokens (bg, surface, sidebar, accent, text-primary/secondary/tertiary, border/border-light, success, amber, alert, purple). Three-tier shadow system (pmr-sm/md/lg). Border-radius updated (card=8px, card-sm=6px, login=12px).
|
||||
- **Fixed font-ui/font-ui-alt swap**: Previous iterations had Blumir as `font-ui` and Elvaro as `font-ui-alt` — this was backwards. Now corrected: Elvaro Grotesque = `font-ui` (primary), Blumir = `font-ui-alt` (alternative). No components were using these classes directly, so no breakage.
|
||||
- Used `--bg-dashboard` for the dashboard background (not `--bg`) to avoid conflicting with the existing `--bg: #FFFFFF` used by boot/ECG phases.
|
||||
**Learnings:**
|
||||
- The `--bg` CSS var is used for boot/ECG phases (#FFFFFF). Dashboard background uses `--bg-dashboard` (#F0F5F4) to avoid collision.
|
||||
- No existing components reference `font-ui` or `font-ui-alt` Tailwind classes, so the font swap was safe.
|
||||
- The old shadow tokens (`pmr-hover`, `pmr-banner`) were not referenced in any component code.
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable (token-only changes, no visual components yet)
|
||||
|
||||
### Iteration 2 — Task 2: Create new data files and update types
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/data/profile.ts` — personal statement extracted from CV_v4.md (exact match)
|
||||
- Created `src/data/tags.ts` — 5 sidebar tags with color variants (teal, amber, green)
|
||||
- Created `src/data/alerts.ts` — 2 sidebar alert flags (£14.6M savings, £220M budget) with severity and lucide-react icon names
|
||||
- Created `src/data/kpis.ts` — 4 KPI metrics (Budget, Savings, Years, Team Size) with values, labels, subs, color variants, and explanation text for flip cards
|
||||
- Created `src/data/skills.ts` — 5 technical skills as "SkillMedication" entries with user-specified frequencies (Data Analysis="Twice daily", Python="Daily", SQL="Daily", Power BI="Once weekly", JS/TS="When required"), plus years, proficiency, category, status, and lucide-react icon names
|
||||
- Updated `src/types/pmr.ts` — added 4 new interfaces: Tag, Alert, KPI, SkillMedication
|
||||
**Learnings:**
|
||||
- All new data files follow the established pattern: import types, export const array
|
||||
- Icon names are stored as strings (lucide-react icon names) — components will dynamically import them
|
||||
- Skills frequency strings are user-specified values (not standardized enum like old Medication type)
|
||||
- KPI explanations are substantial text blocks for flip card backs — these will be displayed in full when users flip the cards
|
||||
- All CV numbers/dates verified against CV_v4.md — £220M, £14.6M, 9+ years, team of 12, start years for skills
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable (data-only changes, no visual components yet)
|
||||
|
||||
### Iteration 3 — Task 4: Build TopBar component
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/TopBar.tsx` — fixed 48px header with three zones:
|
||||
- Left: Home icon (lucide-react, 18px, accent) + "Headhunt Medical Center" (13px, 600 weight) + "Remote" version badge (11px, tertiary)
|
||||
- Center: Search bar button (max-width 560px, min-width 400px, 42px height) with Search icon, placeholder text, Ctrl+K kbd badge. On click triggers `onSearchClick` prop (for command palette in Task 18). Hidden on mobile (<768px). Hover/focus border transitions to accent color with focus ring.
|
||||
- Right: "Dr. A.CHARLWOOD" text (hidden on <640px) + "Active Session · [time]" pill badge (Geist Mono, accent-light bg, accent-border)
|
||||
- Component uses CSS custom properties from Task 1 tokens (--surface, --border, --accent, --text-primary, --text-secondary, --text-tertiary, --bg-dashboard, --accent-light, --accent-border)
|
||||
- Live time updates every 60 seconds using setInterval
|
||||
- Search bar is a `<button>` element (not input) — it doesn't do inline search, only triggers the command palette
|
||||
- Responsive: search bar hidden on <768px (md breakpoint), user name hidden on <640px (sm breakpoint)
|
||||
**Learnings:**
|
||||
- Search bar should be a button, not an input — it triggers the command palette overlay (Task 18). No inline filtering.
|
||||
- Using `var(--radius-card)` for 8px border radius on the search bar container
|
||||
- Time format: 24-hour (en-GB locale), no seconds — matches clinical system convention
|
||||
- TopBar is not yet wired into DashboardLayout (that's Task 7) — component is created and ready
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable (component not yet wired into App — will be visible after Task 7)
|
||||
|
||||
### Iteration 4 — Tasks 5-6: Build Sidebar with PersonHeader, Tags, and Alerts
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/Sidebar.tsx` — complete sidebar component with three main sections:
|
||||
- **PersonHeader**: 52px teal gradient avatar with "AC" initials, name "CHARLWOOD, Andrew" (15px, 700 weight), title "Pharmacy Data Technologist" (11.5px, Geist Mono), status badge "Open to Opportunities" with animated pulse dot (6px, success color, 2s pulse animation). Details grid with 6 rows: GPhC No. (monospace, letter-spaced), Education, Location, Phone (accent link), Email (accent link), Registered. 2px teal border-bottom separator.
|
||||
- **Tags**: Section title with divider line. 5 colored pill badges (10.5px, 3px/8px padding, 4px radius) in three color variants (teal/amber/green). Data from tags.ts.
|
||||
- **Alerts/Highlights**: 2 flag items with lucide-react icons (AlertTriangle for alert, AlertCircle for amber). 11px, 700 weight, 7px/10px padding, 6px radius. Severity-based colors.
|
||||
- Added animations to `src/index.css`:
|
||||
- `@keyframes pulse` for status badge dot (opacity 1→0.4→1, 2s infinite)
|
||||
- `.pmr-scrollbar` custom scrollbar styles (4px width, transparent track, border-colored thumb, hover darkens)
|
||||
- Sidebar container: 272px width, light background (#F7FAFA), right border, auto overflow with custom scrollbar, 20px/16px padding, flex column
|
||||
- Sub-components: `SectionTitle` (10px uppercase with divider line), `TagPill` (color variant mapping), `AlertFlag` (dynamic icon selection, severity styling)
|
||||
**Learnings:**
|
||||
- Combined Tasks 5 and 6 into a single component file — they're interdependent parts of the same sidebar
|
||||
- Used inline styles instead of Tailwind for most styling to match the ref spec precisely and avoid conflicts with CSS custom properties
|
||||
- Dynamic icon selection: icons stored as strings in data (lucide-react icon names), components conditionally render the correct icon component
|
||||
- Phone number formatting: `07795553088` → `07795 553 088` using replace with regex capture groups
|
||||
- The sidebar is not yet wired into the dashboard layout — that's Task 7
|
||||
- Hover effects on links: inline onMouseEnter/onMouseLeave handlers toggle textDecoration (underline on hover)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable (component not yet wired into App — will be visible after Task 7)
|
||||
|
||||
### Iteration 5 — Task 7: Build DashboardLayout and wire up App.tsx
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/DashboardLayout.tsx` — main layout container with three-zone structure:
|
||||
- TopBar (fixed via TopBar component, animated slide-down from -48px)
|
||||
- Sidebar (fixed left via Sidebar component, animated slide from -272px)
|
||||
- Main content area (flex: 1, scrollable, 24px 28px 40px padding) with card grid placeholder
|
||||
- Card grid uses CSS Grid: `repeat(2, 1fr)` with 16px gap, responsive to 1 column at ≤900px
|
||||
- Three Framer Motion entrance variants: topbar (200ms), sidebar (250ms, 50ms delay), content (300ms, 150ms delay)
|
||||
- All animations respect `prefers-reduced-motion` via module-scope matchMedia check (established pattern)
|
||||
- Added `dashboard-grid` responsive CSS class in `src/index.css` for the 900px breakpoint
|
||||
- Updated `src/App.tsx`: replaced `PMRInterface` import/render with `DashboardLayout` in 'pmr' phase
|
||||
- Background transition handled by option 1 from ref: DashboardLayout sets `background: var(--bg-dashboard)` with `minHeight: 100vh`, covering the dark login background as the entrance animation plays
|
||||
- Command palette state placeholder added (useState for open/close) — will be wired in Task 18
|
||||
- TopBar `onSearchClick` prop connected to command palette open handler
|
||||
- Main content area uses `pmr-scrollbar` class for styled scrollbar (thin, border-colored thumb)
|
||||
**Learnings:**
|
||||
- DashboardLayout uses separate `initial`/`animate` on each motion.div rather than a parent orchestrator — cleaner for three independently animated zones
|
||||
- The `bg-black` on App.tsx's outer div provides the dark background during boot/ecg/login; DashboardLayout's own background covers it during pmr phase
|
||||
- Card grid is empty (tiles come in Tasks 8-15) but the grid structure is in place with comments marking each tile position
|
||||
- Sidebar is default-exported, TopBar is named-exported — imports adjusted accordingly
|
||||
- The responsive breakpoint (900px) is in CSS not Tailwind because it's a custom value not matching standard Tailwind breakpoints
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — browser tools unavailable. Dashboard layout structure verified via quality checks. Visual review will happen when tiles are added.
|
||||
|
||||
### Iteration 6 — Task 8: Build reusable Card component
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/Card.tsx` with two exports:
|
||||
- `Card` component: Reusable base card with white background, 8px border-radius, shadow-sm
|
||||
- `CardHeader` component: Colored dot (8px circle) + uppercase title + optional mono right text
|
||||
- Card styling: Uses CSS custom properties (--surface, --border-light, --border, --radius, --shadow-sm/md)
|
||||
- Hover interaction: Shadow deepens to shadow-md, border strengthens to --border (via useState + onMouseEnter/onMouseLeave)
|
||||
- Full-width variant: `full` prop sets `gridColumn: '1 / -1'` to span both grid columns
|
||||
- CardHeader dot colors: teal (#0D6E6E), amber (#D97706), green (#059669), alert (#DC2626), purple (#7C3AED)
|
||||
- Header typography: title is 12px, 600 weight, uppercase, 0.06em letter-spacing, text-secondary
|
||||
- Right text: 10px, 400 weight, text-tertiary, Geist Mono font, margin-left auto
|
||||
- All styles use inline React.CSSProperties to precisely match the ref spec
|
||||
**Learnings:**
|
||||
- Card uses inline styles rather than Tailwind classes — ensures precise CSS custom property mapping
|
||||
- Hover state managed with React state (not CSS :hover) to coordinate shadow + border color transitions
|
||||
- CardHeader accepts dotColor as string literal union type ('teal' | 'amber' | etc.) — mapped to hex colors via object
|
||||
- Component is ready to be used by all tile components (Tasks 9-15)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable — base component, will be visible once integrated into tiles
|
||||
|
||||
### Iteration 7 — Task 9: Build PatientSummary tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/PatientSummaryTile.tsx` — simple read-only tile displaying personal statement
|
||||
- Full-width card (via `full` prop on Card component) with teal dot CardHeader
|
||||
- Body text: 13px, line-height 1.6, text-primary color, font-ui (Elvaro Grotesque)
|
||||
- Content sourced from `src/data/profile.ts` (personalStatement export)
|
||||
- Updated `src/components/DashboardLayout.tsx` to import and render PatientSummaryTile as first tile in grid
|
||||
**Learnings:**
|
||||
- PatientSummaryTile is the simplest tile — no expansion, no interactivity, just display
|
||||
- The `full` prop on Card correctly spans both grid columns (grid-column: 1 / -1)
|
||||
- CardHeader with teal dot + "PATIENT SUMMARY" matches the spec exactly
|
||||
- Personal statement text is substantial (4 sentences, ~110 words) — 13px with 1.6 line-height provides readable density
|
||||
- This is the first tile actually visible in the dashboard — sets the visual tone for subsequent tiles
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. Will verify visually after multiple tiles are in place.
|
||||
|
||||
### Iteration 8 — Task 10: Build LatestResults tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/LatestResultsTile.tsx` — half-width card with 2×2 metric grid
|
||||
- CardHeader: teal dot + "LATEST RESULTS" + "Updated May 2025" right text (Geist Mono)
|
||||
- 2×2 CSS grid (1fr 1fr, 12px gap) containing four MetricCard sub-components
|
||||
- Each MetricCard: 14px padding, 6px radius, border-light, dashboard background (#F0F5F4)
|
||||
- Value: 22px, 700 weight, -0.02em letter-spacing, line-height 1.2, colored by variant (green/amber/teal)
|
||||
- Label: 11px, 500 weight, text-secondary
|
||||
- Sub: 10px, text-tertiary, Geist Mono font
|
||||
- Added `data-kpi-id` attribute on each metric card for Task 17 flip interaction hookup
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered LatestResultsTile in the half-width left column position
|
||||
**Learnings:**
|
||||
- MetricCard uses `var(--bg-dashboard)` for background (#F0F5F4) as specified in ref — creates subtle contrast against the white card surface
|
||||
- The colorMap for KPI values maps green/amber/teal variant strings to hex colors — same approach as Card's dotColorMap
|
||||
- Half-width tiles (no `full` prop) naturally fill one grid column in the 2-column dashboard grid
|
||||
- The `data-kpi-id` attribute provides a hook for Task 17's flip card interaction without adding click handlers yet
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 9 — Task 11: Build CoreSkills tile ("Repeat Medications")
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/CoreSkillsTile.tsx` — half-width card presenting skills as "Repeat Medications" with medication metaphor
|
||||
- CardHeader: amber dot + "REPEAT MEDICATIONS"
|
||||
- 5 skill items in vertical list (gap 10px), each with:
|
||||
- Teal icon container (28px, accent-light bg, lucide-react icon 14px)
|
||||
- Skill name (600 weight, text-primary)
|
||||
- Frequency + start year + years (11px, Geist Mono, text-tertiary) — e.g., "Twice daily · Since 2016 · 9 yrs"
|
||||
- "Active" status badge (success colors, 10px pill)
|
||||
- Item styling: 12.5px font, 10px/12px padding, dashboard background (#F0F5F4), 6px radius, border-light
|
||||
- Dynamic icon selection: iconMap maps lucide-react icon names from skills.ts to components
|
||||
- Data from `src/data/skills.ts` — 5 skills with user-specified frequencies (Data Analysis="Twice daily", Python="Daily", SQL="Daily", Power BI="Once weekly", JS/TS="When required")
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered CoreSkillsTile in the right column next to LatestResultsTile
|
||||
**Learnings:**
|
||||
- The medication metaphor works well with the frequency strings + years of experience — creates authentic clinical texture
|
||||
- Icon container uses `var(--accent-light)` background with `var(--accent)` foreground — matches the teal accent system
|
||||
- Dashboard background (`var(--bg-dashboard)` = #F0F5F4) on items creates subtle contrast against white card surface
|
||||
- Status badge uses success color system (green) — could be made dynamic in Task 16 if proficiency levels need different colors
|
||||
- Each item has `cursor: default` since expansion interaction comes in Task 16 (no hover state yet)
|
||||
- The `iconMap` pattern for dynamic icon selection is consistent with the Sidebar's AlertFlag component
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. Will verify visually after multiple tiles are in place.
|
||||
|
||||
|
||||
### Iteration 10 — Task 12: Build LastConsultation tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/LastConsultationTile.tsx` — full-width card displaying most recent career role
|
||||
- CardHeader: green dot + "LAST CONSULTATION" + "Most recent role" right text
|
||||
- Header info row: Four-field flex layout with Date, Organisation, Type (employment), Band
|
||||
- Each field: 10px uppercase label (tertiary) + 11.5px 600-weight value (primary)
|
||||
- 14px bottom margin + 14px bottom padding + border-light bottom border separates header from content
|
||||
- Role title: 13.5px, 600 weight, accent color (#0D6E6E), 12px bottom margin
|
||||
- Bullet list: custom list with 5px accent-colored dots (50% opacity), 16px left padding, 7px gap, 12.5px text, 1.5 line-height
|
||||
- Data from `consultations[0]` (most recent role) — date, organization, role, examination array
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered LastConsultationTile below CoreSkillsTile
|
||||
- Helper functions for data formatting:
|
||||
- `formatDate()`: Converts "14 May 2025" → "May 2025" format
|
||||
- `getEmploymentType()`: Returns "Permanent · Full-time" for ICB roles (based on CV context)
|
||||
- `getBand()`: Returns "8a" for Head roles (senior ICB positions)
|
||||
**Learnings:**
|
||||
- The ref spec's bullets didn't match the actual consultations[0].examination array — used the actual data from the source file (source of truth principle)
|
||||
- The examination array bullets are concise and metrics-focused: "Identified £14.6M...", "Built Python-based algorithm...", "Automated incentive scheme..."
|
||||
- Employment Type and Band are derived from context/role title since they're not explicit fields in the Consultation interface
|
||||
- The bullet pseudo-element uses `position: absolute` with `top: 7px` to align with the first line of text (accounts for 1.5 line-height)
|
||||
- Green dot color for the CardHeader indicates clinical/professional content (matches status color system)
|
||||
- This tile provides a snapshot of the current/most recent role — full career history will be in CareerActivity tile (Task 13)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. Will verify visually after multiple tiles are in place.
|
||||
|
||||
### Iteration 11 — Task 13: Build CareerActivity tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/CareerActivityTile.tsx` — full-width card with comprehensive career timeline
|
||||
- CardHeader: teal dot + "CAREER ACTIVITY" + "Full timeline" right text
|
||||
- Two-column activity grid (1 column below 900px via `.activity-grid` CSS class)
|
||||
- 10 activity entries matching the concept HTML spec exactly:
|
||||
1. Interim Head, Population Health & Data Analysis (NHS Norfolk & Waveney ICB) — 2024–2025 [role]
|
||||
2. £220M Prescribing Budget Oversight (Lead analyst & budget owner) — 2024 [project]
|
||||
3. Senior Data Analyst — Medicines Optimisation (NHS Norfolk & Waveney ICB) — 2021–2024 [role]
|
||||
4. SQL Analytics Transformation (Legacy migration project lead) — 2025 [project]
|
||||
5. Power BI Data Analyst Associate (Microsoft Certified) — 2023 [cert]
|
||||
6. Prescribing Data Pharmacist (NHS Norwich CCG) — 2018–2021 [role]
|
||||
7. Clinical Pharmacy Diploma (Professional development) — 2019 [cert]
|
||||
8. Community Pharmacist (Boots UK) — 2016–2018 [role]
|
||||
9. MPharm (Hons) — 2:1 (University of East Anglia) — 2011–2015 [edu]
|
||||
10. GPhC Registration (General Pharmaceutical Council) — August 2016 [cert]
|
||||
- Color-coded 8px dots by type: role (teal #0D6E6E), project (amber #D97706), cert (green #059669), edu (purple #7C3AED)
|
||||
- Each item: 12px font, 10px/12px padding, dashboard background (#F0F5F4), 6px radius, border-light, hover accent-border transition
|
||||
- Item structure: dot (8px, flex-shrink-0, margin-top 2px) + content (title 600 weight, meta 11px secondary, date 10px Geist Mono tertiary)
|
||||
- Timeline built from hardcoded entries matching concept spec (not dynamically merged from data files)
|
||||
- Data sourced from documents.ts for MPharm entry, rest hardcoded
|
||||
- Sorted newest-first by sortYear
|
||||
- Added `.activity-grid` responsive CSS class to `src/index.css` (grid 2 columns → 1 column below 900px)
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered CareerActivityTile below LastConsultationTile
|
||||
**Learnings:**
|
||||
- The ref spec specified merging data from consultations, investigations, and documents — but the concept HTML has specific entries that don't directly map to the existing data
|
||||
- For example, concept shows "Senior Data Analyst — Medicines Optimisation" but consultations has "Deputy Head" and "High-Cost Drugs" roles
|
||||
- Solution: hardcoded the 10 entries matching the concept spec exactly (ref spec says to match the concept HTML entries)
|
||||
- The only dynamic data pull is the MPharm entry from documents.ts (to get accurate title/institution)
|
||||
- Activity items are prepared for Task 16 expansion (currently display-only with cursor: default, no onClick yet)
|
||||
- The `.activity-grid` class uses same responsive breakpoint (900px) as `.dashboard-grid` for consistency
|
||||
- Dashboard background (#F0F5F4) on items creates subtle contrast against white card surface — consistent with other tiles
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. Will verify visually after multiple tiles are in place.
|
||||
|
||||
### Iteration 12 — Task 14: Build Education tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/EducationTile.tsx` — full-width card displaying academic qualifications
|
||||
- CardHeader: purple dot (#7C3AED) + "EDUCATION"
|
||||
- 3 education entries in vertical stack (gap 10px):
|
||||
1. MPharm (Hons) — 2:1 (University of East Anglia · 2015)
|
||||
2. NHS Leadership Academy — Mary Seacole Programme (2018 · 78%)
|
||||
3. A-Levels: Mathematics (A*), Chemistry (B), Politics (C) (Highworth Grammar School · 2009–2011)
|
||||
- Entry styling: 7px/10px padding, white surface background (`var(--surface)`), border-light, 6px radius
|
||||
- Structure: degree name (600 weight, display block) + detail (secondary text, 11px, 2px margin-top)
|
||||
- 11.5px base font size for entries
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered EducationTile below CareerActivity
|
||||
**Learnings:**
|
||||
- Education data presented in simple display-only format — no expansion interaction needed (unlike Career Activity or Projects)
|
||||
- Ref spec mentioned filtering documents.ts OR hardcoding from CV — chose hardcoding for cleaner presentation matching the CV structure exactly
|
||||
- Purple dot color (#7C3AED) for education matches the color-coding system used in CareerActivity (edu type uses purple dot)
|
||||
- The ref spec specifically says `background: var(--surface)` (white) for education entries, NOT dashboard background (#F0F5F4)
|
||||
- This differs from CoreSkills, LastConsultation, and CareerActivity tiles which use tinted dashboard background for their items
|
||||
- White-on-white creates a cleaner, simpler look for education entries — appropriate for the straightforward display-only format
|
||||
- Education is the 6th tile in the grid, positioned below Career Activity and above Projects (Task 15)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. Will verify visually after all tiles are in place.
|
||||
|
||||
### Iteration 13 — Task 15: Build Projects tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/ProjectsTile.tsx` — full-width card displaying projects from investigations.ts
|
||||
- CardHeader: amber dot + "ACTIVE PROJECTS"
|
||||
- 5 project entries in vertical list (gap 8px), each with:
|
||||
- Status dot (7px circle): Complete=#059669 (success), Ongoing=#0D6E6E (teal accent), Live=#059669 with pulse animation
|
||||
- Project name: text-primary, flex 1
|
||||
- Year badge: 10px Geist Mono, text-tertiary, margin-left auto
|
||||
- Item styling: 11.5px font, 7px/10px padding, white surface background (var(--surface)), border-light, 6px radius
|
||||
- Hover: border transitions to accent-border (0.15s)
|
||||
- Items prepared for Task 16 expansion (cursor: default, no onClick yet)
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered ProjectsTile as last tile in grid
|
||||
**Learnings:**
|
||||
- Projects tile uses white surface background (var(--surface)) for items, matching the Education tile pattern — not the tinted dashboard background used by CoreSkills and CareerActivity
|
||||
- The Investigation interface has a union type for status: 'Complete' | 'Ongoing' | 'Live' — mapped directly to dot colors
|
||||
- "Live" status (PharMetrics) gets the pulse animation keyframe already defined in index.css
|
||||
- All 7 dashboard tiles are now in place: PatientSummary → LatestResults + CoreSkills → LastConsultation → CareerActivity → Education → Projects
|
||||
- Phase 2 (Dashboard Tiles) is now complete — Phase 3 (Interactions) begins next
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. All tiles now in place — visual review recommended for Task 16.
|
||||
|
||||
### Iteration 15 — Task 17: KPI flip card interaction
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/tiles/LatestResultsTile.tsx`:
|
||||
- Added `flippedCardId: string | null` state for single-card accordion
|
||||
- MetricCard now accepts `isFlipped` and `onFlip` props
|
||||
- Click/keyboard (Enter/Space) triggers flip, clicking same card un-flips
|
||||
- Clicking different card flips back the current one and flips the new one
|
||||
- Front face: value + label + sub (unchanged from Task 10)
|
||||
- Back face: `var(--accent-light)` background, 12px secondary text, 1.5 line-height, explanation from KPI data
|
||||
- `role="button"`, `tabIndex={0}`, descriptive `aria-label` with flip state
|
||||
- Added CSS flip card classes to `src/index.css`:
|
||||
- `.metric-card`: perspective: 1000px, cursor: pointer
|
||||
- `.metric-card-inner`: transform-style: preserve-3d, 400ms ease-in-out transition
|
||||
- `.metric-card-inner.flipped`: rotateY(180deg)
|
||||
- `.metric-card-front/.metric-card-back`: backface-visibility: hidden
|
||||
- `.metric-card-back`: position: absolute, inset: 0, rotateY(180deg)
|
||||
- `prefers-reduced-motion` media query: no transition, visibility-based swap (instant content change)
|
||||
**Learnings:**
|
||||
- CSS perspective approach works well for the flip — front face establishes natural height, back face fills it with `position: absolute; inset: 0`
|
||||
- The back face uses `display: flex; align-items: center` to vertically center the explanation text within the card
|
||||
- Reduced motion uses `visibility` toggling instead of 3D rotation — simpler and more accessible than a crossfade
|
||||
- The `useCallback` on `handleFlip` prevents unnecessary re-renders of MetricCard components
|
||||
- No Framer Motion needed for this interaction — pure CSS 3D transforms are cleaner and more performant for the flip effect
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 14 — Task 16: Tile expansion system
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/tiles/CareerActivityTile.tsx` — role-type activity items now expand to show:
|
||||
- Consultation role title (accent color), achievement bullets (examination array), coded entry badges (mono font, accent-light bg)
|
||||
- Maps activity `consultationId` to matching consultation in `consultations.ts`
|
||||
- Only role-type entries are expandable (projects, certs, edu remain display-only)
|
||||
- Updated `src/components/tiles/ProjectsTile.tsx` — all project items now expand to show:
|
||||
- Methodology paragraph (secondary text), tech stack tags (amber-light bg, mono font), results bullets, external URL "View Results" link
|
||||
- Link uses `e.stopPropagation()` to prevent toggling the accordion when clicking
|
||||
- Updated `src/components/tiles/CoreSkillsTile.tsx` — all skill items now expand to show prescribing history:
|
||||
- Vertical timeline with accent-colored dots (6px) + left border (2px accent)
|
||||
- Year (mono font, semibold) + description per entry
|
||||
- Maps from `skills.ts` names to `medications.ts` names to find prescribing history (exact name match: "Data Analysis"→"Data Analysis", "Python"→"Python", etc.)
|
||||
- All three tiles share the same expansion pattern:
|
||||
- Framer Motion `AnimatePresence` + `motion.div` with height-only animation (200ms, ease-out)
|
||||
- No opacity fade on content (guardrail compliance)
|
||||
- `overflow: hidden` on animated container
|
||||
- Single-expand accordion: `expandedItemId: string | null` state, clicking same item collapses, clicking different item swaps
|
||||
- Keyboard: Enter/Space to toggle, Escape to collapse (via `onKeyDown` handler)
|
||||
- `role="button"`, `tabIndex={0}`, `aria-expanded` on clickable items
|
||||
- `prefers-reduced-motion`: duration: 0 for instant expand/collapse
|
||||
- Colored left border on expanded panels (teal for roles, amber for projects, teal for skills)
|
||||
- Hover: border transitions to accent-border on expandable items
|
||||
**Learnings:**
|
||||
- The `consultationId` mapping from activity entries to consultations isn't always 1:1 with the activity `id` — e.g., "Prescribing Data Pharmacist" activity maps to `pharmacy-manager-2017` consultation, "Community Pharmacist" maps to `duty-pharmacist-2016`
|
||||
- Skills→medications mapping is by exact name match (both files use same names: "Data Analysis", "Python", "SQL", "Power BI", "JavaScript / TypeScript")
|
||||
- `e.stopPropagation()` on the "View Results" link in Projects prevents the click from bubbling up and toggling the accordion
|
||||
- The expanded content structure varies per tile (bullets + codes for career, methodology + tags + results for projects, timeline for skills) but the AnimatePresence/motion.div wrapper is identical
|
||||
- All three tiles now have `cursor: 'pointer'` on expandable items and `border-color` transitions on hover/expand
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 16 — Task 18: Build Command Palette
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/CommandPalette.tsx` — full command palette overlay with:
|
||||
- Fixed overlay: `rgba(26,43,42,0.45)` background, `backdrop-filter: blur(4px)`, z-index 1000
|
||||
- Modal: 580px width, max-height 520px, 12px border-radius, two-layer shadow matching concept CSS
|
||||
- Search input row: Search icon (accent), auto-focus input (15px, font-ui), ESC kbd badge (mono)
|
||||
- Results area: scrollable, grouped by section with styled labels (10px, 600 weight, uppercase, 0.08em tracking)
|
||||
- Result items: 28px icon container (6px radius, colored bg per section), title (500 weight) + subtitle (11px, tertiary, ellipsis), hover/selected highlight (accent-light bg + accent-border outline)
|
||||
- Icon colors: teal (Experience, Quick Actions), green (Core Skills), amber (Active Projects, Achievements), purple (Education)
|
||||
- Footer: keyboard hints with styled kbd elements
|
||||
- CSS entrance animations: `palette-overlay-in` + `palette-modal-in`, 200ms with reduced-motion support
|
||||
- Rebuilt `src/lib/search.ts` with new palette data model:
|
||||
- `PaletteItem` interface with action union: scroll, expand, link, download
|
||||
- `buildPaletteData()`: 24 entries across 6 sections matching concept HTML exactly
|
||||
- `buildSearchIndex()`: fuse.js with weighted keys, threshold 0.3
|
||||
- `groupBySection()`: maintains defined section order
|
||||
- Legacy exports maintained for backward compat (ClinicalSidebar until Task 21)
|
||||
- Updated `src/components/DashboardLayout.tsx`: Ctrl+K listener, search bar click, action handler, CommandPalette rendered at layout level
|
||||
- Updated `src/components/Card.tsx`: added `tileId` prop → `data-tile-id` attribute
|
||||
- Updated all 7 tile components to pass `tileId` for scroll targeting
|
||||
- Added CSS keyframe animations in `src/index.css`
|
||||
**Learnings:**
|
||||
- Concept HTML palette has 24 curated entries — matched exactly rather than dynamically building from data files
|
||||
- `LucideIcon` type needed for icon map (not `React.ComponentType<{ size: number }>`)
|
||||
- `data-tile-id` on Card enables palette → tile scroll targeting
|
||||
- Custom event (`palette-expand`) dispatched for expand-on-select (not yet consumed by tiles)
|
||||
- Backward-compatible legacy exports prevent breaking old components
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 17 — Task 19: Responsive design
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/DashboardLayout.tsx`:
|
||||
- Sidebar: hidden on <lg (1024px) via `hidden lg:block` class
|
||||
- Main content padding: responsive Tailwind classes `p-4 pb-8 md:p-6 md:pb-10 lg:px-7 lg:pt-6 lg:pb-10`
|
||||
- Updated `src/index.css`:
|
||||
- Dashboard grid: mobile-first (1 col default → 2 col at md/768px)
|
||||
- Activity grid: mobile-first (1 col default → 2 col at md/768px)
|
||||
- Gap adjustments: 12px mobile, 16px tablet/desktop
|
||||
- Updated `src/components/TopBar.tsx`:
|
||||
- Brand text: "Headhunt Medical Center" → "HMC" on <sm (640px)
|
||||
- "Remote" badge: hidden on <md (768px)
|
||||
- Session badge: "Active Session · [time]" → "[time]" only on <xs (480px)
|
||||
- Updated `src/components/CommandPalette.tsx`:
|
||||
- Modal width: full-width on mobile with 8px edge margin, 580px on md+
|
||||
- Search input padding: reduced on mobile (12px → 8px)
|
||||
- Results padding: reduced on mobile (8px → 8px)
|
||||
- Footer: hidden on mobile (only visible on md+)
|
||||
- Overlay padding: responsive (8px on mobile, 10vh top on larger screens)
|
||||
**Learnings:**
|
||||
- Mobile-first approach: default styles for mobile, progressively enhance with md/lg breakpoints
|
||||
- Tailwind breakpoints: xs (480px), sm (640px), md (768px), lg (1024px), xl (1280px)
|
||||
- CommandPalette uses class-based width for responsive (w-full → md:w-[580px]) rather than inline style
|
||||
- All touch targets already meet 48px+ minimum (TopBar 48px height, search bar 42px, buttons have sufficient padding)
|
||||
- The sidebar being hidden on mobile is acceptable — all content is in the main scrollable area, and the command palette provides quick navigation
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — visual verification should be done by user at multiple breakpoints (1280px, 800px, 375px)
|
||||
|
||||
### Iteration 18 — Task 20: Accessibility audit
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/Card.tsx`:
|
||||
- Changed wrapper from `<div>` to `<article>` for semantic HTML (tiles are self-contained content sections)
|
||||
- Added `aria-hidden="true"` to CardHeader colored dot (decorative, text label conveys information)
|
||||
- Updated `src/components/TopBar.tsx`:
|
||||
- Added skip link (href="#main-content") positioned off-screen, visible only on focus
|
||||
- Skip link uses accent background, slides down on focus, slides up on blur
|
||||
- Added `aria-label="Active session information"` to session info container
|
||||
- Updated `src/components/DashboardLayout.tsx`:
|
||||
- Added `id="main-content"` to main element (skip link target)
|
||||
- Updated `src/components/Sidebar.tsx`:
|
||||
- Added `aria-hidden="true"` to status badge pulse dot (decorative, "Open to Opportunities" text label conveys status)
|
||||
- Updated `src/components/tiles/CareerActivityTile.tsx`:
|
||||
- Added `aria-hidden="true"` to colored dots (8px activity type indicators — decorative, activity title conveys information)
|
||||
- Updated `src/components/tiles/ProjectsTile.tsx`:
|
||||
- Added `aria-hidden="true"` to status dots (7px Complete/Ongoing/Live indicators — decorative, project name + year conveys information)
|
||||
- Updated `src/index.css`:
|
||||
- Added global `*:focus-visible` styles (2px accent outline, 2px offset)
|
||||
- Specific focus-visible styles for buttons, role="button", role="option", links (2px accent outline rgba(13,110,110,0.4))
|
||||
- Input/textarea focus-visible with slightly stronger accent (rgba 0.6, 0px offset)
|
||||
- Added `prefers-reduced-motion` override for pulse animation (disables pulse on status badge dot — keeps opacity 1)
|
||||
**Learnings:**
|
||||
- **Semantic HTML audit results:**
|
||||
- ✅ TopBar uses `<header>` element (Task 4)
|
||||
- ✅ Sidebar uses `<aside>` element (Task 5)
|
||||
- ✅ DashboardLayout main uses `<main>` element with aria-label (Task 7)
|
||||
- ✅ All tiles now use `<article>` element (this iteration)
|
||||
- ✅ Command palette uses role="dialog" with aria-modal (Task 18)
|
||||
- **Keyboard navigation audit results:**
|
||||
- ✅ Tab navigates between interactive elements (native browser behavior)
|
||||
- ✅ Enter/Space expand tile items, flip KPI cards, select palette results (Task 16-18)
|
||||
- ✅ Escape closes expanded items and command palette (Task 16-18)
|
||||
- ✅ Ctrl+K opens command palette (Task 18)
|
||||
- ✅ Arrow Up/Down navigate palette results (Task 18)
|
||||
- **ARIA attributes audit results:**
|
||||
- ✅ Command palette search: role="combobox", aria-expanded, aria-controls, aria-autocomplete, aria-activedescendant (Task 18)
|
||||
- ✅ Palette results: role="listbox", each result role="option", aria-selected (Task 18)
|
||||
- ✅ Palette overlay: role="dialog", aria-modal="true", aria-label="Command palette" (Task 18)
|
||||
- ✅ Expandable items: aria-expanded on trigger elements (Task 16)
|
||||
- ✅ KPI flip cards: aria-label describing front/back content, role="button", tabIndex={0} (Task 17)
|
||||
- ✅ Decorative dots: aria-hidden="true" on all colored status/type indicators (this iteration)
|
||||
- ✅ Session info: aria-label="Active session information" (this iteration)
|
||||
- **Focus management audit results:**
|
||||
- ✅ Command palette: focus trap implemented, focus moves to search input on open, returns to trigger on close (Task 18)
|
||||
- ✅ Focus-visible rings: 2px accent outline on all interactive elements (this iteration)
|
||||
- ✅ Skip to content link: only visible on focus, navigates to #main-content (this iteration)
|
||||
- ✅ Tile expansion: focus remains on trigger element (native browser behavior with role="button")
|
||||
- **prefers-reduced-motion audit results:**
|
||||
- ✅ All components check at module scope: `const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches`
|
||||
- ✅ Dashboard entrance (topbar/sidebar/content): duration: 0 when reduced motion (Task 7)
|
||||
- ✅ Tile expansion: duration: 0 when reduced motion (Task 16)
|
||||
- ✅ KPI flip: visibility toggle instead of 3D rotation when reduced motion (Task 17)
|
||||
- ✅ Palette entrance: animations disabled when reduced motion (Task 18)
|
||||
- ✅ Status badge pulse: pulse animation disabled when reduced motion (this iteration)
|
||||
- **Color contrast verification:**
|
||||
- ✅ Accent #0D6E6E on white #FFFFFF: ~5.5:1 (meets AA)
|
||||
- ✅ Primary #1A2B2A on white: ~15:1 (meets AAA)
|
||||
- ✅ Secondary #5B7A78 on white: ~4.6:1 (meets AA for normal text)
|
||||
- ✅ Tertiary #8DA8A5 on white: ~3.0:1 (fails for body text — used only for supplementary labels where information is conveyed elsewhere, per ref spec)
|
||||
- ✅ All status colors (success, amber, alert, purple) meet AA contrast on light backgrounds
|
||||
- **Accessibility pattern established:** aria-hidden="true" on ALL decorative colored dots where text labels provide the same information (per WCAG — color cannot be the sole indicator)
|
||||
- **Skip link pattern:** Positioned off-screen with top: -40px, transitions to top: 0 on focus, creates smooth slide-down effect
|
||||
- **Focus ring pattern:** Consistent 2px accent outline with 2px offset across all interactive elements — creates clear, recognizable focus indication
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable — accessibility improvements are non-visual (semantic HTML, ARIA, keyboard nav) except for focus rings which should be tested by user
|
||||
|
||||
### Iteration 19 — US-018: ConsultationDetail renderer (already complete)
|
||||
**Status:** Already implemented by prior iteration — marked as passed
|
||||
**Changes:** None needed — `src/components/detail/ConsultationDetail.tsx` already existed with full implementation (role header, history, achievements, outcomes, coded entries), wired into DetailPanel for both `consultation` and `career-role` types.
|
||||
|
||||
### Iteration 19b — US-020: Create SkillDetail renderer for detail panel
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/detail/SkillDetail.tsx` — narrow panel renderer for individual skills:
|
||||
- Skill header: 20px name, frequency badge (accent-light), status badge (success/neutral)
|
||||
- Category label: 11px uppercase tertiary text (Technical / Healthcare Domain / Strategic & Leadership)
|
||||
- Proficiency bar: 6px height, color-coded (green >=90%, teal >=75%, amber <75%), percentage label
|
||||
- Experience section: large year number (28px) + "years" + "Since YYYY" (Geist Mono)
|
||||
- "Used in" section: lists roles from constellation data (roleSkillMappings), with org-colored dots, role labels, organization + date range
|
||||
- Updated `src/components/DetailPanel.tsx`:
|
||||
- Added import for SkillDetail
|
||||
- Added `content.type === 'skill'` rendering branch
|
||||
- Narrowed placeholder fallback to exclude 'skill' type
|
||||
**Learnings:**
|
||||
- Constellation data provides the skill-to-role mapping via `roleSkillMappings` — filter by skill ID, then look up role nodes for display
|
||||
- Role nodes sorted chronologically (earliest first) gives a natural career progression view
|
||||
- The non-null assertions on `node!` are safe because the `.filter(Boolean)` ensures no nulls
|
||||
- Pre-existing lint error (`_sectionId` in DashboardLayout:64) is unrelated to this work
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 20 — US-021: Create SkillsAllDetail renderer for detail panel
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/detail/SkillsAllDetail.tsx` — narrow panel renderer for full categorised skill list:
|
||||
- Groups all 21 skills by Technical / Healthcare Domain / Strategic & Leadership
|
||||
- Category headers match CoreSkillsTile style: 10px uppercase label + divider line + item count (Geist Mono)
|
||||
- Each skill row: icon container (26px, accent-light), name + frequency/years (Geist Mono), mini proficiency bar (40px wide, color-coded), percentage, chevron
|
||||
- Skill rows clickable → `openPanel({ type: 'skill', skill })` to switch panel to individual SkillDetail
|
||||
- If opened with category filter (from "View all" button), scrolls to and highlights that category (accent-colored header + bottom border)
|
||||
- Hover: border color shift + shadow deepens (matching CoreSkillsTile rows)
|
||||
- Keyboard: Enter/Space triggers skill detail, role="button", tabIndex={0}, descriptive aria-label
|
||||
- Updated `src/components/DetailPanel.tsx`:
|
||||
- Added import for SkillsAllDetail
|
||||
- Added `content.type === 'skills-all'` rendering branch with category prop pass-through
|
||||
- Narrowed placeholder fallback to exclude 'skills-all' type
|
||||
**Learnings:**
|
||||
- Reused the SkillRow pattern from CoreSkillsTile but added a mini proficiency bar instead of status badge — provides more info density in the "view all" context
|
||||
- The `useRef<Record<string, HTMLDivElement | null>>` pattern with callback ref works well for multiple dynamic refs
|
||||
- Category highlight uses both accent-colored text and a 2px bottom border to visually distinguish the filtered category
|
||||
- Pre-existing lint error (`_sectionId` in DashboardLayout:64) continues to be unrelated
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 21 — US-022: Create EducationDetail renderer for detail panel
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/detail/EducationDetail.tsx` — narrow panel renderer for education entries:
|
||||
- Header: type-specific icon (GraduationCap/Award/BookOpen/FlaskConical) + title + institution (purple accent) + duration + classification badge (purple-light bg)
|
||||
- Research Project section: renders `extra.researchDescription` for MPharm entry
|
||||
- OSCE Performance section: renders score in success-colored badge with description
|
||||
- Extracurricular Activities section: bullet list from `extra.extracurriculars`
|
||||
- Programme Overview section: renders `extra.programmeDetail` for Mary Seacole
|
||||
- Notes section: italic secondary text from `document.notes`
|
||||
- All sections use shared `sectionHeaderStyle` (12px uppercase, secondary color, 0.05em tracking)
|
||||
- Updated `src/components/DetailPanel.tsx`:
|
||||
- Added import for EducationDetail
|
||||
- Added `content.type === 'education'` rendering branch
|
||||
- Narrowed placeholder fallback to exclude 'education' type
|
||||
**Learnings:**
|
||||
- Icon type for lucide-react must use `LucideIcon` type, not `React.ComponentType<{ size: number }>` — the latter causes type incompatibility with ForwardRefExoticComponent
|
||||
- The `educationExtras` data matches documents by `documentId` field — currently only MPharm and Mary Seacole have extras
|
||||
- Purple color (#7C3AED) is used consistently for education across the app (dot colors in CardHeader, CareerActivity, and now EducationDetail institution text and classification badge)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 22 — US-023: Install D3 and scaffold CareerConstellation component
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Installed `d3` and `@types/d3` npm packages (70 packages added)
|
||||
- Created `src/components/CareerConstellation.tsx` — scaffolded component with:
|
||||
- Props: `onRoleClick(id)` and `onSkillClick(id)` stored in callbacksRef for future D3 event binding
|
||||
- Responsive SVG container using ResizeObserver: 400px desktop, 300px tablet (<1024px), 250px mobile (<768px)
|
||||
- viewBox matches actual dimensions for responsive scaling
|
||||
- Radial gradient background: `#F0F5F4` (--bg-dashboard) center → `#FFFFFF` (--surface) edge, rx=6
|
||||
- Placeholder text showing node/link counts from constellation data (Geist Mono, tertiary color)
|
||||
- Container with border-radius and overflow hidden
|
||||
- SVG has `role="img"` and `aria-label` for accessibility
|
||||
- Imperative SVG drawing via useEffect on svgRef (matches ECG pattern for D3 compatibility)
|
||||
**Learnings:**
|
||||
- `callbacksRef` pattern stores click handlers in a ref for D3 imperative code — avoids stale closures when D3 attaches event listeners in US-024/026
|
||||
- ResizeObserver provides cleaner responsive behavior than CSS media queries for SVG — container width determines height tier
|
||||
- The SVG namespace `http://www.w3.org/2000/svg` is required for createElement in imperative SVG building
|
||||
- D3 is installed but not yet imported — US-024 will use `d3.forceSimulation` etc. on the svgRef
|
||||
- Pre-existing lint error (`_sectionId` in DashboardLayout:64) continues to be unrelated
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — component not yet integrated into CareerActivityTile (will be wired in US-026).
|
||||
|
||||
### Iteration 23 — US-024: Build D3 force-directed graph rendering in CareerConstellation
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Rewrote `src/components/CareerConstellation.tsx` to use D3 force simulation:
|
||||
- Replaced imperative SVG createElement with D3 selections (`d3.select`, `.selectAll`, `.join`)
|
||||
- D3 force simulation with: `forceManyBody(-200)`, `forceLink(distance 80, strength from data * 0.5)`, `forceX` chronological (roles positioned left-to-right by `startYear` via `d3.scaleLinear`), `forceY` centered at `height/2`, `forceCollide` (30 for roles, 14 for skills)
|
||||
- Role nodes: 24px radius circles filled with `orgColor`, 2px white stroke, 8px white `shortLabel` text centered
|
||||
- Skill nodes: 10px radius circles, color-coded by domain (clinical=#059669 green, technical=#0D6E6E teal, leadership=#D97706 amber), 1.5px white stroke, opacity 0.85
|
||||
- Skill labels: 9px Geist Mono text below each skill node (using `shortLabel`)
|
||||
- Links: 1px `#D4E0DE` lines at opacity 0.3
|
||||
- Node positions constrained within SVG bounds on each tick
|
||||
- Layered rendering: links group below nodes group
|
||||
- `simulationRef` stores active simulation, stopped on cleanup or dimension change
|
||||
- Preserved existing ResizeObserver responsive height (400/300/250px)
|
||||
- Preserved radial gradient background, `role="img"`, `aria-label`
|
||||
- Removed unused `ConstellationLink` type import (caught by typecheck)
|
||||
**Learnings:**
|
||||
- D3 `forceLink.strength()` receives the link object — cast to `SimLink` to access `.strength` field
|
||||
- Role `forceX` uses strong pull (0.8) to maintain chronological layout; skill `forceX` uses weak pull (0.05) to let links drive position
|
||||
- `forceCollide` radius should be slightly larger for skills than their visual radius to prevent label overlap
|
||||
- The `SimNode` interface extending `ConstellationNode` with `x/y/vx/vy/fx/fy` satisfies D3's `SimulationNodeDatum` needs
|
||||
- Pre-existing lint issues: `_sectionId` error + 2 context warnings — all unrelated
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — component not yet wired into CareerActivityTile (US-026). D3 simulation verified via successful build.
|
||||
|
||||
### Iteration 24 — US-025: Add accessibility to CareerConstellation
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/CareerConstellation.tsx` with four accessibility features:
|
||||
- **Screen-reader description**: `buildScreenReaderDescription()` generates a hidden `<p>` (sr-only via clip rect) describing all 5 roles, their organizations, year ranges, and associated skills from `roleSkillMappings`
|
||||
- **Keyboard navigation**: Hidden `<button>` elements overlaid on the SVG container, one per role node. Tab navigates through roles, Enter/Space triggers `onRoleClick`. Each button has descriptive `aria-label` (role name, org, year range)
|
||||
- **Focus indicators**: SVG `.focus-ring` circle (ROLE_RADIUS + 4px) rendered behind each role node. Transparent by default, becomes teal `#0D6E6E` stroke when the corresponding hidden button receives focus (tracked via `focusedNodeId` state + `useEffect` on D3 selection)
|
||||
- **prefers-reduced-motion**: When enabled, simulation runs 300 ticks synchronously (`simulation.stop()` + loop), then renders final positions immediately — no animation frames. Uses the established module-scope `matchMedia` check pattern
|
||||
- Imported `roleSkillMappings` from constellation data for SR description
|
||||
- Added `useCallback` for `handleNodeKeyDown` to prevent re-renders
|
||||
**Learnings:**
|
||||
- D3 focus indicators work via a dual approach: hidden HTML buttons for actual keyboard focus, plus D3-drawn SVG circles that respond to React state changes — avoids fighting D3's imperative model with React's declarative focus management
|
||||
- Running `simulation.tick()` in a loop (300 iterations) is sufficient to reach stable positions for this graph size (5 roles + 21 skills)
|
||||
- The `.focus-ring` circle must be appended before the main circle in the SVG group to render behind it (SVG painting order = DOM order)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — not yet wired into CareerActivityTile (US-026).
|
||||
|
||||
### Iteration 25 — US-026: Add hover and click interactions to CareerConstellation
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/CareerConstellation.tsx` with three interaction features:
|
||||
- **Hover highlighting**: Built adjacency map from `constellationLinks`. On `mouseenter`, non-connected nodes fade to 0.15 opacity. Connected links brighten to teal (`#0D6E6E`), thicken to 2px, increase opacity to 0.7. Non-connected links dim to 0.1 opacity. Role hover also scales connected skill nodes up (+3px radius) via D3 transition (150ms).
|
||||
- **Hover reset**: On `mouseleave`, all nodes reset to full opacity, skill circles return to `SKILL_RADIUS`, links return to default stroke/opacity/width.
|
||||
- **Click handlers**: Click on any node calls `callbacksRef.current.onRoleClick(id)` or `onSkillClick(id)` via the existing callbacksRef pattern (avoids stale closures).
|
||||
- Added `.node-circle` and `.node-label` classes to circles/text for targeted D3 selections during hover
|
||||
- Updated `src/components/tiles/CareerActivityTile.tsx`:
|
||||
- Replaced placeholder `<div>` with actual `<CareerConstellation>` component
|
||||
- Added `handleRoleClick(roleId)` → finds consultation by ID → `openPanel({ type: 'career-role', consultation })`
|
||||
- Added `handleSkillClick(skillId)` → finds skill by ID → `openPanel({ type: 'skill', skill })`
|
||||
- Refactored `handleItemClick` to delegate to `handleRoleClick` for consistency
|
||||
- Imported `skills` from `@/data/skills` and `CareerConstellation` from `../CareerConstellation`
|
||||
**Learnings:**
|
||||
- D3 hover uses `mouseenter`/`mouseleave` (not `mouseover`/`mouseout`) to avoid bubbling issues with nested SVG groups
|
||||
- The adjacency map uses source/target strings from `constellationLinks` (pre-simulation), not SimNode objects — link data gets resolved by D3 after forceLink runs, so during hover the source/target may be either string or SimNode objects. The click/hover handlers check both forms.
|
||||
- The `callbacksRef` pattern established in US-023 works perfectly for D3 click events — no stale closures
|
||||
- Pre-existing lint issues: `_sectionId` error + 2 context warnings — all unrelated
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 26 — US-027: Restyle LoginScreen with teal accents
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/LoginScreen.tsx`:
|
||||
- Replaced all `#005EB8` (NHS Blue) with `#0D6E6E` (teal accent): shield icon color, active field borders, cursor color, button default bg, focus ring
|
||||
- Replaced `#004D9F` (hover) with `#0A8080` (teal hover)
|
||||
- Replaced `#004494` (pressed) with `#085858` (teal pressed)
|
||||
- Background color: `#1E293B` → `#1A2B2A` (warmer, cohesive with dashboard palette)
|
||||
- Shield icon container: `rgba(0, 94, 184, 0.07)` → `rgba(13, 110, 110, 0.08)` (teal-tinted)
|
||||
**Learnings:**
|
||||
- LoginScreen had 6 instances of `#005EB8` — all replaced for consistency
|
||||
- The background change from `#1E293B` (slate) to `#1A2B2A` (dark teal-green) creates visual cohesion with the teal accent palette
|
||||
- Button states follow the teal gradient: default #0D6E6E → hover #0A8080 → pressed #085858 (progressively darker)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
---
|
||||
|
||||
## 2026-02-14 - US-028
|
||||
- **What was implemented:** Changed login username from A.CHARLWOOD to a.recruiter, added connection status indicator with red→green transition, updated button disabled logic to require both typing complete AND connection established.
|
||||
- **Files changed:**
|
||||
- `src/components/LoginScreen.tsx` — new `connectionState` state, connection timer (2000ms), connection status indicator UI (6px dot + Geist Mono text), `canLogin` derived state replacing `typingComplete` for button control
|
||||
- `src/components/DashboardLayout.tsx` — fixed pre-existing lint error (unused `_sectionId` parameter, added eslint-disable comment)
|
||||
- **Learnings for future iterations:**
|
||||
- The DashboardLayout had a pre-existing lint error with `_sectionId` — ESLint config doesn't respect underscore-prefix unused var convention, needed `eslint-disable-next-line` comment. TypeScript `tsc -b` (used in build) DOES respect underscore prefix though.
|
||||
- Connection status uses CSS `transition: 300ms` for the color change — matches the spec for smooth dot/text color transition
|
||||
- `canLogin` is a derived value (not state) combining `typingComplete && connectionState === 'connected'` — cleaner than adding another state variable
|
||||
|
||||
---
|
||||
|
||||
## 2026-02-14 - US-029
|
||||
- **What was implemented:** Added post-login loading state with CSS spinner (~600ms) that replaces the login card content after clicking Log In. Updated TopBar session display name from "Dr. A.CHARLWOOD" to "A.RECRUITER".
|
||||
- **Files changed:**
|
||||
- `src/components/LoginScreen.tsx` — new `isLoading` state, handleLogin now sets isLoading before isExiting, card content conditionally renders either login form or spinner + "Loading clinical records..." text. Spinner uses CSS `login-spin` animation.
|
||||
- `src/components/TopBar.tsx` — changed session name from "Dr. A.CHARLWOOD" to "A.RECRUITER"
|
||||
- `src/index.css` — added `@keyframes login-spin` and `.login-spinner` class, plus `prefers-reduced-motion` override (static indicator, no spin)
|
||||
- **Learnings for future iterations:**
|
||||
- The loading state replaces card content via conditional rendering (`isLoading ? spinner : form`) rather than an overlay — keeps the card dimensions stable
|
||||
- The sequence is: buttonPressed (100ms) → isLoading (600ms) → isExiting (200ms) → onComplete. With reduced motion, loading and exit delays are 0ms.
|
||||
- Spinner uses pure CSS animation (`border-top-color` trick) — no library needed
|
||||
|
||||
---
|
||||
|
||||
## 2026-02-14 - US-030
|
||||
- **What was implemented:** Updated CommandPalette search index to include all 21 skills (not just 5), added `panel` action type to PaletteAction union, and wired skill/KPI/project palette results to open detail panels directly.
|
||||
- **Files changed:**
|
||||
- `src/lib/search.ts` — Added `panel` action type with `DetailPanelContent` payload. Skills section now iterates all 21 skills from `skills.ts` (was hardcoded to 5). Project results find matching `Investigation` by ID and use `panel` action. Achievement results find matching `KPI` by ID and use `panel` action. Imported `kpis` and `DetailPanelContent` type.
|
||||
- `src/components/DashboardLayout.tsx` — Added `panel` case to `handlePaletteAction` switch that calls `openPanel(action.panelContent)`. Imported `useDetailPanel` from context.
|
||||
- **Learnings for future iterations:**
|
||||
- The `panel` action type carries a full `DetailPanelContent` discriminated union payload — this means any palette item can open any detail panel type without intermediate mapping
|
||||
- Achievement "Team of 12 Led" was updated to "1.2M Population Served" to match the KPI data change from US-006
|
||||
- For projects, a fallback to `scroll` action is used when the investigation ID doesn't match — defensive pattern for data mismatches
|
||||
|
||||
---
|
||||
|
||||
## 2026-02-14 - US-031
|
||||
- **What was implemented:** Responsive testing and fixes for all new components. Audited DetailPanel, SubNav, CareerConstellation, dashboard grid, CoreSkillsTile, touch targets, and 375px overflow.
|
||||
- **Files changed:**
|
||||
- `src/components/SubNav.tsx` — Added `overflowX: auto`, `scrollbarWidth: 'none'`, horizontal padding, `flexShrink: 0` on tab buttons, `minHeight: 36px` for touch targets, flex layout for vertical centering
|
||||
- `src/index.css` — Added `.subnav-scroll::-webkit-scrollbar { display: none }` for WebKit scrollbar hiding
|
||||
- `src/components/DetailPanel.tsx` — Enlarged close button from 32x32px to 44x44px for mobile touch target compliance
|
||||
- `src/components/tiles/CoreSkillsTile.tsx` — Added `minHeight: 44px` to SkillRow and "View all" button for touch target compliance
|
||||
- `src/components/tiles/ProjectsTile.tsx` — Added `minHeight: 44px` to ProjectItem for touch target compliance
|
||||
- `src/components/tiles/LastConsultationTile.tsx` — Added `minHeight: 44px` to "View full record" button
|
||||
- **Audit results (already passing):**
|
||||
- DetailPanel: `@media (max-width: 767px)` already set both widths to 100vw ✓
|
||||
- CareerConstellation: `getHeight()` already returns 400/300/250px by breakpoint ✓
|
||||
- Dashboard grid: mobile-first 1fr → 2fr at 768px, KPIs + Projects stack correctly ✓
|
||||
- CoreSkillsTile: `full` prop spans both columns at all breakpoints ✓
|
||||
- No horizontal overflow at 375px: TopBar search hidden <768px, no problematic nowrap on wide content ✓
|
||||
- **Learnings for future iterations:**
|
||||
- `scrollbarWidth: 'none'` (Firefox) + `::-webkit-scrollbar { display: none }` (Chrome/Safari) together hide scrollbars cross-browser
|
||||
- WCAG touch target minimum is 44x44px — check all `role="button"`, `<button>`, and clickable elements
|
||||
- SubNav at 375px has ~345px available (375 - 2*16px padding) — 5 short labels with 24px gaps fit without scroll, but the scroll fallback is good insurance
|
||||
|
||||
## 2026-02-14 — US-032
|
||||
- **What was implemented:** Reduced motion audit, final cleanup, and visual review
|
||||
- **Files changed:**
|
||||
- `src/index.css` — Added prefers-reduced-motion overrides for SubNav button transitions and smooth scroll behavior. Removed 18 unused `--pmr-*` legacy CSS variables and `.pmr-theme` utility class.
|
||||
- `src/components/LoginScreen.tsx` — Connection status dot and text transitions now respect `prefersReducedMotion` (instant when enabled).
|
||||
- `src/components/detail/ProjectDetail.tsx` — Created missing ProjectDetail renderer (project name, year, status badge, methodology, tech stack tags, results bullets, external link button).
|
||||
- `src/components/DetailPanel.tsx` — Wired ProjectDetail for `content.type === 'project'`. Removed placeholder fallback (all content types now have renderers).
|
||||
- Deleted `src/hooks/useBreakpoint.ts` (unused)
|
||||
- Deleted `src/data/profile.ts` (unused — PatientSummaryTile has profile text hardcoded)
|
||||
- **Learnings for future iterations:**
|
||||
- ProjectDetail was missing despite US-019 being marked as passed — always verify file existence, not just PRD status
|
||||
- `profile.ts` was created but never imported — PatientSummaryTile hardcodes the profile text instead
|
||||
- `useBreakpoint.ts` was orphaned after its consumers were deleted in US-001
|
||||
- Legacy `--pmr-*` CSS variables were all superseded by the new design token system and safe to remove
|
||||
- `pmr-scrollbar` class is still actively used (Sidebar, DashboardLayout, CommandPalette) — do not remove
|
||||
- SubNav inline transitions need CSS `!important` override in prefers-reduced-motion since they're set via inline styles
|
||||
- The `html { scroll-behavior: smooth }` also needs a reduced-motion override to `auto`
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-001
|
||||
- **What was implemented:** Changed initial Phase state in App.tsx from `'boot'` to `'pmr'` to skip boot/ECG/login animation during dev iteration.
|
||||
- **Files changed:** `src/App.tsx` — single line change (line 47)
|
||||
- **Learnings for future iterations:**
|
||||
- This is a temporary change — US-013 will revert it back to `'boot'` as the final story
|
||||
- All boot/ECG/login components remain in code, only the initial state changes
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-002
|
||||
- **What was implemented:** Added fluid root font-size scaling via `clamp()` on the `html` element, updated body font-size to `1rem`, and converted all layout CSS custom properties from px to rem.
|
||||
- **Files changed:**
|
||||
- `src/index.css` — Added `html { font-size: clamp(15px, calc(8px + 0.3646vw), 22px); }`. Changed `body { font-size: 15px }` → `font-size: 1rem`. Converted `--topbar-height` (48px → 3.2rem), `--sidebar-width` (272px → 18.133rem), `--subnav-height` (36px → 2.4rem), `--panel-narrow` (400px → 26.667rem).
|
||||
- **Learnings for future iterations:**
|
||||
- Clamp formula derivation: slope = (22-15)/(3840-1920) = 7/1920 ≈ 0.3646vw. Intercept: 15 - 0.3646*19.2 = 8px. Result: `clamp(15px, calc(8px + 0.3646vw), 22px)`. Hits exactly 15px at 1920vw and 22px at 3840vw.
|
||||
- Border-radius (`--radius-card`, `--radius-sm`) and filter values (`--backdrop-blur`) stay in px — they don't need to scale with viewport.
|
||||
- `--panel-wide: 60vw` already uses viewport units — no conversion needed.
|
||||
- rem base is 15px at 1920px. Conversion: px / 15 = rem. All Tailwind utility classes already use rem internally, so they auto-scale with the root font-size.
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-003
|
||||
- **What was implemented:** Converted all inline px values in Card.tsx, SubNav.tsx, and DetailPanel.tsx to rem. DashboardLayout.tsx had no inline px values to convert (uses CSS variables and Tailwind classes which already use rem).
|
||||
- **Files changed:**
|
||||
- `src/components/Card.tsx` — padding 20px→1.333rem, CardHeader gap 8px→0.533rem, marginBottom 16px→1.067rem, dot width/height 8px→0.533rem, title fontSize 12px→0.8rem, rightText fontSize 10px→0.667rem
|
||||
- `src/components/SubNav.tsx` — nav gap 24px→1.6rem, padding 0 16px→0 1.067rem, button fontSize 13px→0.867rem, button padding 0 4px 2px→0 0.267rem 0.133rem, minHeight 36px→2.4rem
|
||||
- `src/components/DetailPanel.tsx` — header padding 20px 24px→1.333rem 1.6rem, title container gap 8px→0.533rem, dot width/height 8px→0.533rem, title fontSize 14px→0.933rem, close button width/height 44px→2.933rem, body padding 24px→1.6rem
|
||||
- **Learnings for future iterations:**
|
||||
- DashboardLayout.tsx already uses CSS variables (--topbar-height, --subnav-height, --sidebar-width) which were converted to rem in US-002, and Tailwind responsive classes which inherently use rem — no inline px values to convert
|
||||
- Framer Motion variant values like `y: -48` and `x: -272` are animation transform offsets (not layout units) — these are number values that Framer interprets as px. They only affect animation start position and don't need rem conversion
|
||||
- Border-radius values (e.g., `var(--radius)`, `var(--radius-sm)`, `'50%'`) stay as-is — border-radius doesn't benefit from scaling
|
||||
- The `height: '2px'` active indicator bar in SubNav stays in px — decorative border elements don't need scaling
|
||||
- At 1920px viewport, 1rem = 15px, so all rem values compute to identical px values as before — verified via Playwright screenshot
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-004
|
||||
- **What was implemented:** Converted all inline px values in TopBar.tsx and Sidebar.tsx (including sub-components SectionTitle, TagPill, AlertFlag) to rem.
|
||||
- **Files changed:**
|
||||
- `src/components/TopBar.tsx` — 21 px→rem conversions: header padding, skip-link positioning/padding/fontSize, brand fontSize (x2), "Remote" fontSize/marginLeft, search bar maxWidth/minWidth/height/padding, search placeholder fontSize, kbd fontSize/padding, username fontSize, session badge fontSize/padding (x2 variants)
|
||||
- `src/components/Sidebar.tsx` — ~40 px→rem conversions across: aside padding/gap, PersonHeader (avatar size/fontSize/marginBottom, name fontSize, title fontSize/marginTop, status badge gap/marginTop/fontSize/padding, pulse dot size, details grid gap/marginTop, detail rows fontSize/padding x6, GPhC fontSize), SectionTitle (gap/fontSize/marginBottom), TagPill (fontSize/padding), AlertFlag (gap/fontSize/padding, icon container size), Tags/Alerts sections (padding/gap)
|
||||
- **Learnings for future iterations:**
|
||||
- Sidebar has the highest density of inline px values in the project (~40 conversions) — all sub-components (SectionTitle, TagPill, AlertFlag) are defined in the same file
|
||||
- Box-shadow values like `'0 2px 8px rgba(...)'` stay as-is — shadow px values don't benefit from scaling
|
||||
- Lucide icon `size` and `strokeWidth` props are numbers, not CSS units — they stay unchanged
|
||||
- `letterSpacing` values in `em` (e.g., '0.08em', '0.12em') stay as-is — em is already relative to font-size
|
||||
- The skip-link onFocus/onBlur handlers also had a `-40px` value that needed converting to `-2.667rem`
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-005
|
||||
- **What was implemented:** Converted all inline px values in PatientSummaryTile.tsx, LastConsultationTile.tsx, CareerActivityTile.tsx, and ConsultationDetail.tsx to rem.
|
||||
- **Files changed:**
|
||||
- `src/components/tiles/PatientSummaryTile.tsx` — 8 conversions: grid minmax 140px→9.333rem, gaps 12px→0.8rem, marginBottom/paddingBottom 20px→1.333rem, detail gaps 2px→0.133rem, fontSizes 18px→1.2rem, 11px→0.733rem, 13px→0.867rem
|
||||
- `src/components/tiles/LastConsultationTile.tsx` — ~21 conversions: metadata grid gaps/padding, fontSizes (10px→0.667rem, 11.5px→0.767rem, 13.5px→0.9rem, 12.5px→0.833rem, 12px→0.8rem), bullet dots 5px→0.333rem, button minHeight 44px→2.933rem, section gaps/margins
|
||||
- `src/components/tiles/CareerActivityTile.tsx` — ~12 conversions: entry fontSizes 12px→0.8rem, gaps/padding, dot 8px→0.533rem, meta fontSize 11px→0.733rem, date fontSize 10px→0.667rem, constellation marginBottom 20px→1.333rem
|
||||
- `src/components/detail/ConsultationDetail.tsx` — ~13 conversion groups: outer gap 24px→1.6rem, role title fontSize 20px→1.333rem, org fontSize 14px→0.933rem, section headers 12px→0.8rem/marginBottom 8px→0.533rem, list paddingLeft 20px→1.333rem, coded entries padding/gap/fontSize conversions
|
||||
- **Learnings for future iterations:**
|
||||
- ConsultationDetail.tsx has the most repetitive patterns — same styled sections (role summary, key achievements, clinical notes, coded entries) with identical fontSize/margin patterns repeated 4+ times
|
||||
- Grid `minmax()` values like `minmax(140px, 1fr)` need rem conversion too — they control responsive column widths
|
||||
- `lineHeight` numeric values (e.g., 1.3, 1.5) are unitless multipliers and don't need conversion
|
||||
- Bullet dot sizes (5px→0.333rem) are small enough that they could stay in px, but converting ensures consistency with the scaling approach
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-006
|
||||
- **What was implemented:** Converted all inline px values to rem in the remaining 4 tile components and 5 detail panel components: CoreSkillsTile, LatestResultsTile, EducationTile, ProjectsTile, SkillDetail, SkillsAllDetail, KPIDetail, EducationDetail, ProjectDetail.
|
||||
- **Files changed:**
|
||||
- `src/components/tiles/CoreSkillsTile.tsx` — ~20 conversions: SkillRow gap/padding/minHeight, icon container 26px→1.733rem, skill name/frequency fontSizes, status badge, CategorySection marginTop, category header gap/marginBottom/fontSize, skill rows gap, view all button gap/marginTop/padding/minHeight/fontSize
|
||||
- `src/components/tiles/LatestResultsTile.tsx` — 7 conversions: MetricCard padding, value fontSize 28px→1.867rem, label fontSize/marginTop, sub fontSize/marginTop, grid gap
|
||||
- `src/components/tiles/EducationTile.tsx` — 11 conversions: entry list gap, button padding/fontSize, title row gap/marginBottom, title/year/institution/detail fontSizes, detail gap
|
||||
- `src/components/tiles/ProjectsTile.tsx` — 12 conversions: ProjectItem padding/minHeight/fontSize, row gap/marginBottom, dot size/marginTop, year fontSize, tech stack gap, tech tag fontSize/padding, list gap
|
||||
- `src/components/detail/SkillDetail.tsx` — ~27 conversions: outer gap, skill name fontSize/marginBottom, badge padding/fontSize, category fontSize, section h3s, proficiency bar gap/text fontSize/minWidth, experience number/years/since fontSize, role list/item gap/padding, role dot/name/org
|
||||
- `src/components/detail/SkillsAllDetail.tsx` — ~13 conversions: outer gap, category header gap/marginBottom/paddingBottom/fontSize, skill rows gap, SkillRow gap/padding, icon size, name/frequency fontSize, proficiency bar width/gap/text fontSize/minWidth
|
||||
- `src/components/detail/KPIDetail.tsx` — ~15 conversions: fallback fontSize/value/marginBottom, outer gap, headline fontSize 48px→3.2rem, label/sub fontSize, period badge, section h3s, body text, outcomes list/items
|
||||
- `src/components/detail/EducationDetail.tsx` — ~16 conversions: sectionHeaderStyle, outer gap, header icon row, icon container 36px→2.4rem, title fontSize, institution fontSize, date row, classification badge, body text, OSCE container/value/label, lists/items
|
||||
- `src/components/detail/ProjectDetail.tsx` — ~15 conversions: outer gap, header row, year/status fontSize/padding, clinician fontSize, section h3s, body text, tech stack gap/tags, results list/items, external link gap/padding/fontSize
|
||||
- **Learnings for future iterations:**
|
||||
- All rem conversion stories (US-003 through US-006) are now complete — every component in the app uses rem for layout/typography values
|
||||
- Detail panel components share a common pattern: sectionHeaderStyle with fontSize 12px→0.8rem, marginBottom 8px→0.533rem, uppercase, letterSpacing 0.05em — this is repeated across KPIDetail, EducationDetail, ProjectDetail, SkillDetail
|
||||
- Progress bar heights (4px, 6px) and divider heights (1px) were left in px — these are decorative elements that don't benefit from scaling
|
||||
- The largest value converted was 48px→3.2rem (KPI headline in KPIDetail)
|
||||
---
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
{
|
||||
"project": "Portfolio — Typography & Spacing Scale Rework",
|
||||
"branchName": "ralph/dashboard-restructure",
|
||||
"description": "Rework the entire type scale, spatial tokens, and layout proportions so the dashboard reads comfortably on a 2560x1440 QHD display. Everything is currently undersized — body text at 13px, labels at 9-10px, sidebar details at 11px. Scale up proportionally with larger type, wider sidebar, taller topbar, and more generous spacing.",
|
||||
"userStories": [
|
||||
{
|
||||
"id": "US-018",
|
||||
"title": "Skip boot/login sequence for dev iteration",
|
||||
"description": "As a developer, I want to skip the boot/ECG/login animation during this feature work so I can iterate on dashboard sizing quickly.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/App.tsx, change the initial Phase state from 'boot' to 'pmr' so the app loads directly to the dashboard",
|
||||
"The boot, ECG, and login phases remain in code — only the initial state changes",
|
||||
"App loads directly to the dashboard layout on refresh",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 1,
|
||||
"passes": true,
|
||||
"notes": "Temporary — final story reverts this."
|
||||
},
|
||||
{
|
||||
"id": "US-019",
|
||||
"title": "Update global layout tokens and Card/CardHeader component",
|
||||
"description": "As a viewer, I want the foundational layout dimensions and card sizing to be larger so everything downstream inherits better proportions.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/index.css, update --sidebar-width from 272px to 304px",
|
||||
"Update --topbar-height from 48px to 56px",
|
||||
"Update --subnav-height from 36px to 42px",
|
||||
"Update .dashboard-grid gap from 12px (mobile) to 14px, and 16px (tablet/desktop) to 20px",
|
||||
"In src/components/Card.tsx, update Card padding from 20px to 24px",
|
||||
"In Card.tsx CardHeader: title fontSize from 12px to 13px",
|
||||
"CardHeader right text fontSize from 10px to 11px",
|
||||
"CardHeader dot size from 8px to 9px",
|
||||
"CardHeader marginBottom from 16px to 18px",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 2,
|
||||
"passes": true,
|
||||
"notes": "These are foundational tokens — many components reference --sidebar-width, --topbar-height, --subnav-height via CSS vars, so changes propagate automatically. Card.tsx changes affect every card and subsection header in the dashboard."
|
||||
},
|
||||
{
|
||||
"id": "US-020",
|
||||
"title": "Scale TopBar and SubNav",
|
||||
"description": "As a viewer, I want the TopBar and SubNav to feel substantial rather than thin strips across the top of the screen.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/components/TopBar.tsx: brand text fontSize from 13px to 15px",
|
||||
"TopBar 'Remote' label fontSize from 11px to 12px",
|
||||
"Search bar text fontSize from 13px to 14px",
|
||||
"Search bar height from 42px to 46px",
|
||||
"Ctrl+K kbd fontSize from 10px to 11px",
|
||||
"Session 'A.RECRUITER' text fontSize from 12px to 13px",
|
||||
"Session badge fontSize from 11px to 12px",
|
||||
"Skip-to-content link fontSize from 13px to 14px",
|
||||
"Home icon size from 18 to 20",
|
||||
"Search icon size from 16 to 17",
|
||||
"In src/components/SubNav.tsx: tab fontSize from 13px to 14px",
|
||||
"SubNav minHeight from 36px to 42px (matching the updated --subnav-height)",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 3,
|
||||
"passes": true,
|
||||
"notes": "TopBar and SubNav heights are controlled by CSS vars updated in US-019. This story just scales the internal text and elements. The search bar min/max widths may also need slight adjustment — use judgement."
|
||||
},
|
||||
{
|
||||
"id": "US-021",
|
||||
"title": "Scale Sidebar proportions",
|
||||
"description": "As a viewer, I want the sidebar text and spacing to be comfortably readable rather than tiny and cramped.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/components/Sidebar.tsx: sidebar padding from '20px 16px' to '24px 20px'",
|
||||
"Avatar size from 52px to 60px, font size from 18px to 20px",
|
||||
"Name fontSize from 15px to 17px",
|
||||
"Job title fontSize from 11.5px to 13px",
|
||||
"Status badge fontSize from 11px to 12px, dot from 6px to 7px",
|
||||
"All detail row fontSize from 11.5px to 13px (labels and values)",
|
||||
"GPhC mono number fontSize from 11px to 12px",
|
||||
"SectionTitle component fontSize from 10px to 11px",
|
||||
"TagPill fontSize from 10.5px to 12px, padding from '3px 8px' to '4px 10px'",
|
||||
"AlertFlag fontSize from 11px to 13px, padding from '7px 10px' to '8px 12px'",
|
||||
"AlertFlag icon size from 14 to 16, container from 16px to 18px",
|
||||
"Detail row padding from '2px 0' to '4px 0', gap from 6px to 8px",
|
||||
"PersonHeader section marginBottom from 6px to 10px",
|
||||
"Tags and Alerts section padding adjusted proportionally",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 4,
|
||||
"passes": true,
|
||||
"notes": "Sidebar width is already updated via CSS var in US-019. This story handles all internal sizing. The sidebar has many repeated patterns (detail rows) — ensure consistency across all rows."
|
||||
},
|
||||
{
|
||||
"id": "US-022",
|
||||
"title": "Scale PatientSummaryTile content and KPIs",
|
||||
"description": "As a viewer, I want the Patient Summary to feel like the commanding hero section of the dashboard with comfortable body text and prominent KPIs.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/components/tiles/PatientSummaryTile.tsx: profile text fontSize from 13px to 15px, lineHeight from 1.6 to 1.65",
|
||||
"KPI MetricCard value fontSize from 28px to 34px",
|
||||
"KPI label fontSize from 12px to 14px",
|
||||
"KPI sublabel fontSize from 10px to 12px",
|
||||
"KPI card padding from 16px to 20px",
|
||||
"KPI grid gap from 12px to 16px",
|
||||
"Latest Results section marginTop from 24px to 28px",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 5,
|
||||
"passes": true,
|
||||
"notes": "The profile text is the largest body copy block in the dashboard — it must feel effortlessly readable. KPI values should be the most visually prominent numbers on the page."
|
||||
},
|
||||
{
|
||||
"id": "US-023",
|
||||
"title": "Scale LastConsultationSubsection and main content padding",
|
||||
"description": "As a viewer, I want career consultation details and the overall content area to match the new scale.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/components/DashboardLayout.tsx LastConsultationSubsection: field label fontSize from 10px to 12px",
|
||||
"Field value fontSize from 11.5px to 13px",
|
||||
"Role title fontSize from 13.5px to 15px",
|
||||
"Examination bullet fontSize from 12.5px to 14px",
|
||||
"Bullet dot top offset adjusted for new line height",
|
||||
"View full record button fontSize from 12px to 13px, ChevronRight size from 14 to 15",
|
||||
"In DashboardLayout main content area: update className padding from 'p-4 pb-8 md:p-6 md:pb-10 lg:px-7 lg:pt-6 lg:pb-10' to 'p-5 pb-10 md:p-7 md:pb-12 lg:px-8 lg:pt-7 lg:pb-12'",
|
||||
"LastConsultation field gap from 16px to 20px",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 6,
|
||||
"passes": true,
|
||||
"notes": "LastConsultationSubsection is defined inline in DashboardLayout.tsx (around lines 57-232). The main content padding classes are on the <motion.main> element around line 369."
|
||||
},
|
||||
{
|
||||
"id": "US-024",
|
||||
"title": "Scale ProjectsTile and EducationSubsection",
|
||||
"description": "As a viewer, I want project entries and education entries to be readable and well-spaced.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/components/tiles/ProjectsTile.tsx: ProjectItem fontSize from 11.5px to 13px",
|
||||
"ProjectItem padding from '10px 12px' to '12px 16px'",
|
||||
"Project year label fontSize from 10px to 11px",
|
||||
"Tech stack tag fontSize from 9px to 10px, padding from '2px 6px' to '3px 8px'",
|
||||
"Project list gap from 8px to 10px",
|
||||
"Status dot from 7px to 8px",
|
||||
"In src/components/EducationSubsection.tsx: base fontSize from 12px to 13px",
|
||||
"Education entry padding from '10px 12px' to '12px 16px'",
|
||||
"Education title fontSize from 12.5px to 14px",
|
||||
"Institution fontSize from 11px to 12px",
|
||||
"Education detail fontSize from 10.5px to 12px",
|
||||
"Year label fontSize from 10px to 11px",
|
||||
"Education list gap from 10px to 12px",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 7,
|
||||
"passes": true,
|
||||
"notes": "Both are list-style components with similar patterns. ProjectsTile is a standalone card, EducationSubsection is inside Patient Pathway."
|
||||
},
|
||||
{
|
||||
"id": "US-025",
|
||||
"title": "Scale WorkExperienceSubsection and RepeatMedicationsSubsection",
|
||||
"description": "As a viewer, I want work experience roles and skill entries in the two-column layout to be comfortably readable.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/components/WorkExperienceSubsection.tsx: role title fontSize from 12.5px to 14px",
|
||||
"Organisation fontSize from 11px to 12px",
|
||||
"Duration fontSize from 10px to 11px",
|
||||
"Role item header padding from '10px 12px' to '12px 14px'",
|
||||
"Expanded content examination bullet fontSize from 11.5px to 13px",
|
||||
"Coded entry tag fontSize from 10px to 11px, padding from '2px 6px' to '3px 8px'",
|
||||
"View full record link fontSize from 11px to 12px",
|
||||
"Role list gap from 8px to 10px",
|
||||
"Teal dot from 8px to 9px",
|
||||
"In src/components/RepeatMedicationsSubsection.tsx: skill name fontSize from 12.5px to 14px",
|
||||
"Skill frequency text fontSize from 10.5px to 12px",
|
||||
"Skill status badge fontSize from 10px to 11px, padding from '2px 7px' to '3px 8px'",
|
||||
"Skill row padding from '8px 10px' to '10px 12px'",
|
||||
"Skill icon container from 26px to 30px, icon size from 13 to 15",
|
||||
"Category section label fontSize from 10px to 11px",
|
||||
"Category item count fontSize from 10px to 11px",
|
||||
"View all button fontSize from 11px to 12px",
|
||||
"Skill row gap from 6px to 8px",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 8,
|
||||
"passes": true,
|
||||
"notes": "These two components sit side by side in the Patient Pathway two-column layout. Both are dense with inline style objects. The changes are mechanical — find each fontSize/padding and bump it up."
|
||||
},
|
||||
{
|
||||
"id": "US-026",
|
||||
"title": "Adjust ParentSection headings for new proportions",
|
||||
"description": "As a viewer, I want the parent section headings to remain visually dominant now that body text is larger.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/components/ParentSection.tsx: evaluate whether heading sizes still create clear hierarchy with the scaled-up body text",
|
||||
"Current responsive scale: 1.375rem (22px) → 1.6rem (25.6px) → 1.8rem (28.8px) → 2.2rem (35.2px)",
|
||||
"If body text at 15px makes the heading feel less dominant, bump desktop heading to 2.4rem (38.4px)",
|
||||
"Heading paddingBottom may need to increase from 1.333rem to 1.5rem to maintain spacing proportion",
|
||||
"Use Playwright to verify the heading-to-body ratio feels right at 2560x1440",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 9,
|
||||
"passes": true,
|
||||
"notes": "This is a judgement call — the agent should look at the dashboard after all previous scaling and decide if headings need adjustment. They may be fine as-is, or may need a small bump. Use dev-browser to compare."
|
||||
},
|
||||
{
|
||||
"id": "US-027",
|
||||
"title": "Visual regression check across all breakpoints",
|
||||
"description": "As a developer, I need to verify the scaled dashboard works at all viewport sizes without overflow or layout breaks.",
|
||||
"acceptanceCriteria": [
|
||||
"Use Playwright to verify at 2560x1440 (QHD) — primary target, everything should feel commanding and well-proportioned",
|
||||
"Verify at 1920x1080 (HD) — content should still be comfortable, not oversized",
|
||||
"Verify at 1440x900 — should work without overflow",
|
||||
"Verify at 768px width (tablet) — sidebar hidden, single column, no overflow or truncation",
|
||||
"Verify at 375px width (mobile) — everything stacks, no horizontal scroll",
|
||||
"No horizontal scrollbar at any tested width",
|
||||
"Fix any overflow, wrapping, truncation, or spacing issues discovered during verification",
|
||||
"npm run build succeeds without errors",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 10,
|
||||
"passes": true,
|
||||
"notes": "Use Playwright browser_resize to test each breakpoint. Take screenshots at each size. Fix any issues found — this may involve adding responsive overrides or adjusting min-widths."
|
||||
},
|
||||
{
|
||||
"id": "US-028",
|
||||
"title": "Re-enable boot/login sequence",
|
||||
"description": "As a user, I want the full boot → ECG → login experience restored for production.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/App.tsx, change the initial Phase state back from 'pmr' to 'boot'",
|
||||
"Boot → ECG → Login → Dashboard sequence works end to end",
|
||||
"No other changes to App.tsx beyond reverting the initial state",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill: app starts at boot, progresses through ECG and login, arrives at the scaled dashboard"
|
||||
],
|
||||
"priority": 11,
|
||||
"passes": true,
|
||||
"notes": "Simple revert of US-018."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
# Progress Log — Typography & Spacing Scale Rework
|
||||
# Branch: ralph/dashboard-restructure
|
||||
# Started: 2026-02-14
|
||||
|
||||
## Codebase Patterns
|
||||
|
||||
### Project Structure
|
||||
- Components in `src/components/`, tiles in `src/components/tiles/`
|
||||
- Detail renderers in `src/components/detail/`
|
||||
- Data files in `src/data/`
|
||||
- Types in `src/types/pmr.ts` and `src/types/index.ts`
|
||||
- Hooks in `src/hooks/`, Contexts in `src/contexts/`, Lib in `src/lib/`
|
||||
- Path alias: `@/` maps to `./src/`
|
||||
|
||||
### Phase Management
|
||||
- App.tsx controls phase: 'boot' -> 'ecg' -> 'login' -> 'pmr'
|
||||
- BootSequence.tsx, ECGAnimation.tsx — LOCKED, do not modify
|
||||
- LoginScreen.tsx bridges to dashboard
|
||||
|
||||
### Typography
|
||||
- Elvaro Grotesque (`font-ui`) — primary UI font
|
||||
- Blumir (`font-ui-alt`) — alternative variable font
|
||||
- Geist Mono (`font-geist`) — timestamps, data values
|
||||
- Fira Code (`font-mono`) — boot/ECG terminal only
|
||||
- Do NOT use Inter, Roboto, DM Sans, or system defaults
|
||||
|
||||
### Design Tokens (index.css)
|
||||
- --sidebar-width: 272px (target: 304px)
|
||||
- --topbar-height: 48px (target: 56px)
|
||||
- --subnav-height: 36px (target: 42px)
|
||||
- Dashboard grid gap: 12/16px (target: 14/20px)
|
||||
- Card padding: 20px (target: 24px)
|
||||
|
||||
### Known Dependencies
|
||||
- React 18.3.1, TypeScript, Vite, Tailwind CSS
|
||||
- Framer Motion 11.15.0, Lucide React 0.468.0, fuse.js 7.0.0
|
||||
|
||||
### Key Files for This Feature
|
||||
- src/index.css — CSS custom properties, grid gap
|
||||
- src/components/Card.tsx — Card padding, CardHeader sizing
|
||||
- src/components/TopBar.tsx — brand, search, session text
|
||||
- src/components/SubNav.tsx — tab text, height
|
||||
- src/components/Sidebar.tsx — all sidebar content sizing
|
||||
- src/components/tiles/PatientSummaryTile.tsx — profile text, KPI cards
|
||||
- src/components/DashboardLayout.tsx — LastConsultationSubsection, main content padding
|
||||
- src/components/tiles/ProjectsTile.tsx — project items, tech tags
|
||||
- src/components/EducationSubsection.tsx — education entries
|
||||
- src/components/WorkExperienceSubsection.tsx — role items, expanded content
|
||||
- src/components/RepeatMedicationsSubsection.tsx — skill rows, category sections
|
||||
- src/components/ParentSection.tsx — parent heading sizing
|
||||
|
||||
### Responsive Breakpoints
|
||||
- Sidebar shows at `lg` (1024px) — uses `lg:block` in DashboardLayout.tsx
|
||||
- TopBar search bar shows at `lg` (1024px) — uses `lg:flex` in TopBar.tsx
|
||||
- TopBar "Remote" label shows at `lg` (1024px) — uses `lg:inline`
|
||||
- Brand switches from "HMC" to "Headhunt Medical Center" at `sm` (640px)
|
||||
- Session badge switches from time-only to "Active Session · time" at `xs` (480px)
|
||||
- SubNav is horizontally scrollable on mobile (overflow-x: auto, scrollbarWidth: none)
|
||||
|
||||
### Sizing Context
|
||||
- Target display: 2560x1440 (QHD)
|
||||
- Current body text: 13px → target: 15px
|
||||
- Current labels/metadata: 9-10px → target: 11-12px minimum
|
||||
- Current sidebar details: 11-11.5px → target: 13px
|
||||
- No text below 11px anywhere in dashboard
|
||||
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-019
|
||||
- Updated --sidebar-width 272px→304px, --topbar-height 48px→56px, --subnav-height 36px→42px in index.css
|
||||
- Updated .dashboard-grid gap from 12px→14px (mobile), 16px→20px (tablet/desktop)
|
||||
- Updated Card.tsx: padding 20px→24px, CardHeader title fontSize 12px→13px, rightText fontSize 10px→11px, dot 8px→9px, marginBottom 16px→18px
|
||||
- Files changed: src/index.css, src/components/Card.tsx
|
||||
- **Learnings for future iterations:**
|
||||
- CSS vars (--sidebar-width, --topbar-height, --subnav-height) propagate automatically to TopBar, Sidebar, DashboardLayout — no additional changes needed
|
||||
- Card padding and CardHeader sizing affect every tile in the dashboard since all tiles use these components
|
||||
- Dashboard grid gap is defined in 3 media query blocks in index.css: base (mobile), 768px (tablet), 1024px (desktop)
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-020
|
||||
- Scaled TopBar: brand text 13px→15px, 'Remote' label 11px→12px, search text 13px→14px, search height 42px→46px, Ctrl+K kbd 10px→11px, A.RECRUITER 12px→13px, session badge 11px→12px, skip-link 13px→14px, Home icon 18→20, Search icon 16→17
|
||||
- Scaled SubNav: tab fontSize 13px→14px, minHeight 36px→42px
|
||||
- Files changed: src/components/TopBar.tsx, src/components/SubNav.tsx
|
||||
- **Learnings for future iterations:**
|
||||
- TopBar has two brand spans: one for desktop (sm:inline) and one for mobile (sm:hidden) — both need fontSize updates
|
||||
- TopBar session badge also has two spans: one for xs+ (xs:inline) and one for mobile (xs:hidden) — both need updating
|
||||
- SubNav height is controlled by both the CSS var (--subnav-height) and the button minHeight — both should match
|
||||
- SubNav uses sticky positioning with top: var(--topbar-height), so it automatically adjusts when topbar height changes
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-018
|
||||
- Changed initial Phase state from 'boot' to 'pmr' in src/App.tsx (line 47)
|
||||
- Boot/ECG/login phases remain in code — only the default state changed
|
||||
- App now loads directly to DashboardLayout on refresh
|
||||
- Files changed: src/App.tsx
|
||||
- **Learnings for future iterations:**
|
||||
- Phase state is a simple string union on line 47 of App.tsx: `useState<Phase>('boot'|'ecg'|'login'|'pmr')`
|
||||
- US-028 will revert this exact change back to 'boot'
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-021
|
||||
- Scaled all Sidebar internal sizing: padding, avatar, name, title, status badge, detail rows, SectionTitle, TagPill, AlertFlag
|
||||
- Sidebar padding 20px/16px → 24px/20px, avatar 52→60px, name 15→17px, title 11.5→13px
|
||||
- Status badge 11→12px, dot 6→7px, all detail rows 11.5→13px, GPhC mono 11→12px
|
||||
- SectionTitle 10→11px, TagPill 10.5→12px (padding 3px/8px → 4px/10px)
|
||||
- AlertFlag 11→13px (padding 7px/10px → 8px/12px), icon 14→16, container 16→18px
|
||||
- Detail row padding 2px → 4px, grid gap 6→8px, PersonHeader marginBottom 6→10px
|
||||
- Tags/Alerts section padding 14px/6px → 16px/8px
|
||||
- Files changed: src/components/Sidebar.tsx
|
||||
- **Learnings for future iterations:**
|
||||
- Sidebar has 6 identical detail row blocks (GPhC, Education, Location, Phone, Email, Registered) — use replace_all for fontSize/padding changes
|
||||
- SectionTitle fontSize affects both Tags and Alerts section headers
|
||||
- The sidebar width itself comes from CSS var --sidebar-width (updated in US-019), not from Sidebar.tsx
|
||||
- Tags gap (5px) and alerts gap (6px) were left as-is — they looked proportional already
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-022
|
||||
- Scaled PatientSummaryTile: profile text 13px→15px (lineHeight 1.6→1.65), KPI value 28px→34px, label 12px→14px, sublabel 10px→12px
|
||||
- KPI card padding 16px→20px, grid gap 12px→16px, Latest Results marginTop 24px→28px
|
||||
- Files changed: src/components/tiles/PatientSummaryTile.tsx
|
||||
- **Learnings for future iterations:**
|
||||
- PatientSummaryTile has two main sections: profile text block and KPI grid (Latest Results)
|
||||
- MetricCard is a local component (not exported) — all KPI styling is self-contained in this file
|
||||
- KPI grid uses Tailwind responsive classes (grid-cols-1 xs:grid-cols-2) combined with inline style for gap
|
||||
- The sublabel uses font-geist (monospace) for the "technical texture" pattern
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-023
|
||||
- Scaled LastConsultationSubsection: field label 10px→12px, field value 11.5px→13px, role title 13.5px→15px
|
||||
- Examination bullet fontSize 12.5px→14px, bullet dot top offset 7px→8px for new line height
|
||||
- View full record button 12px→13px, ChevronRight 14→15
|
||||
- Field gap 16px→20px for better spacing between metadata fields
|
||||
- Updated main content padding from 'p-4 pb-8 md:p-6 md:pb-10 lg:px-7 lg:pt-6 lg:pb-10' to 'p-5 pb-10 md:p-7 md:pb-12 lg:px-8 lg:pt-7 lg:pb-12'
|
||||
- Files changed: src/components/DashboardLayout.tsx
|
||||
- **Learnings for future iterations:**
|
||||
- LastConsultationSubsection is defined inline in DashboardLayout.tsx (lines ~57-232), not in a separate file
|
||||
- fieldLabelStyle and fieldValueStyle are shared CSSProperties objects — changing them updates all 4 field columns (Date, Organisation, Type, Band) at once
|
||||
- The bullet dot `top` offset needs to be adjusted when bullet fontSize changes — at 14px text with 1.5 line-height, 8px top centers the 5px dot
|
||||
- Main content padding uses Tailwind responsive classes on <motion.main> — these are cumulative (p-5 base, md:p-7 overrides, lg:px-8/lg:pt-7 overrides further)
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-024
|
||||
- Scaled ProjectsTile: ProjectItem fontSize 11.5px→13px, padding 10px/12px→12px/16px
|
||||
- Status dot 7px→8px, year label 10px→11px, tech stack tags 9px→10px (padding 2px/6px→3px/8px)
|
||||
- Project list gap 8px→10px
|
||||
- Scaled EducationSubsection: base fontSize 12px→13px, entry padding 10px/12px→12px/16px
|
||||
- Education title 12.5px→14px, institution 11px→12px, detail 10.5px→12px, year 10px→11px
|
||||
- Education list gap 10px→12px
|
||||
- Files changed: src/components/tiles/ProjectsTile.tsx, src/components/EducationSubsection.tsx
|
||||
- **Learnings for future iterations:**
|
||||
- ProjectsTile and EducationSubsection follow the same list-item-in-card pattern — similar inline styles with padding, fontSize, and gap
|
||||
- ProjectItem year label fontSize is a standalone `10px` in a `<span>` — only one occurrence in the file, easy to target
|
||||
- EducationSubsection has a single `fontSize: '12px'` on the button element that acts as the base font for the entry
|
||||
- Education detail items use font-geist (monospace) for the "technical texture" pattern, consistent with KPI sublabels
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-025
|
||||
- Scaled WorkExperienceSubsection: role title 12.5px→14px, organisation 11px→12px, duration 10px→11px
|
||||
- Role item header padding 10px/12px→12px/14px, teal dot 8px→9px
|
||||
- Expanded content: examination bullet 11.5px→13px, coded entry tags 10px→11px (padding 2px/6px→3px/8px)
|
||||
- View full record link 11px→12px, role list gap 8px→10px
|
||||
- Scaled RepeatMedicationsSubsection: skill name 12.5px→14px, frequency text 10.5px→12px
|
||||
- Skill status badge 10px→11px (padding 2px/7px→3px/8px), skill row padding 8px/10px→10px/12px
|
||||
- Skill icon container 26px→30px, icon size 13→15
|
||||
- Category section label 10px→11px, item count 10px→11px, view all button 11px→12px
|
||||
- Skill row gap 6px→8px
|
||||
- Files changed: src/components/WorkExperienceSubsection.tsx, src/components/RepeatMedicationsSubsection.tsx
|
||||
- **Learnings for future iterations:**
|
||||
- WorkExperienceSubsection and RepeatMedicationsSubsection sit side-by-side in the two-column Patient Pathway layout
|
||||
- Both components use inline style objects extensively — all sizing changes are mechanical find-and-replace
|
||||
- RepeatMedicationsSubsection has CategorySection as a local component — category label and item count fontSize are in there, not in SkillRow
|
||||
- The category section label fontSize (`10px`) appears once as a unique pattern (with textTransform: 'uppercase') — safe to use replace_all
|
||||
- SkillRow gap is inside CategorySection's visibleSkills container, not on the outer RepeatMedicationsSubsection
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-026
|
||||
- Evaluated ParentSection heading sizes against scaled body text at 2560x1440
|
||||
- Before: lg heading 2.2rem (35.2px), paddingBottom 1.333rem (21.3px) — ratio to 15px body was 2.35:1
|
||||
- After: lg heading 2.4rem (38.4px), paddingBottom 1.5rem (24px) — ratio restored to 2.56:1
|
||||
- Mobile/tablet breakpoints (1.375rem, 1.6rem, 1.8rem) left unchanged — they scale proportionally at smaller viewports
|
||||
- Files changed: src/components/ParentSection.tsx
|
||||
- **Learnings for future iterations:**
|
||||
- ParentSection is used by PatientSummaryTile ("Patient Summary") and the Patient Pathway card — only 2 h2 headings in the dashboard
|
||||
- The heading-to-body ratio should stay above 2.4:1 for clear visual hierarchy at QHD resolution
|
||||
- Only the lg breakpoint needed adjustment — smaller breakpoints paired with smaller body text already maintain good proportions
|
||||
- paddingBottom in rem scales with root font size, making it future-proof for further type scale changes
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-027
|
||||
- Visual regression check across 5 breakpoints: 2560x1440, 1920x1080, 1440x900, 768x1024, 375x812
|
||||
- No horizontal scrollbar at any tested width
|
||||
- Issue found: TopBar search bar (minWidth 400px) and "Remote" label overflowed at 768px tablet width
|
||||
- Fix: Changed search bar from `md:flex` to `lg:flex` and "Remote" label from `md:inline` to `lg:inline` in TopBar.tsx
|
||||
- All other breakpoints clean — no overflow, truncation, or layout breaks
|
||||
- SubNav at 375px hides "Education" tab off-screen but is scrollable (standard mobile pattern)
|
||||
- npm run build and typecheck both pass
|
||||
- Files changed: src/components/TopBar.tsx
|
||||
- **Learnings for future iterations:**
|
||||
- TopBar search bar has minWidth: 400px which is too wide for md (768px) — must use lg (1024px) breakpoint for search bar visibility
|
||||
- The "Remote" label should match the search bar's breakpoint since they compete for the same horizontal space
|
||||
- Sidebar already uses `lg:block` so TopBar search/Remote at `lg` is consistent with the sidebar showing
|
||||
- SubNav has overflow-x: auto with scrollbarWidth: none — horizontal scroll on mobile is by design, not a bug
|
||||
- At 768px without sidebar or search bar, the TopBar cleanly shows brand + A.RECRUITER + session badge
|
||||
---
|
||||
|
||||
## 2026-02-14 — US-028
|
||||
- Reverted initial Phase state from 'pmr' back to 'boot' in src/App.tsx (line 47)
|
||||
- Verified full sequence in browser: boot animation → ECG heartbeat → login screen → clicked Log In → dashboard loaded
|
||||
- All phases transition correctly, dashboard renders with all scaled components
|
||||
- npm run build and typecheck both pass
|
||||
- Files changed: src/App.tsx
|
||||
- **Learnings for future iterations:**
|
||||
- This is the exact revert of US-018 — single line change on line 47: useState<Phase>('boot')
|
||||
- The full boot→ECG→login sequence takes ~15 seconds before the login button becomes clickable
|
||||
- Canvas2D warnings during ECG are expected (multiple readback operations) — not errors
|
||||
---
|
||||
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
{
|
||||
"project": "Portfolio — Login Screen Rework",
|
||||
"branchName": "ralph/login-screen-rework",
|
||||
"description": "Rework the login screen: responsive sizing, dashboard style alignment, CVMIS rebrand, animated capsule logo, live blurred dashboard background, connection status indicator UX, button pulse, and dissolve transition.",
|
||||
"userStories": [
|
||||
{
|
||||
"id": "US-001",
|
||||
"title": "Skip to login phase for dev iteration",
|
||||
"description": "As a developer, I want to skip boot/ECG and land directly on the login screen so I can iterate on login changes quickly.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/App.tsx, change the initial Phase state from 'boot' to 'login'",
|
||||
"The boot, ECG, and login phases remain in code — only the initial state changes",
|
||||
"App loads directly to the login screen on refresh",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 1,
|
||||
"passes": false,
|
||||
"notes": "Temporary — final story reverts this. Phase state is on line 47 of App.tsx."
|
||||
},
|
||||
{
|
||||
"id": "US-002",
|
||||
"title": "Create CvmisLogo React SVG component",
|
||||
"description": "As a developer, I need a reusable CvmisLogo component that renders the CVMIS capsule logo from cvmis-logo.svg, supporting both static and animated modes.",
|
||||
"acceptanceCriteria": [
|
||||
"Create src/components/CvmisLogo.tsx as a React component",
|
||||
"Component accepts props: size (number, controls height in px), animated (boolean, default false), className (optional string)",
|
||||
"SVG paths are inlined from cvmis-logo.svg — three <g> groups: capsule-rx (teal #0b7979), capsule-terminal (amber #d97706), capsule-data (green #059669)",
|
||||
"The SVG viewBox is preserved so the logo scales correctly at any size",
|
||||
"When animated=false, all three capsules render in their final fanned-out positions (matching the original SVG layout)",
|
||||
"When animated=true, the component plays a two-phase reveal using framer-motion: Phase 1 (Rise ~500ms): green data capsule scales from 0 to 1 and translates upward into center position, other capsules hidden. Phase 2 (Fan-out ~500ms): all three capsules appear and rotate/translate to their final fanned-out positions with staggered easing",
|
||||
"Animation reference: LogoReveal/frame 1-5.jpg — frame 1-3 show green capsule rising, frame 4-5 show all three fanning out",
|
||||
"Each capsule group uses transform-origin at its base/bottom so fan-out looks like cards spreading from a hand",
|
||||
"prefers-reduced-motion: skip animation, render final state immediately",
|
||||
"Typecheck passes"
|
||||
],
|
||||
"priority": 2,
|
||||
"passes": false,
|
||||
"notes": "The SVG uses a transform with scale(0.05, -0.05) and translate — you'll need to simplify the viewBox and transforms for React. The three <g> IDs are capsule-rx, capsule-terminal, capsule-data. Framer Motion is already installed (11.15.0). Look at LogoReveal/frame 1-5.jpg for the animation sequence. The fan-out in frames 4-5 shows: teal Rx tilts left, amber terminal stays center, green data tilts right."
|
||||
},
|
||||
{
|
||||
"id": "US-003",
|
||||
"title": "Responsive login card sizing and dashboard style alignment",
|
||||
"description": "As a visitor on a 1440p or 4K display, I want the login card to be proportionate to my screen and styled consistently with the GP dashboard.",
|
||||
"acceptanceCriteria": [
|
||||
"Login card width changes from fixed 320px to responsive: clamp(320px, 28vw, 480px)",
|
||||
"Card padding scales from fixed 32px to clamp(24px, 2.5vw, 40px)",
|
||||
"Input field font size scales proportionally (minimum 13px, up to 15px on large viewports)",
|
||||
"Button font size scales proportionally (minimum 14px, up to 16px)",
|
||||
"Label font size scales proportionally (minimum 12px, up to 14px)",
|
||||
"Card uses dashboard color tokens via CSS variables: background var(--surface), border color var(--border-card) or #E4EDEB, text colors var(--text-primary) and var(--text-secondary)",
|
||||
"Input fields use var(--accent) (#0D6E6E) for focus border, #E4EDEB for default border, var(--bg-dashboard) for inactive background",
|
||||
"Card shadow uses the project shadow tokens: 0 1px 2px rgba(26,43,42,0.05) resting, 0 2px 8px rgba(26,43,42,0.08) elevated",
|
||||
"Card border radius remains 12px",
|
||||
"Card still looks good on mobile (≤480px) — should not exceed viewport width minus 32px margin",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 3,
|
||||
"passes": false,
|
||||
"notes": "LoginScreen.tsx currently uses inline styles with hardcoded colors (#E5E7EB borders, #64748B text, etc). Replace these with the dashboard CSS custom properties defined in index.css (--surface, --accent, --border, --text-primary, --text-secondary, --text-tertiary). Font family vars: var(--font-ui) for labels/buttons, var(--font-geist-mono) for input monospace."
|
||||
},
|
||||
{
|
||||
"id": "US-004",
|
||||
"title": "Rebrand to CVMIS and integrate animated logo",
|
||||
"description": "As the portfolio owner, I want the login to say CVMIS with the capsule logo replacing the Shield icon.",
|
||||
"acceptanceCriteria": [
|
||||
"Title text changed from 'CareerRecord PMR' to 'CVMIS'",
|
||||
"Subtitle changed from 'Clinical Information System' to 'CV Management Information System'",
|
||||
"The Shield icon import and its teal background container are removed from the branding section",
|
||||
"CvmisLogo component is imported and rendered in the branding section with animated=true",
|
||||
"Logo height is proportional to the responsive card size (roughly 48-64px depending on viewport, use clamp)",
|
||||
"Logo animation completes before the typing animation starts — adjust the startLoginSequence delay (currently 400ms) to account for logo animation duration (~1000ms total)",
|
||||
"Footer text 'Secure clinical system login' remains unchanged",
|
||||
"prefers-reduced-motion: logo shows instantly in final state, typing starts after original 400ms delay",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 4,
|
||||
"passes": false,
|
||||
"notes": "The Shield icon is at LoginScreen.tsx lines 213-218. The logo animation is ~1000ms (500ms rise + 500ms fan-out). Increase the startLoginSequence delay from 400ms to ~1500ms (400ms card entrance + 1000ms logo + 100ms pause). CvmisLogo component from US-002."
|
||||
},
|
||||
{
|
||||
"id": "US-005",
|
||||
"title": "Replace Home icon with CVMIS logo on TopBar",
|
||||
"description": "As a visitor on the dashboard, I want to see the CVMIS brand logo in the top-left corner instead of the generic Home icon.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/components/TopBar.tsx, remove the Home import from lucide-react",
|
||||
"Import CvmisLogo from ./CvmisLogo",
|
||||
"Replace the <Home> element with <CvmisLogo size={24} /> (static, no animation)",
|
||||
"Logo colors match SVG source: teal #0b7979, amber #d97706, green #059669",
|
||||
"Logo maintains aspect ratio and fits within the TopBar height",
|
||||
"The 'Headhunt Medical Center' brand text and all other TopBar elements remain unchanged",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 5,
|
||||
"passes": false,
|
||||
"notes": "TopBar.tsx line 57-61 has the Home icon. Simple swap — CvmisLogo with animated=false (the default). If Home is the only lucide icon used in the import, clean up the import. Check: Search is also imported from lucide-react on line 2."
|
||||
},
|
||||
{
|
||||
"id": "US-006",
|
||||
"title": "Render live dashboard behind login with blur overlay",
|
||||
"description": "As a visitor, I want to see the GP dashboard blurred behind the login card, creating visual continuity.",
|
||||
"acceptanceCriteria": [
|
||||
"In App.tsx, during the 'login' phase, render DashboardLayout underneath the login overlay (both visible simultaneously)",
|
||||
"DashboardLayout is wrapped in DetailPanelProvider (as it is in the 'pmr' phase)",
|
||||
"DashboardLayout renders at scroll position 0 (showing patient summary header area)",
|
||||
"LoginScreen becomes an overlay: fixed position, full viewport, semi-transparent background rgba(240, 245, 244, 0.7) with backdrop-filter: blur(20px)",
|
||||
"Dashboard content is non-interactive while login overlay is present (the overlay captures all pointer events)",
|
||||
"The login card remains centered on top of the blurred overlay",
|
||||
"backdrop-filter blur is constant from the moment login appears (no ease-in)",
|
||||
"prefers-reduced-motion: blur still applies (static visual treatment), only entrance animations are skipped",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 6,
|
||||
"passes": false,
|
||||
"notes": "Currently App.tsx renders phases exclusively (only one at a time). Change so that login phase renders: <DetailPanelProvider><DashboardLayout /></DetailPanelProvider> + <LoginScreen overlay on top>. LoginScreen.tsx already has 'fixed inset-0 z-50' — just change its backgroundColor from solid #1A2B2A to the semi-transparent value with backdrop-filter. Consider adding will-change: backdrop-filter for performance."
|
||||
},
|
||||
{
|
||||
"id": "US-007",
|
||||
"title": "Connection status indicator with animated dots and typing-linked timing",
|
||||
"description": "As a visitor, I want to see a clear red-to-green status transition tied to the typing sequence, not an arbitrary timer.",
|
||||
"acceptanceCriteria": [
|
||||
"Status indicator LED dot size increased from 6px to 10px",
|
||||
"LED dot has a subtle glow effect: box-shadow 0 0 6px 1px in the LED color (red or green)",
|
||||
"Status text size increased from 10px to 12px",
|
||||
"Initial state: RED LED + 'Awaiting secure connection' in red (#DC2626) with animated trailing dots",
|
||||
"The trailing dots animate: dots cycle through '.', '..', '...' repeating every ~1.5 seconds",
|
||||
"Remove the existing independent 2000ms connectionTimeout timer",
|
||||
"Instead, connection transitions to green exactly 500ms after typingComplete becomes true",
|
||||
"Green state: GREEN LED (#059669) + 'Secure connection established, awaiting login' in green",
|
||||
"Transition between red and green states has a smooth 300ms color/shadow transition",
|
||||
"prefers-reduced-motion: no dot cycling animation, state changes happen instantly",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 7,
|
||||
"passes": false,
|
||||
"notes": "The connectionTimeout is set on line 117 of LoginScreen.tsx (2000ms independent timer). Remove it and add a useEffect that watches typingComplete — when true, setTimeout 500ms then setConnectionState('connected'). The dot animation can use a simple interval cycling dotCount 0→1→2→0. The LED glow box-shadow: '0 0 6px 1px rgba(220,38,38,0.4)' for red, '0 0 6px 1px rgba(5,150,105,0.4)' for green."
|
||||
},
|
||||
{
|
||||
"id": "US-008",
|
||||
"title": "Login button pulse animation on activation",
|
||||
"description": "As a visitor, I want the login button to pulse subtly when it becomes clickable so I know to click it.",
|
||||
"acceptanceCriteria": [
|
||||
"Add a CSS @keyframes animation 'login-pulse' in index.css: scale 1 → 1.03 → 1, ease-in-out, duration 1.5s",
|
||||
"When canLogin becomes true (button enabled), apply the pulse animation repeating every 3 seconds (1.5s animation + 1.5s pause via animation-delay or longer duration with keyframe percentages)",
|
||||
"Pulse animation stops when button is hovered (animation: none on hover)",
|
||||
"Pulse animation stops immediately on click (remove animation class on buttonPressed)",
|
||||
"Button opacity transitions from 0.6 to 1.0 when enabled (existing behavior, preserve)",
|
||||
"prefers-reduced-motion: no pulse animation, button just becomes enabled with opacity 1",
|
||||
"Button still receives keyboard focus when it becomes enabled (existing behavior)",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 8,
|
||||
"passes": false,
|
||||
"notes": "The canLogin variable is on line 43 of LoginScreen.tsx. Add a CSS class 'login-pulse-active' that applies the animation, and conditionally apply it when canLogin && !buttonPressed && !buttonHovered. The @keyframes could use: 0%,100% { transform: scale(1) } 50% { transform: scale(1.03) } with animation: login-pulse 1.5s ease-in-out infinite and a wrapper that adds 1.5s gaps (or use 0%,35%,65%,100% keyframe percentages to build in the pause)."
|
||||
},
|
||||
{
|
||||
"id": "US-009",
|
||||
"title": "Login dissolve transition to reveal dashboard",
|
||||
"description": "As a visitor, I want the login card and blur overlay to dissolve smoothly on login, revealing the dashboard underneath.",
|
||||
"acceptanceCriteria": [
|
||||
"On login click: existing pressed state + loading spinner behavior is preserved",
|
||||
"After loading spinner phase, the login card fades out (opacity 0) with slight scale up (1.03)",
|
||||
"Simultaneously, the overlay backdrop-filter blur animates from 20px to 0px",
|
||||
"Overlay background opacity fades from 0.7 to 0",
|
||||
"Total dissolve duration: ~600ms from card exit to fully revealed dashboard",
|
||||
"After dissolve completes, the login overlay is removed from DOM and dashboard becomes interactive",
|
||||
"In App.tsx, transition from login to pmr phase after the overlay dissolve completes (use a callback from LoginScreen)",
|
||||
"prefers-reduced-motion: instant transition, no dissolve animation",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill"
|
||||
],
|
||||
"priority": 9,
|
||||
"passes": false,
|
||||
"notes": "Currently LoginScreen has isExiting state that scales card to 1.03 and fades to opacity 0 (line 163). Extend this to also animate the overlay container. The overlay is the outer div with 'fixed inset-0' — animate its backdrop-filter and background-color. Use framer-motion animate for coordinated exit. The onComplete callback should fire after the full dissolve, not after the card fade."
|
||||
},
|
||||
{
|
||||
"id": "US-010",
|
||||
"title": "Re-enable boot sequence",
|
||||
"description": "As a user, I want the full boot → ECG → login → dashboard experience restored.",
|
||||
"acceptanceCriteria": [
|
||||
"In src/App.tsx, change the initial Phase state back from 'login' to 'boot'",
|
||||
"Boot → ECG → Login → Dashboard sequence works end to end",
|
||||
"Login screen shows blurred dashboard behind it",
|
||||
"Logo animation plays, typing animation follows, connection indicator transitions, button pulses",
|
||||
"Clicking login dissolves the overlay to reveal the dashboard",
|
||||
"No other changes to App.tsx beyond reverting the initial state",
|
||||
"Typecheck passes",
|
||||
"Verify in browser using dev-browser skill: app starts at boot, progresses through ECG, login with blur background and logo animation, arrives at dashboard"
|
||||
],
|
||||
"priority": 10,
|
||||
"passes": false,
|
||||
"notes": "Simple revert of US-001. Phase state is on line 47 of App.tsx."
|
||||
}
|
||||
]
|
||||
}
|
||||
+45
-845
@@ -1,865 +1,65 @@
|
||||
# Progress Log
|
||||
# Progress Log — Login Screen Rework
|
||||
# Branch: ralph/login-screen-rework
|
||||
# Started: 2026-02-15
|
||||
|
||||
## Codebase Patterns
|
||||
|
||||
### Project Structure
|
||||
- Components in `src/components/`, tiles in `src/components/tiles/`
|
||||
- Detail renderers in `src/components/detail/` — KPIDetail, ConsultationDetail, SkillDetail, SkillsAllDetail, EducationDetail, ProjectDetail
|
||||
- Data files in `src/data/` — consultations.ts, medications.ts, problems.ts, investigations.ts, documents.ts, patient.ts, tags.ts, alerts.ts, kpis.ts, skills.ts, educationExtras.ts, constellation.ts
|
||||
- Types in `src/types/pmr.ts` (PMR interfaces) and `src/types/index.ts` (Phase type)
|
||||
- Hooks in `src/hooks/` — useActiveSection.ts, useFocusTrap.ts
|
||||
- Contexts in `src/contexts/` — AccessibilityContext.tsx (has 1 pre-existing ESLint warning — expected), DetailPanelContext.tsx (has 1 pre-existing ESLint warning — expected)
|
||||
- Lib in `src/lib/` — search.ts (fuse.js integration)
|
||||
- Data files in `src/data/`
|
||||
- Types in `src/types/pmr.ts` and `src/types/index.ts`
|
||||
- Hooks in `src/hooks/`, Contexts in `src/contexts/`, Lib in `src/lib/`
|
||||
- Path alias: `@/` maps to `./src/`
|
||||
|
||||
### Phase Management
|
||||
- App.tsx controls phase: 'boot' -> 'ecg' -> 'login' -> 'pmr'
|
||||
- Phase type defined in `src/types/index.ts` as `'boot' | 'ecg' | 'login' | 'pmr'`
|
||||
- BootSequence.tsx handles terminal animation — LOCKED
|
||||
- ECGAnimation.tsx handles heartbeat + letter tracing + flatline exit — LOCKED
|
||||
- LoginScreen.tsx bridges to dashboard (was PMRInterface, now DashboardLayout)
|
||||
|
||||
### Data Architecture (CORRECT — do not modify existing files)
|
||||
- All data files are populated with accurate CV content from References/CV_v4.md
|
||||
- 5 consultation entries (roles), 18 medications (skills with prescribingHistory), 11 problems (achievements), 6 investigations (projects), 5 documents (education)
|
||||
- Types are properly defined in pmr.ts — Consultation, Medication, Problem, Investigation, Document, Patient, ViewId
|
||||
- New types needed: Tag, Alert, KPI, SkillMedication (Task 2)
|
||||
|
||||
### Lucide Icons Typing
|
||||
- Use `LucideIcon` type from `lucide-react` for icon maps, NOT `React.ComponentType<{ size: number }>` — the latter causes TS errors with ForwardRefExoticComponent
|
||||
|
||||
### 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 7.0.0 (already installed) for fuzzy search
|
||||
- BootSequence.tsx, ECGAnimation.tsx — LOCKED, do not modify
|
||||
- LoginScreen.tsx bridges to dashboard
|
||||
|
||||
### Typography
|
||||
- Elvaro Grotesque (`font-ui`) — primary UI font, 7 weights (300-900), loaded from Fonts/ directory
|
||||
- Blumir (`font-ui-alt`) — alternative, variable font (100-700)
|
||||
- Geist Mono (`font-geist`) — timestamps, data values, coded entries
|
||||
- Elvaro Grotesque (`font-ui`, `var(--font-ui)`) — primary UI font
|
||||
- Blumir (`font-ui-alt`) — alternative variable font
|
||||
- Geist Mono (`font-geist`, `var(--font-geist-mono)`) — timestamps, data values
|
||||
- Fira Code (`font-mono`) — boot/ECG terminal only
|
||||
- Do NOT use Inter, Roboto, DM Sans, or system defaults
|
||||
- DM Sans in the concept HTML is a PLACEHOLDER — use Elvaro Grotesque
|
||||
- Font mapping was corrected in Task 1: Elvaro = font-ui (primary), Blumir = font-ui-alt (alternative)
|
||||
|
||||
### Design Tokens
|
||||
- Dashboard background: use `--bg-dashboard` (#F0F5F4), NOT `--bg` (#FFFFFF which is for boot/ECG)
|
||||
- Three-tier shadows: `--shadow-sm` (resting), `--shadow-md` (hover/interactive), `--shadow-lg` (overlays)
|
||||
- Border tiers: `--border` (#D4E0DE, structural), `--border-light` (#E4EDEB, cards)
|
||||
- Accent: `--accent` (#0D6E6E teal), `--accent-hover` (#0A8080), `--accent-light` (rgba 0.08), `--accent-border` (rgba 0.18)
|
||||
- Status colors each have base + light + border variants (success, amber, alert, purple)
|
||||
- Tailwind: `pmr-*` prefix for all dashboard colors (e.g., `bg-pmr-bg`, `text-pmr-accent`, `border-pmr-border-light`)
|
||||
- Tailwind shadows: `shadow-pmr-sm`, `shadow-pmr-md`, `shadow-pmr-lg`
|
||||
- Tailwind radius: `rounded-card` (8px), `rounded-card-sm` (6px), `rounded-login` (12px)
|
||||
### Design Tokens (index.css CSS variables)
|
||||
- --surface: #FFFFFF (card/topbar background)
|
||||
- --bg-dashboard: #F0F5F4 (warm sage content background)
|
||||
- --accent: #0D6E6E (teal primary)
|
||||
- --accent-hover: #0A8080
|
||||
- --accent-light: rgba(10,128,128,0.08)
|
||||
- --border: #D4E0DE (structural borders)
|
||||
- --border-card: #E4EDEB (card/inner borders)
|
||||
- --text-primary: #1A2B2A
|
||||
- --text-secondary: #5B7A78
|
||||
- --text-tertiary: #8DA8A5
|
||||
- --sidebar-width: 304px
|
||||
- --topbar-height: 56px
|
||||
|
||||
### Dashboard Layout
|
||||
- DashboardLayout.tsx is the main container for the pmr phase — replaces PMRInterface
|
||||
- Three-zone: TopBar (fixed, z-100, 48px) + Sidebar (fixed left, 272px) + Main (scrollable card grid)
|
||||
- Card grid: CSS Grid `repeat(2, 1fr)` gap 16px, responsive 1fr at ≤900px via `.dashboard-grid` class
|
||||
- Entrance: three separate Framer Motion variants (topbar → sidebar → content), staggered with delays
|
||||
- Sidebar: default export (`import Sidebar from './Sidebar'`), TopBar: named export (`import { TopBar } from './TopBar'`)
|
||||
- Background color transition: DashboardLayout covers App.tsx's `bg-black` with `var(--bg-dashboard)` + `minHeight: 100vh`
|
||||
### Known Dependencies
|
||||
- React 18.3.1, TypeScript, Vite, Tailwind CSS
|
||||
- Framer Motion 11.15.0, Lucide React 0.468.0, fuse.js 7.0.0
|
||||
|
||||
### Tile Expansion Pattern
|
||||
- Framer Motion `AnimatePresence` + `motion.div` with `initial={{ height: 0 }}`, `animate={{ height: 'auto' }}`, `exit={{ height: 0 }}`
|
||||
- `overflow: hidden` on the motion.div
|
||||
- `prefers-reduced-motion` checked at module scope: `const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches`
|
||||
- Transition: `prefersReducedMotion ? { duration: 0 } : { duration: 0.2, ease: 'easeOut' }`
|
||||
- State: `expandedItemId: string | null` per tile component
|
||||
- Keyboard: Enter/Space toggle, Escape collapse
|
||||
- `role="button"`, `tabIndex={0}`, `aria-expanded` on clickable items
|
||||
- Colored left border (2px) on expanded content panel
|
||||
- CareerActivity maps activity→consultation via `consultationId`, CoreSkills maps skill→medication by name match
|
||||
### Key Files for This Feature
|
||||
- src/App.tsx — phase management, will need restructuring for blur overlay
|
||||
- src/components/LoginScreen.tsx — main login screen (416 lines)
|
||||
- src/components/TopBar.tsx — Home icon replacement target (line 57)
|
||||
- src/components/DashboardLayout.tsx — rendered behind login blur
|
||||
- src/contexts/DetailPanelContext.tsx — wraps DashboardLayout
|
||||
- cvmis-logo.svg — source SVG with 3 capsule groups
|
||||
- LogoReveal/frame 1-5.jpg — animation reference frames
|
||||
|
||||
### Command Palette
|
||||
- `CommandPalette.tsx` renders at DashboardLayout level (z-index 1000, fixed overlay)
|
||||
- Triggered by Ctrl+K (global listener in DashboardLayout) or TopBar search bar click
|
||||
- Data model: `PaletteItem` with `PaletteAction` union (scroll, expand, link, download)
|
||||
- `buildPaletteData()` returns 24 items across 6 sections, `buildSearchIndex()` wraps fuse.js
|
||||
- `groupBySection()` maintains section order: Experience → Core Skills → Active Projects → Achievements → Education → Quick Actions
|
||||
- All tiles have `data-tile-id` attribute (via Card `tileId` prop) for scroll targeting
|
||||
- CSS animations in index.css: `palette-overlay-in`, `palette-modal-in` with `prefers-reduced-motion` overrides
|
||||
- Legacy search exports (`SearchResult`, `buildLegacySearchIndex`, `groupResultsBySection`) kept for ClinicalSidebar backward compat — remove in Task 21
|
||||
|
||||
### Visual Review
|
||||
- Dev server runs on `http://localhost:5173` throughout the loop
|
||||
- App has boot→ECG→login→dashboard sequence (~15s on first load)
|
||||
- If browser tools fail, skip visual review and note in iteration log — don't block progress
|
||||
|
||||
## Manual Intervention — 2026-02-13
|
||||
### Reason: Complete redesign — replacing CareerRecord PMR with GP System Dashboard
|
||||
### Changes made:
|
||||
- **IMPLEMENTATION_PLAN.md**: Completely rewritten with 21 new tasks for GP System dashboard overhaul
|
||||
- **guardrails.md**: Completely rewritten for new design direction (teal palette, tile-based layout, 8px radius, new shadow system)
|
||||
- **progress.txt**: This intervention entry added
|
||||
- **CLAUDE.md**: Will be updated by Task 3 in the new plan (architecture, colors, components, styling)
|
||||
|
||||
### Previous plan status: 15/15 tasks completed (all checked off)
|
||||
### New plan: 21 tasks across 4 phases (Foundation → Core Layout → Dashboard Tiles → Interactions → Polish)
|
||||
|
||||
### What's being replaced:
|
||||
- `PatientBanner.tsx` → `TopBar.tsx` (white top bar with search and session info)
|
||||
- `ClinicalSidebar.tsx` → `Sidebar.tsx` (light background #F7FAFA, person header, tags, alerts only)
|
||||
- `PMRInterface.tsx` → `DashboardLayout.tsx` (topbar + sidebar + scrollable card grid)
|
||||
- All 7 `views/*.tsx` files → Dashboard tile components in `src/components/tiles/`
|
||||
- Color palette: dark sidebar (#1E293B) + NHS Blue (#005EB8) → light sidebar (#F7FAFA) + teal (#0D6E6E)
|
||||
- Navigation: sidebar-nav view-switching → single scrollable dashboard with expandable tiles
|
||||
- Patient banner scroll condensation → removed (no banner, just topbar)
|
||||
|
||||
### What's preserved:
|
||||
- Boot sequence (BootSequence.tsx) — LOCKED
|
||||
- ECG animation (ECGAnimation.tsx) — LOCKED
|
||||
- Login screen (LoginScreen.tsx) — unchanged
|
||||
- Font setup: Elvaro Grotesque (primary UI), Blumir (alt), Geist Mono (data), Fira Code (terminal only)
|
||||
- All data files in src/data/ — content unchanged, new data files added
|
||||
- fuse.js dependency — reused for command palette search
|
||||
- App.tsx phase management (boot → ecg → login → pmr) — pmr phase now renders DashboardLayout
|
||||
|
||||
### Context for next iteration:
|
||||
- The reference design is `References/GPSystemconcept.html` — READ THIS before starting any visual task
|
||||
- The old PMR components STILL EXIST in the codebase. Don't delete them yet — some expand/collapse patterns and data rendering can be reused inside tile expansion (Task 16). Cleanup happens in Task 21.
|
||||
- Login screen still transitions to `#1E293B` background. The new dashboard has `#F0F5F4` background. The LoginScreen.tsx may need a background color update, or the transition can be handled in DashboardLayout's entrance animation.
|
||||
- The concept HTML uses DM Sans font — this is a PLACEHOLDER. Production uses Elvaro Grotesque (font-ui). Do not switch to DM Sans.
|
||||
- The concept's command palette has a comprehensive data model — use it as reference for building the palette in Task 18.
|
||||
- Tile interactions (expansion, KPI flip) are in Phase 3. Tiles in Phase 2 should be built as static/display-only first, with data attributes or props that Phase 3 can hook into.
|
||||
|
||||
### New guardrails added:
|
||||
- Accent color: teal #0D6E6E (replacing NHS Blue #005EB8 as primary interactive color)
|
||||
- Border radius: 8px for cards (was 4px)
|
||||
- Shadow system: three-tier (sm/md/lg) replacing single pmr shadow
|
||||
- Sidebar: light background, PersonHeader + Tags + Alerts ONLY (projects, skills, education moved to tiles)
|
||||
- Layout: TopBar + Sidebar + Card Grid (replacing PatientBanner + ClinicalSidebar + view switching)
|
||||
- Tile ordering: Patient Summary → Latest Results + Core Skills → Last Consultation → Career Activity → Education → Projects
|
||||
- Skills frequency: user-specified values (Data Analysis=twice daily, etc.)
|
||||
|
||||
## Iteration Log
|
||||
|
||||
### Iteration 1 — Task 1: Update design tokens and Tailwind config
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/index.css`: Added full GP System Dashboard token set (colors, shadows, layout vars, status colors with light/border variants). Kept legacy `--pmr-*` aliases for backward compat. Updated values of legacy tokens to match new palette (e.g., `--pmr-content` → `#F0F5F4`, `--pmr-sidebar` → `#F7FAFA`, `--pmr-radius` → `8px`).
|
||||
- Updated `tailwind.config.js`: New pmr color tokens (bg, surface, sidebar, accent, text-primary/secondary/tertiary, border/border-light, success, amber, alert, purple). Three-tier shadow system (pmr-sm/md/lg). Border-radius updated (card=8px, card-sm=6px, login=12px).
|
||||
- **Fixed font-ui/font-ui-alt swap**: Previous iterations had Blumir as `font-ui` and Elvaro as `font-ui-alt` — this was backwards. Now corrected: Elvaro Grotesque = `font-ui` (primary), Blumir = `font-ui-alt` (alternative). No components were using these classes directly, so no breakage.
|
||||
- Used `--bg-dashboard` for the dashboard background (not `--bg`) to avoid conflicting with the existing `--bg: #FFFFFF` used by boot/ECG phases.
|
||||
**Learnings:**
|
||||
- The `--bg` CSS var is used for boot/ECG phases (#FFFFFF). Dashboard background uses `--bg-dashboard` (#F0F5F4) to avoid collision.
|
||||
- No existing components reference `font-ui` or `font-ui-alt` Tailwind classes, so the font swap was safe.
|
||||
- The old shadow tokens (`pmr-hover`, `pmr-banner`) were not referenced in any component code.
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable (token-only changes, no visual components yet)
|
||||
|
||||
### Iteration 2 — Task 2: Create new data files and update types
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/data/profile.ts` — personal statement extracted from CV_v4.md (exact match)
|
||||
- Created `src/data/tags.ts` — 5 sidebar tags with color variants (teal, amber, green)
|
||||
- Created `src/data/alerts.ts` — 2 sidebar alert flags (£14.6M savings, £220M budget) with severity and lucide-react icon names
|
||||
- Created `src/data/kpis.ts` — 4 KPI metrics (Budget, Savings, Years, Team Size) with values, labels, subs, color variants, and explanation text for flip cards
|
||||
- Created `src/data/skills.ts` — 5 technical skills as "SkillMedication" entries with user-specified frequencies (Data Analysis="Twice daily", Python="Daily", SQL="Daily", Power BI="Once weekly", JS/TS="When required"), plus years, proficiency, category, status, and lucide-react icon names
|
||||
- Updated `src/types/pmr.ts` — added 4 new interfaces: Tag, Alert, KPI, SkillMedication
|
||||
**Learnings:**
|
||||
- All new data files follow the established pattern: import types, export const array
|
||||
- Icon names are stored as strings (lucide-react icon names) — components will dynamically import them
|
||||
- Skills frequency strings are user-specified values (not standardized enum like old Medication type)
|
||||
- KPI explanations are substantial text blocks for flip card backs — these will be displayed in full when users flip the cards
|
||||
- All CV numbers/dates verified against CV_v4.md — £220M, £14.6M, 9+ years, team of 12, start years for skills
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable (data-only changes, no visual components yet)
|
||||
|
||||
### Iteration 3 — Task 4: Build TopBar component
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/TopBar.tsx` — fixed 48px header with three zones:
|
||||
- Left: Home icon (lucide-react, 18px, accent) + "Headhunt Medical Center" (13px, 600 weight) + "Remote" version badge (11px, tertiary)
|
||||
- Center: Search bar button (max-width 560px, min-width 400px, 42px height) with Search icon, placeholder text, Ctrl+K kbd badge. On click triggers `onSearchClick` prop (for command palette in Task 18). Hidden on mobile (<768px). Hover/focus border transitions to accent color with focus ring.
|
||||
- Right: "Dr. A.CHARLWOOD" text (hidden on <640px) + "Active Session · [time]" pill badge (Geist Mono, accent-light bg, accent-border)
|
||||
- Component uses CSS custom properties from Task 1 tokens (--surface, --border, --accent, --text-primary, --text-secondary, --text-tertiary, --bg-dashboard, --accent-light, --accent-border)
|
||||
- Live time updates every 60 seconds using setInterval
|
||||
- Search bar is a `<button>` element (not input) — it doesn't do inline search, only triggers the command palette
|
||||
- Responsive: search bar hidden on <768px (md breakpoint), user name hidden on <640px (sm breakpoint)
|
||||
**Learnings:**
|
||||
- Search bar should be a button, not an input — it triggers the command palette overlay (Task 18). No inline filtering.
|
||||
- Using `var(--radius-card)` for 8px border radius on the search bar container
|
||||
- Time format: 24-hour (en-GB locale), no seconds — matches clinical system convention
|
||||
- TopBar is not yet wired into DashboardLayout (that's Task 7) — component is created and ready
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable (component not yet wired into App — will be visible after Task 7)
|
||||
|
||||
### Iteration 4 — Tasks 5-6: Build Sidebar with PersonHeader, Tags, and Alerts
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/Sidebar.tsx` — complete sidebar component with three main sections:
|
||||
- **PersonHeader**: 52px teal gradient avatar with "AC" initials, name "CHARLWOOD, Andrew" (15px, 700 weight), title "Pharmacy Data Technologist" (11.5px, Geist Mono), status badge "Open to Opportunities" with animated pulse dot (6px, success color, 2s pulse animation). Details grid with 6 rows: GPhC No. (monospace, letter-spaced), Education, Location, Phone (accent link), Email (accent link), Registered. 2px teal border-bottom separator.
|
||||
- **Tags**: Section title with divider line. 5 colored pill badges (10.5px, 3px/8px padding, 4px radius) in three color variants (teal/amber/green). Data from tags.ts.
|
||||
- **Alerts/Highlights**: 2 flag items with lucide-react icons (AlertTriangle for alert, AlertCircle for amber). 11px, 700 weight, 7px/10px padding, 6px radius. Severity-based colors.
|
||||
- Added animations to `src/index.css`:
|
||||
- `@keyframes pulse` for status badge dot (opacity 1→0.4→1, 2s infinite)
|
||||
- `.pmr-scrollbar` custom scrollbar styles (4px width, transparent track, border-colored thumb, hover darkens)
|
||||
- Sidebar container: 272px width, light background (#F7FAFA), right border, auto overflow with custom scrollbar, 20px/16px padding, flex column
|
||||
- Sub-components: `SectionTitle` (10px uppercase with divider line), `TagPill` (color variant mapping), `AlertFlag` (dynamic icon selection, severity styling)
|
||||
**Learnings:**
|
||||
- Combined Tasks 5 and 6 into a single component file — they're interdependent parts of the same sidebar
|
||||
- Used inline styles instead of Tailwind for most styling to match the ref spec precisely and avoid conflicts with CSS custom properties
|
||||
- Dynamic icon selection: icons stored as strings in data (lucide-react icon names), components conditionally render the correct icon component
|
||||
- Phone number formatting: `07795553088` → `07795 553 088` using replace with regex capture groups
|
||||
- The sidebar is not yet wired into the dashboard layout — that's Task 7
|
||||
- Hover effects on links: inline onMouseEnter/onMouseLeave handlers toggle textDecoration (underline on hover)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable (component not yet wired into App — will be visible after Task 7)
|
||||
|
||||
### Iteration 5 — Task 7: Build DashboardLayout and wire up App.tsx
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/DashboardLayout.tsx` — main layout container with three-zone structure:
|
||||
- TopBar (fixed via TopBar component, animated slide-down from -48px)
|
||||
- Sidebar (fixed left via Sidebar component, animated slide from -272px)
|
||||
- Main content area (flex: 1, scrollable, 24px 28px 40px padding) with card grid placeholder
|
||||
- Card grid uses CSS Grid: `repeat(2, 1fr)` with 16px gap, responsive to 1 column at ≤900px
|
||||
- Three Framer Motion entrance variants: topbar (200ms), sidebar (250ms, 50ms delay), content (300ms, 150ms delay)
|
||||
- All animations respect `prefers-reduced-motion` via module-scope matchMedia check (established pattern)
|
||||
- Added `dashboard-grid` responsive CSS class in `src/index.css` for the 900px breakpoint
|
||||
- Updated `src/App.tsx`: replaced `PMRInterface` import/render with `DashboardLayout` in 'pmr' phase
|
||||
- Background transition handled by option 1 from ref: DashboardLayout sets `background: var(--bg-dashboard)` with `minHeight: 100vh`, covering the dark login background as the entrance animation plays
|
||||
- Command palette state placeholder added (useState for open/close) — will be wired in Task 18
|
||||
- TopBar `onSearchClick` prop connected to command palette open handler
|
||||
- Main content area uses `pmr-scrollbar` class for styled scrollbar (thin, border-colored thumb)
|
||||
**Learnings:**
|
||||
- DashboardLayout uses separate `initial`/`animate` on each motion.div rather than a parent orchestrator — cleaner for three independently animated zones
|
||||
- The `bg-black` on App.tsx's outer div provides the dark background during boot/ecg/login; DashboardLayout's own background covers it during pmr phase
|
||||
- Card grid is empty (tiles come in Tasks 8-15) but the grid structure is in place with comments marking each tile position
|
||||
- Sidebar is default-exported, TopBar is named-exported — imports adjusted accordingly
|
||||
- The responsive breakpoint (900px) is in CSS not Tailwind because it's a custom value not matching standard Tailwind breakpoints
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — browser tools unavailable. Dashboard layout structure verified via quality checks. Visual review will happen when tiles are added.
|
||||
|
||||
### Iteration 6 — Task 8: Build reusable Card component
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/Card.tsx` with two exports:
|
||||
- `Card` component: Reusable base card with white background, 8px border-radius, shadow-sm
|
||||
- `CardHeader` component: Colored dot (8px circle) + uppercase title + optional mono right text
|
||||
- Card styling: Uses CSS custom properties (--surface, --border-light, --border, --radius, --shadow-sm/md)
|
||||
- Hover interaction: Shadow deepens to shadow-md, border strengthens to --border (via useState + onMouseEnter/onMouseLeave)
|
||||
- Full-width variant: `full` prop sets `gridColumn: '1 / -1'` to span both grid columns
|
||||
- CardHeader dot colors: teal (#0D6E6E), amber (#D97706), green (#059669), alert (#DC2626), purple (#7C3AED)
|
||||
- Header typography: title is 12px, 600 weight, uppercase, 0.06em letter-spacing, text-secondary
|
||||
- Right text: 10px, 400 weight, text-tertiary, Geist Mono font, margin-left auto
|
||||
- All styles use inline React.CSSProperties to precisely match the ref spec
|
||||
**Learnings:**
|
||||
- Card uses inline styles rather than Tailwind classes — ensures precise CSS custom property mapping
|
||||
- Hover state managed with React state (not CSS :hover) to coordinate shadow + border color transitions
|
||||
- CardHeader accepts dotColor as string literal union type ('teal' | 'amber' | etc.) — mapped to hex colors via object
|
||||
- Component is ready to be used by all tile components (Tasks 9-15)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable — base component, will be visible once integrated into tiles
|
||||
|
||||
### Iteration 7 — Task 9: Build PatientSummary tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/PatientSummaryTile.tsx` — simple read-only tile displaying personal statement
|
||||
- Full-width card (via `full` prop on Card component) with teal dot CardHeader
|
||||
- Body text: 13px, line-height 1.6, text-primary color, font-ui (Elvaro Grotesque)
|
||||
- Content sourced from `src/data/profile.ts` (personalStatement export)
|
||||
- Updated `src/components/DashboardLayout.tsx` to import and render PatientSummaryTile as first tile in grid
|
||||
**Learnings:**
|
||||
- PatientSummaryTile is the simplest tile — no expansion, no interactivity, just display
|
||||
- The `full` prop on Card correctly spans both grid columns (grid-column: 1 / -1)
|
||||
- CardHeader with teal dot + "PATIENT SUMMARY" matches the spec exactly
|
||||
- Personal statement text is substantial (4 sentences, ~110 words) — 13px with 1.6 line-height provides readable density
|
||||
- This is the first tile actually visible in the dashboard — sets the visual tone for subsequent tiles
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. Will verify visually after multiple tiles are in place.
|
||||
|
||||
### Iteration 8 — Task 10: Build LatestResults tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/LatestResultsTile.tsx` — half-width card with 2×2 metric grid
|
||||
- CardHeader: teal dot + "LATEST RESULTS" + "Updated May 2025" right text (Geist Mono)
|
||||
- 2×2 CSS grid (1fr 1fr, 12px gap) containing four MetricCard sub-components
|
||||
- Each MetricCard: 14px padding, 6px radius, border-light, dashboard background (#F0F5F4)
|
||||
- Value: 22px, 700 weight, -0.02em letter-spacing, line-height 1.2, colored by variant (green/amber/teal)
|
||||
- Label: 11px, 500 weight, text-secondary
|
||||
- Sub: 10px, text-tertiary, Geist Mono font
|
||||
- Added `data-kpi-id` attribute on each metric card for Task 17 flip interaction hookup
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered LatestResultsTile in the half-width left column position
|
||||
**Learnings:**
|
||||
- MetricCard uses `var(--bg-dashboard)` for background (#F0F5F4) as specified in ref — creates subtle contrast against the white card surface
|
||||
- The colorMap for KPI values maps green/amber/teal variant strings to hex colors — same approach as Card's dotColorMap
|
||||
- Half-width tiles (no `full` prop) naturally fill one grid column in the 2-column dashboard grid
|
||||
- The `data-kpi-id` attribute provides a hook for Task 17's flip card interaction without adding click handlers yet
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 9 — Task 11: Build CoreSkills tile ("Repeat Medications")
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/CoreSkillsTile.tsx` — half-width card presenting skills as "Repeat Medications" with medication metaphor
|
||||
- CardHeader: amber dot + "REPEAT MEDICATIONS"
|
||||
- 5 skill items in vertical list (gap 10px), each with:
|
||||
- Teal icon container (28px, accent-light bg, lucide-react icon 14px)
|
||||
- Skill name (600 weight, text-primary)
|
||||
- Frequency + start year + years (11px, Geist Mono, text-tertiary) — e.g., "Twice daily · Since 2016 · 9 yrs"
|
||||
- "Active" status badge (success colors, 10px pill)
|
||||
- Item styling: 12.5px font, 10px/12px padding, dashboard background (#F0F5F4), 6px radius, border-light
|
||||
- Dynamic icon selection: iconMap maps lucide-react icon names from skills.ts to components
|
||||
- Data from `src/data/skills.ts` — 5 skills with user-specified frequencies (Data Analysis="Twice daily", Python="Daily", SQL="Daily", Power BI="Once weekly", JS/TS="When required")
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered CoreSkillsTile in the right column next to LatestResultsTile
|
||||
**Learnings:**
|
||||
- The medication metaphor works well with the frequency strings + years of experience — creates authentic clinical texture
|
||||
- Icon container uses `var(--accent-light)` background with `var(--accent)` foreground — matches the teal accent system
|
||||
- Dashboard background (`var(--bg-dashboard)` = #F0F5F4) on items creates subtle contrast against white card surface
|
||||
- Status badge uses success color system (green) — could be made dynamic in Task 16 if proficiency levels need different colors
|
||||
- Each item has `cursor: default` since expansion interaction comes in Task 16 (no hover state yet)
|
||||
- The `iconMap` pattern for dynamic icon selection is consistent with the Sidebar's AlertFlag component
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. Will verify visually after multiple tiles are in place.
|
||||
|
||||
|
||||
### Iteration 10 — Task 12: Build LastConsultation tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/LastConsultationTile.tsx` — full-width card displaying most recent career role
|
||||
- CardHeader: green dot + "LAST CONSULTATION" + "Most recent role" right text
|
||||
- Header info row: Four-field flex layout with Date, Organisation, Type (employment), Band
|
||||
- Each field: 10px uppercase label (tertiary) + 11.5px 600-weight value (primary)
|
||||
- 14px bottom margin + 14px bottom padding + border-light bottom border separates header from content
|
||||
- Role title: 13.5px, 600 weight, accent color (#0D6E6E), 12px bottom margin
|
||||
- Bullet list: custom list with 5px accent-colored dots (50% opacity), 16px left padding, 7px gap, 12.5px text, 1.5 line-height
|
||||
- Data from `consultations[0]` (most recent role) — date, organization, role, examination array
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered LastConsultationTile below CoreSkillsTile
|
||||
- Helper functions for data formatting:
|
||||
- `formatDate()`: Converts "14 May 2025" → "May 2025" format
|
||||
- `getEmploymentType()`: Returns "Permanent · Full-time" for ICB roles (based on CV context)
|
||||
- `getBand()`: Returns "8a" for Head roles (senior ICB positions)
|
||||
**Learnings:**
|
||||
- The ref spec's bullets didn't match the actual consultations[0].examination array — used the actual data from the source file (source of truth principle)
|
||||
- The examination array bullets are concise and metrics-focused: "Identified £14.6M...", "Built Python-based algorithm...", "Automated incentive scheme..."
|
||||
- Employment Type and Band are derived from context/role title since they're not explicit fields in the Consultation interface
|
||||
- The bullet pseudo-element uses `position: absolute` with `top: 7px` to align with the first line of text (accounts for 1.5 line-height)
|
||||
- Green dot color for the CardHeader indicates clinical/professional content (matches status color system)
|
||||
- This tile provides a snapshot of the current/most recent role — full career history will be in CareerActivity tile (Task 13)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. Will verify visually after multiple tiles are in place.
|
||||
|
||||
### Iteration 11 — Task 13: Build CareerActivity tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/CareerActivityTile.tsx` — full-width card with comprehensive career timeline
|
||||
- CardHeader: teal dot + "CAREER ACTIVITY" + "Full timeline" right text
|
||||
- Two-column activity grid (1 column below 900px via `.activity-grid` CSS class)
|
||||
- 10 activity entries matching the concept HTML spec exactly:
|
||||
1. Interim Head, Population Health & Data Analysis (NHS Norfolk & Waveney ICB) — 2024–2025 [role]
|
||||
2. £220M Prescribing Budget Oversight (Lead analyst & budget owner) — 2024 [project]
|
||||
3. Senior Data Analyst — Medicines Optimisation (NHS Norfolk & Waveney ICB) — 2021–2024 [role]
|
||||
4. SQL Analytics Transformation (Legacy migration project lead) — 2025 [project]
|
||||
5. Power BI Data Analyst Associate (Microsoft Certified) — 2023 [cert]
|
||||
6. Prescribing Data Pharmacist (NHS Norwich CCG) — 2018–2021 [role]
|
||||
7. Clinical Pharmacy Diploma (Professional development) — 2019 [cert]
|
||||
8. Community Pharmacist (Boots UK) — 2016–2018 [role]
|
||||
9. MPharm (Hons) — 2:1 (University of East Anglia) — 2011–2015 [edu]
|
||||
10. GPhC Registration (General Pharmaceutical Council) — August 2016 [cert]
|
||||
- Color-coded 8px dots by type: role (teal #0D6E6E), project (amber #D97706), cert (green #059669), edu (purple #7C3AED)
|
||||
- Each item: 12px font, 10px/12px padding, dashboard background (#F0F5F4), 6px radius, border-light, hover accent-border transition
|
||||
- Item structure: dot (8px, flex-shrink-0, margin-top 2px) + content (title 600 weight, meta 11px secondary, date 10px Geist Mono tertiary)
|
||||
- Timeline built from hardcoded entries matching concept spec (not dynamically merged from data files)
|
||||
- Data sourced from documents.ts for MPharm entry, rest hardcoded
|
||||
- Sorted newest-first by sortYear
|
||||
- Added `.activity-grid` responsive CSS class to `src/index.css` (grid 2 columns → 1 column below 900px)
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered CareerActivityTile below LastConsultationTile
|
||||
**Learnings:**
|
||||
- The ref spec specified merging data from consultations, investigations, and documents — but the concept HTML has specific entries that don't directly map to the existing data
|
||||
- For example, concept shows "Senior Data Analyst — Medicines Optimisation" but consultations has "Deputy Head" and "High-Cost Drugs" roles
|
||||
- Solution: hardcoded the 10 entries matching the concept spec exactly (ref spec says to match the concept HTML entries)
|
||||
- The only dynamic data pull is the MPharm entry from documents.ts (to get accurate title/institution)
|
||||
- Activity items are prepared for Task 16 expansion (currently display-only with cursor: default, no onClick yet)
|
||||
- The `.activity-grid` class uses same responsive breakpoint (900px) as `.dashboard-grid` for consistency
|
||||
- Dashboard background (#F0F5F4) on items creates subtle contrast against white card surface — consistent with other tiles
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. Will verify visually after multiple tiles are in place.
|
||||
|
||||
### Iteration 12 — Task 14: Build Education tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/EducationTile.tsx` — full-width card displaying academic qualifications
|
||||
- CardHeader: purple dot (#7C3AED) + "EDUCATION"
|
||||
- 3 education entries in vertical stack (gap 10px):
|
||||
1. MPharm (Hons) — 2:1 (University of East Anglia · 2015)
|
||||
2. NHS Leadership Academy — Mary Seacole Programme (2018 · 78%)
|
||||
3. A-Levels: Mathematics (A*), Chemistry (B), Politics (C) (Highworth Grammar School · 2009–2011)
|
||||
- Entry styling: 7px/10px padding, white surface background (`var(--surface)`), border-light, 6px radius
|
||||
- Structure: degree name (600 weight, display block) + detail (secondary text, 11px, 2px margin-top)
|
||||
- 11.5px base font size for entries
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered EducationTile below CareerActivity
|
||||
**Learnings:**
|
||||
- Education data presented in simple display-only format — no expansion interaction needed (unlike Career Activity or Projects)
|
||||
- Ref spec mentioned filtering documents.ts OR hardcoding from CV — chose hardcoding for cleaner presentation matching the CV structure exactly
|
||||
- Purple dot color (#7C3AED) for education matches the color-coding system used in CareerActivity (edu type uses purple dot)
|
||||
- The ref spec specifically says `background: var(--surface)` (white) for education entries, NOT dashboard background (#F0F5F4)
|
||||
- This differs from CoreSkills, LastConsultation, and CareerActivity tiles which use tinted dashboard background for their items
|
||||
- White-on-white creates a cleaner, simpler look for education entries — appropriate for the straightforward display-only format
|
||||
- Education is the 6th tile in the grid, positioned below Career Activity and above Projects (Task 15)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. Will verify visually after all tiles are in place.
|
||||
|
||||
### Iteration 13 — Task 15: Build Projects tile
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/tiles/ProjectsTile.tsx` — full-width card displaying projects from investigations.ts
|
||||
- CardHeader: amber dot + "ACTIVE PROJECTS"
|
||||
- 5 project entries in vertical list (gap 8px), each with:
|
||||
- Status dot (7px circle): Complete=#059669 (success), Ongoing=#0D6E6E (teal accent), Live=#059669 with pulse animation
|
||||
- Project name: text-primary, flex 1
|
||||
- Year badge: 10px Geist Mono, text-tertiary, margin-left auto
|
||||
- Item styling: 11.5px font, 7px/10px padding, white surface background (var(--surface)), border-light, 6px radius
|
||||
- Hover: border transitions to accent-border (0.15s)
|
||||
- Items prepared for Task 16 expansion (cursor: default, no onClick yet)
|
||||
- Updated `src/components/DashboardLayout.tsx` — imported and rendered ProjectsTile as last tile in grid
|
||||
**Learnings:**
|
||||
- Projects tile uses white surface background (var(--surface)) for items, matching the Education tile pattern — not the tinted dashboard background used by CoreSkills and CareerActivity
|
||||
- The Investigation interface has a union type for status: 'Complete' | 'Ongoing' | 'Live' — mapped directly to dot colors
|
||||
- "Live" status (PharMetrics) gets the pulse animation keyframe already defined in index.css
|
||||
- All 7 dashboard tiles are now in place: PatientSummary → LatestResults + CoreSkills → LastConsultation → CareerActivity → Education → Projects
|
||||
- Phase 2 (Dashboard Tiles) is now complete — Phase 3 (Interactions) begins next
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available. All tiles now in place — visual review recommended for Task 16.
|
||||
|
||||
### Iteration 15 — Task 17: KPI flip card interaction
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/tiles/LatestResultsTile.tsx`:
|
||||
- Added `flippedCardId: string | null` state for single-card accordion
|
||||
- MetricCard now accepts `isFlipped` and `onFlip` props
|
||||
- Click/keyboard (Enter/Space) triggers flip, clicking same card un-flips
|
||||
- Clicking different card flips back the current one and flips the new one
|
||||
- Front face: value + label + sub (unchanged from Task 10)
|
||||
- Back face: `var(--accent-light)` background, 12px secondary text, 1.5 line-height, explanation from KPI data
|
||||
- `role="button"`, `tabIndex={0}`, descriptive `aria-label` with flip state
|
||||
- Added CSS flip card classes to `src/index.css`:
|
||||
- `.metric-card`: perspective: 1000px, cursor: pointer
|
||||
- `.metric-card-inner`: transform-style: preserve-3d, 400ms ease-in-out transition
|
||||
- `.metric-card-inner.flipped`: rotateY(180deg)
|
||||
- `.metric-card-front/.metric-card-back`: backface-visibility: hidden
|
||||
- `.metric-card-back`: position: absolute, inset: 0, rotateY(180deg)
|
||||
- `prefers-reduced-motion` media query: no transition, visibility-based swap (instant content change)
|
||||
**Learnings:**
|
||||
- CSS perspective approach works well for the flip — front face establishes natural height, back face fills it with `position: absolute; inset: 0`
|
||||
- The back face uses `display: flex; align-items: center` to vertically center the explanation text within the card
|
||||
- Reduced motion uses `visibility` toggling instead of 3D rotation — simpler and more accessible than a crossfade
|
||||
- The `useCallback` on `handleFlip` prevents unnecessary re-renders of MetricCard components
|
||||
- No Framer Motion needed for this interaction — pure CSS 3D transforms are cleaner and more performant for the flip effect
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 14 — Task 16: Tile expansion system
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/tiles/CareerActivityTile.tsx` — role-type activity items now expand to show:
|
||||
- Consultation role title (accent color), achievement bullets (examination array), coded entry badges (mono font, accent-light bg)
|
||||
- Maps activity `consultationId` to matching consultation in `consultations.ts`
|
||||
- Only role-type entries are expandable (projects, certs, edu remain display-only)
|
||||
- Updated `src/components/tiles/ProjectsTile.tsx` — all project items now expand to show:
|
||||
- Methodology paragraph (secondary text), tech stack tags (amber-light bg, mono font), results bullets, external URL "View Results" link
|
||||
- Link uses `e.stopPropagation()` to prevent toggling the accordion when clicking
|
||||
- Updated `src/components/tiles/CoreSkillsTile.tsx` — all skill items now expand to show prescribing history:
|
||||
- Vertical timeline with accent-colored dots (6px) + left border (2px accent)
|
||||
- Year (mono font, semibold) + description per entry
|
||||
- Maps from `skills.ts` names to `medications.ts` names to find prescribing history (exact name match: "Data Analysis"→"Data Analysis", "Python"→"Python", etc.)
|
||||
- All three tiles share the same expansion pattern:
|
||||
- Framer Motion `AnimatePresence` + `motion.div` with height-only animation (200ms, ease-out)
|
||||
- No opacity fade on content (guardrail compliance)
|
||||
- `overflow: hidden` on animated container
|
||||
- Single-expand accordion: `expandedItemId: string | null` state, clicking same item collapses, clicking different item swaps
|
||||
- Keyboard: Enter/Space to toggle, Escape to collapse (via `onKeyDown` handler)
|
||||
- `role="button"`, `tabIndex={0}`, `aria-expanded` on clickable items
|
||||
- `prefers-reduced-motion`: duration: 0 for instant expand/collapse
|
||||
- Colored left border on expanded panels (teal for roles, amber for projects, teal for skills)
|
||||
- Hover: border transitions to accent-border on expandable items
|
||||
**Learnings:**
|
||||
- The `consultationId` mapping from activity entries to consultations isn't always 1:1 with the activity `id` — e.g., "Prescribing Data Pharmacist" activity maps to `pharmacy-manager-2017` consultation, "Community Pharmacist" maps to `duty-pharmacist-2016`
|
||||
- Skills→medications mapping is by exact name match (both files use same names: "Data Analysis", "Python", "SQL", "Power BI", "JavaScript / TypeScript")
|
||||
- `e.stopPropagation()` on the "View Results" link in Projects prevents the click from bubbling up and toggling the accordion
|
||||
- The expanded content structure varies per tile (bullets + codes for career, methodology + tags + results for projects, timeline for skills) but the AnimatePresence/motion.div wrapper is identical
|
||||
- All three tiles now have `cursor: 'pointer'` on expandable items and `border-color` transitions on hover/expand
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 16 — Task 18: Build Command Palette
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/CommandPalette.tsx` — full command palette overlay with:
|
||||
- Fixed overlay: `rgba(26,43,42,0.45)` background, `backdrop-filter: blur(4px)`, z-index 1000
|
||||
- Modal: 580px width, max-height 520px, 12px border-radius, two-layer shadow matching concept CSS
|
||||
- Search input row: Search icon (accent), auto-focus input (15px, font-ui), ESC kbd badge (mono)
|
||||
- Results area: scrollable, grouped by section with styled labels (10px, 600 weight, uppercase, 0.08em tracking)
|
||||
- Result items: 28px icon container (6px radius, colored bg per section), title (500 weight) + subtitle (11px, tertiary, ellipsis), hover/selected highlight (accent-light bg + accent-border outline)
|
||||
- Icon colors: teal (Experience, Quick Actions), green (Core Skills), amber (Active Projects, Achievements), purple (Education)
|
||||
- Footer: keyboard hints with styled kbd elements
|
||||
- CSS entrance animations: `palette-overlay-in` + `palette-modal-in`, 200ms with reduced-motion support
|
||||
- Rebuilt `src/lib/search.ts` with new palette data model:
|
||||
- `PaletteItem` interface with action union: scroll, expand, link, download
|
||||
- `buildPaletteData()`: 24 entries across 6 sections matching concept HTML exactly
|
||||
- `buildSearchIndex()`: fuse.js with weighted keys, threshold 0.3
|
||||
- `groupBySection()`: maintains defined section order
|
||||
- Legacy exports maintained for backward compat (ClinicalSidebar until Task 21)
|
||||
- Updated `src/components/DashboardLayout.tsx`: Ctrl+K listener, search bar click, action handler, CommandPalette rendered at layout level
|
||||
- Updated `src/components/Card.tsx`: added `tileId` prop → `data-tile-id` attribute
|
||||
- Updated all 7 tile components to pass `tileId` for scroll targeting
|
||||
- Added CSS keyframe animations in `src/index.css`
|
||||
**Learnings:**
|
||||
- Concept HTML palette has 24 curated entries — matched exactly rather than dynamically building from data files
|
||||
- `LucideIcon` type needed for icon map (not `React.ComponentType<{ size: number }>`)
|
||||
- `data-tile-id` on Card enables palette → tile scroll targeting
|
||||
- Custom event (`palette-expand`) dispatched for expand-on-select (not yet consumed by tiles)
|
||||
- Backward-compatible legacy exports prevent breaking old components
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 17 — Task 19: Responsive design
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/DashboardLayout.tsx`:
|
||||
- Sidebar: hidden on <lg (1024px) via `hidden lg:block` class
|
||||
- Main content padding: responsive Tailwind classes `p-4 pb-8 md:p-6 md:pb-10 lg:px-7 lg:pt-6 lg:pb-10`
|
||||
- Updated `src/index.css`:
|
||||
- Dashboard grid: mobile-first (1 col default → 2 col at md/768px)
|
||||
- Activity grid: mobile-first (1 col default → 2 col at md/768px)
|
||||
- Gap adjustments: 12px mobile, 16px tablet/desktop
|
||||
- Updated `src/components/TopBar.tsx`:
|
||||
- Brand text: "Headhunt Medical Center" → "HMC" on <sm (640px)
|
||||
- "Remote" badge: hidden on <md (768px)
|
||||
- Session badge: "Active Session · [time]" → "[time]" only on <xs (480px)
|
||||
- Updated `src/components/CommandPalette.tsx`:
|
||||
- Modal width: full-width on mobile with 8px edge margin, 580px on md+
|
||||
- Search input padding: reduced on mobile (12px → 8px)
|
||||
- Results padding: reduced on mobile (8px → 8px)
|
||||
- Footer: hidden on mobile (only visible on md+)
|
||||
- Overlay padding: responsive (8px on mobile, 10vh top on larger screens)
|
||||
**Learnings:**
|
||||
- Mobile-first approach: default styles for mobile, progressively enhance with md/lg breakpoints
|
||||
- Tailwind breakpoints: xs (480px), sm (640px), md (768px), lg (1024px), xl (1280px)
|
||||
- CommandPalette uses class-based width for responsive (w-full → md:w-[580px]) rather than inline style
|
||||
- All touch targets already meet 48px+ minimum (TopBar 48px height, search bar 42px, buttons have sufficient padding)
|
||||
- The sidebar being hidden on mobile is acceptable — all content is in the main scrollable area, and the command palette provides quick navigation
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Skipped — visual verification should be done by user at multiple breakpoints (1280px, 800px, 375px)
|
||||
|
||||
### Iteration 18 — Task 20: Accessibility audit
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/Card.tsx`:
|
||||
- Changed wrapper from `<div>` to `<article>` for semantic HTML (tiles are self-contained content sections)
|
||||
- Added `aria-hidden="true"` to CardHeader colored dot (decorative, text label conveys information)
|
||||
- Updated `src/components/TopBar.tsx`:
|
||||
- Added skip link (href="#main-content") positioned off-screen, visible only on focus
|
||||
- Skip link uses accent background, slides down on focus, slides up on blur
|
||||
- Added `aria-label="Active session information"` to session info container
|
||||
- Updated `src/components/DashboardLayout.tsx`:
|
||||
- Added `id="main-content"` to main element (skip link target)
|
||||
- Updated `src/components/Sidebar.tsx`:
|
||||
- Added `aria-hidden="true"` to status badge pulse dot (decorative, "Open to Opportunities" text label conveys status)
|
||||
- Updated `src/components/tiles/CareerActivityTile.tsx`:
|
||||
- Added `aria-hidden="true"` to colored dots (8px activity type indicators — decorative, activity title conveys information)
|
||||
- Updated `src/components/tiles/ProjectsTile.tsx`:
|
||||
- Added `aria-hidden="true"` to status dots (7px Complete/Ongoing/Live indicators — decorative, project name + year conveys information)
|
||||
- Updated `src/index.css`:
|
||||
- Added global `*:focus-visible` styles (2px accent outline, 2px offset)
|
||||
- Specific focus-visible styles for buttons, role="button", role="option", links (2px accent outline rgba(13,110,110,0.4))
|
||||
- Input/textarea focus-visible with slightly stronger accent (rgba 0.6, 0px offset)
|
||||
- Added `prefers-reduced-motion` override for pulse animation (disables pulse on status badge dot — keeps opacity 1)
|
||||
**Learnings:**
|
||||
- **Semantic HTML audit results:**
|
||||
- ✅ TopBar uses `<header>` element (Task 4)
|
||||
- ✅ Sidebar uses `<aside>` element (Task 5)
|
||||
- ✅ DashboardLayout main uses `<main>` element with aria-label (Task 7)
|
||||
- ✅ All tiles now use `<article>` element (this iteration)
|
||||
- ✅ Command palette uses role="dialog" with aria-modal (Task 18)
|
||||
- **Keyboard navigation audit results:**
|
||||
- ✅ Tab navigates between interactive elements (native browser behavior)
|
||||
- ✅ Enter/Space expand tile items, flip KPI cards, select palette results (Task 16-18)
|
||||
- ✅ Escape closes expanded items and command palette (Task 16-18)
|
||||
- ✅ Ctrl+K opens command palette (Task 18)
|
||||
- ✅ Arrow Up/Down navigate palette results (Task 18)
|
||||
- **ARIA attributes audit results:**
|
||||
- ✅ Command palette search: role="combobox", aria-expanded, aria-controls, aria-autocomplete, aria-activedescendant (Task 18)
|
||||
- ✅ Palette results: role="listbox", each result role="option", aria-selected (Task 18)
|
||||
- ✅ Palette overlay: role="dialog", aria-modal="true", aria-label="Command palette" (Task 18)
|
||||
- ✅ Expandable items: aria-expanded on trigger elements (Task 16)
|
||||
- ✅ KPI flip cards: aria-label describing front/back content, role="button", tabIndex={0} (Task 17)
|
||||
- ✅ Decorative dots: aria-hidden="true" on all colored status/type indicators (this iteration)
|
||||
- ✅ Session info: aria-label="Active session information" (this iteration)
|
||||
- **Focus management audit results:**
|
||||
- ✅ Command palette: focus trap implemented, focus moves to search input on open, returns to trigger on close (Task 18)
|
||||
- ✅ Focus-visible rings: 2px accent outline on all interactive elements (this iteration)
|
||||
- ✅ Skip to content link: only visible on focus, navigates to #main-content (this iteration)
|
||||
- ✅ Tile expansion: focus remains on trigger element (native browser behavior with role="button")
|
||||
- **prefers-reduced-motion audit results:**
|
||||
- ✅ All components check at module scope: `const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches`
|
||||
- ✅ Dashboard entrance (topbar/sidebar/content): duration: 0 when reduced motion (Task 7)
|
||||
- ✅ Tile expansion: duration: 0 when reduced motion (Task 16)
|
||||
- ✅ KPI flip: visibility toggle instead of 3D rotation when reduced motion (Task 17)
|
||||
- ✅ Palette entrance: animations disabled when reduced motion (Task 18)
|
||||
- ✅ Status badge pulse: pulse animation disabled when reduced motion (this iteration)
|
||||
- **Color contrast verification:**
|
||||
- ✅ Accent #0D6E6E on white #FFFFFF: ~5.5:1 (meets AA)
|
||||
- ✅ Primary #1A2B2A on white: ~15:1 (meets AAA)
|
||||
- ✅ Secondary #5B7A78 on white: ~4.6:1 (meets AA for normal text)
|
||||
- ✅ Tertiary #8DA8A5 on white: ~3.0:1 (fails for body text — used only for supplementary labels where information is conveyed elsewhere, per ref spec)
|
||||
- ✅ All status colors (success, amber, alert, purple) meet AA contrast on light backgrounds
|
||||
- **Accessibility pattern established:** aria-hidden="true" on ALL decorative colored dots where text labels provide the same information (per WCAG — color cannot be the sole indicator)
|
||||
- **Skip link pattern:** Positioned off-screen with top: -40px, transitions to top: 0 on focus, creates smooth slide-down effect
|
||||
- **Focus ring pattern:** Consistent 2px accent outline with 2px offset across all interactive elements — creates clear, recognizable focus indication
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing warning), build ✓
|
||||
**Visual review:** Not applicable — accessibility improvements are non-visual (semantic HTML, ARIA, keyboard nav) except for focus rings which should be tested by user
|
||||
|
||||
### Iteration 19 — US-018: ConsultationDetail renderer (already complete)
|
||||
**Status:** Already implemented by prior iteration — marked as passed
|
||||
**Changes:** None needed — `src/components/detail/ConsultationDetail.tsx` already existed with full implementation (role header, history, achievements, outcomes, coded entries), wired into DetailPanel for both `consultation` and `career-role` types.
|
||||
|
||||
### Iteration 19b — US-020: Create SkillDetail renderer for detail panel
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/detail/SkillDetail.tsx` — narrow panel renderer for individual skills:
|
||||
- Skill header: 20px name, frequency badge (accent-light), status badge (success/neutral)
|
||||
- Category label: 11px uppercase tertiary text (Technical / Healthcare Domain / Strategic & Leadership)
|
||||
- Proficiency bar: 6px height, color-coded (green >=90%, teal >=75%, amber <75%), percentage label
|
||||
- Experience section: large year number (28px) + "years" + "Since YYYY" (Geist Mono)
|
||||
- "Used in" section: lists roles from constellation data (roleSkillMappings), with org-colored dots, role labels, organization + date range
|
||||
- Updated `src/components/DetailPanel.tsx`:
|
||||
- Added import for SkillDetail
|
||||
- Added `content.type === 'skill'` rendering branch
|
||||
- Narrowed placeholder fallback to exclude 'skill' type
|
||||
**Learnings:**
|
||||
- Constellation data provides the skill-to-role mapping via `roleSkillMappings` — filter by skill ID, then look up role nodes for display
|
||||
- Role nodes sorted chronologically (earliest first) gives a natural career progression view
|
||||
- The non-null assertions on `node!` are safe because the `.filter(Boolean)` ensures no nulls
|
||||
- Pre-existing lint error (`_sectionId` in DashboardLayout:64) is unrelated to this work
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 20 — US-021: Create SkillsAllDetail renderer for detail panel
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/detail/SkillsAllDetail.tsx` — narrow panel renderer for full categorised skill list:
|
||||
- Groups all 21 skills by Technical / Healthcare Domain / Strategic & Leadership
|
||||
- Category headers match CoreSkillsTile style: 10px uppercase label + divider line + item count (Geist Mono)
|
||||
- Each skill row: icon container (26px, accent-light), name + frequency/years (Geist Mono), mini proficiency bar (40px wide, color-coded), percentage, chevron
|
||||
- Skill rows clickable → `openPanel({ type: 'skill', skill })` to switch panel to individual SkillDetail
|
||||
- If opened with category filter (from "View all" button), scrolls to and highlights that category (accent-colored header + bottom border)
|
||||
- Hover: border color shift + shadow deepens (matching CoreSkillsTile rows)
|
||||
- Keyboard: Enter/Space triggers skill detail, role="button", tabIndex={0}, descriptive aria-label
|
||||
- Updated `src/components/DetailPanel.tsx`:
|
||||
- Added import for SkillsAllDetail
|
||||
- Added `content.type === 'skills-all'` rendering branch with category prop pass-through
|
||||
- Narrowed placeholder fallback to exclude 'skills-all' type
|
||||
**Learnings:**
|
||||
- Reused the SkillRow pattern from CoreSkillsTile but added a mini proficiency bar instead of status badge — provides more info density in the "view all" context
|
||||
- The `useRef<Record<string, HTMLDivElement | null>>` pattern with callback ref works well for multiple dynamic refs
|
||||
- Category highlight uses both accent-colored text and a 2px bottom border to visually distinguish the filtered category
|
||||
- Pre-existing lint error (`_sectionId` in DashboardLayout:64) continues to be unrelated
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 21 — US-022: Create EducationDetail renderer for detail panel
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Created `src/components/detail/EducationDetail.tsx` — narrow panel renderer for education entries:
|
||||
- Header: type-specific icon (GraduationCap/Award/BookOpen/FlaskConical) + title + institution (purple accent) + duration + classification badge (purple-light bg)
|
||||
- Research Project section: renders `extra.researchDescription` for MPharm entry
|
||||
- OSCE Performance section: renders score in success-colored badge with description
|
||||
- Extracurricular Activities section: bullet list from `extra.extracurriculars`
|
||||
- Programme Overview section: renders `extra.programmeDetail` for Mary Seacole
|
||||
- Notes section: italic secondary text from `document.notes`
|
||||
- All sections use shared `sectionHeaderStyle` (12px uppercase, secondary color, 0.05em tracking)
|
||||
- Updated `src/components/DetailPanel.tsx`:
|
||||
- Added import for EducationDetail
|
||||
- Added `content.type === 'education'` rendering branch
|
||||
- Narrowed placeholder fallback to exclude 'education' type
|
||||
**Learnings:**
|
||||
- Icon type for lucide-react must use `LucideIcon` type, not `React.ComponentType<{ size: number }>` — the latter causes type incompatibility with ForwardRefExoticComponent
|
||||
- The `educationExtras` data matches documents by `documentId` field — currently only MPharm and Mary Seacole have extras
|
||||
- Purple color (#7C3AED) is used consistently for education across the app (dot colors in CardHeader, CareerActivity, and now EducationDetail institution text and classification badge)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 22 — US-023: Install D3 and scaffold CareerConstellation component
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Installed `d3` and `@types/d3` npm packages (70 packages added)
|
||||
- Created `src/components/CareerConstellation.tsx` — scaffolded component with:
|
||||
- Props: `onRoleClick(id)` and `onSkillClick(id)` stored in callbacksRef for future D3 event binding
|
||||
- Responsive SVG container using ResizeObserver: 400px desktop, 300px tablet (<1024px), 250px mobile (<768px)
|
||||
- viewBox matches actual dimensions for responsive scaling
|
||||
- Radial gradient background: `#F0F5F4` (--bg-dashboard) center → `#FFFFFF` (--surface) edge, rx=6
|
||||
- Placeholder text showing node/link counts from constellation data (Geist Mono, tertiary color)
|
||||
- Container with border-radius and overflow hidden
|
||||
- SVG has `role="img"` and `aria-label` for accessibility
|
||||
- Imperative SVG drawing via useEffect on svgRef (matches ECG pattern for D3 compatibility)
|
||||
**Learnings:**
|
||||
- `callbacksRef` pattern stores click handlers in a ref for D3 imperative code — avoids stale closures when D3 attaches event listeners in US-024/026
|
||||
- ResizeObserver provides cleaner responsive behavior than CSS media queries for SVG — container width determines height tier
|
||||
- The SVG namespace `http://www.w3.org/2000/svg` is required for createElement in imperative SVG building
|
||||
- D3 is installed but not yet imported — US-024 will use `d3.forceSimulation` etc. on the svgRef
|
||||
- Pre-existing lint error (`_sectionId` in DashboardLayout:64) continues to be unrelated
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — component not yet integrated into CareerActivityTile (will be wired in US-026).
|
||||
|
||||
### Iteration 23 — US-024: Build D3 force-directed graph rendering in CareerConstellation
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Rewrote `src/components/CareerConstellation.tsx` to use D3 force simulation:
|
||||
- Replaced imperative SVG createElement with D3 selections (`d3.select`, `.selectAll`, `.join`)
|
||||
- D3 force simulation with: `forceManyBody(-200)`, `forceLink(distance 80, strength from data * 0.5)`, `forceX` chronological (roles positioned left-to-right by `startYear` via `d3.scaleLinear`), `forceY` centered at `height/2`, `forceCollide` (30 for roles, 14 for skills)
|
||||
- Role nodes: 24px radius circles filled with `orgColor`, 2px white stroke, 8px white `shortLabel` text centered
|
||||
- Skill nodes: 10px radius circles, color-coded by domain (clinical=#059669 green, technical=#0D6E6E teal, leadership=#D97706 amber), 1.5px white stroke, opacity 0.85
|
||||
- Skill labels: 9px Geist Mono text below each skill node (using `shortLabel`)
|
||||
- Links: 1px `#D4E0DE` lines at opacity 0.3
|
||||
- Node positions constrained within SVG bounds on each tick
|
||||
- Layered rendering: links group below nodes group
|
||||
- `simulationRef` stores active simulation, stopped on cleanup or dimension change
|
||||
- Preserved existing ResizeObserver responsive height (400/300/250px)
|
||||
- Preserved radial gradient background, `role="img"`, `aria-label`
|
||||
- Removed unused `ConstellationLink` type import (caught by typecheck)
|
||||
**Learnings:**
|
||||
- D3 `forceLink.strength()` receives the link object — cast to `SimLink` to access `.strength` field
|
||||
- Role `forceX` uses strong pull (0.8) to maintain chronological layout; skill `forceX` uses weak pull (0.05) to let links drive position
|
||||
- `forceCollide` radius should be slightly larger for skills than their visual radius to prevent label overlap
|
||||
- The `SimNode` interface extending `ConstellationNode` with `x/y/vx/vy/fx/fy` satisfies D3's `SimulationNodeDatum` needs
|
||||
- Pre-existing lint issues: `_sectionId` error + 2 context warnings — all unrelated
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — component not yet wired into CareerActivityTile (US-026). D3 simulation verified via successful build.
|
||||
|
||||
### Iteration 24 — US-025: Add accessibility to CareerConstellation
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/CareerConstellation.tsx` with four accessibility features:
|
||||
- **Screen-reader description**: `buildScreenReaderDescription()` generates a hidden `<p>` (sr-only via clip rect) describing all 5 roles, their organizations, year ranges, and associated skills from `roleSkillMappings`
|
||||
- **Keyboard navigation**: Hidden `<button>` elements overlaid on the SVG container, one per role node. Tab navigates through roles, Enter/Space triggers `onRoleClick`. Each button has descriptive `aria-label` (role name, org, year range)
|
||||
- **Focus indicators**: SVG `.focus-ring` circle (ROLE_RADIUS + 4px) rendered behind each role node. Transparent by default, becomes teal `#0D6E6E` stroke when the corresponding hidden button receives focus (tracked via `focusedNodeId` state + `useEffect` on D3 selection)
|
||||
- **prefers-reduced-motion**: When enabled, simulation runs 300 ticks synchronously (`simulation.stop()` + loop), then renders final positions immediately — no animation frames. Uses the established module-scope `matchMedia` check pattern
|
||||
- Imported `roleSkillMappings` from constellation data for SR description
|
||||
- Added `useCallback` for `handleNodeKeyDown` to prevent re-renders
|
||||
**Learnings:**
|
||||
- D3 focus indicators work via a dual approach: hidden HTML buttons for actual keyboard focus, plus D3-drawn SVG circles that respond to React state changes — avoids fighting D3's imperative model with React's declarative focus management
|
||||
- Running `simulation.tick()` in a loop (300 iterations) is sufficient to reach stable positions for this graph size (5 roles + 21 skills)
|
||||
- The `.focus-ring` circle must be appended before the main circle in the SVG group to render behind it (SVG painting order = DOM order)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — not yet wired into CareerActivityTile (US-026).
|
||||
|
||||
### Iteration 25 — US-026: Add hover and click interactions to CareerConstellation
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/CareerConstellation.tsx` with three interaction features:
|
||||
- **Hover highlighting**: Built adjacency map from `constellationLinks`. On `mouseenter`, non-connected nodes fade to 0.15 opacity. Connected links brighten to teal (`#0D6E6E`), thicken to 2px, increase opacity to 0.7. Non-connected links dim to 0.1 opacity. Role hover also scales connected skill nodes up (+3px radius) via D3 transition (150ms).
|
||||
- **Hover reset**: On `mouseleave`, all nodes reset to full opacity, skill circles return to `SKILL_RADIUS`, links return to default stroke/opacity/width.
|
||||
- **Click handlers**: Click on any node calls `callbacksRef.current.onRoleClick(id)` or `onSkillClick(id)` via the existing callbacksRef pattern (avoids stale closures).
|
||||
- Added `.node-circle` and `.node-label` classes to circles/text for targeted D3 selections during hover
|
||||
- Updated `src/components/tiles/CareerActivityTile.tsx`:
|
||||
- Replaced placeholder `<div>` with actual `<CareerConstellation>` component
|
||||
- Added `handleRoleClick(roleId)` → finds consultation by ID → `openPanel({ type: 'career-role', consultation })`
|
||||
- Added `handleSkillClick(skillId)` → finds skill by ID → `openPanel({ type: 'skill', skill })`
|
||||
- Refactored `handleItemClick` to delegate to `handleRoleClick` for consistency
|
||||
- Imported `skills` from `@/data/skills` and `CareerConstellation` from `../CareerConstellation`
|
||||
**Learnings:**
|
||||
- D3 hover uses `mouseenter`/`mouseleave` (not `mouseover`/`mouseout`) to avoid bubbling issues with nested SVG groups
|
||||
- The adjacency map uses source/target strings from `constellationLinks` (pre-simulation), not SimNode objects — link data gets resolved by D3 after forceLink runs, so during hover the source/target may be either string or SimNode objects. The click/hover handlers check both forms.
|
||||
- The `callbacksRef` pattern established in US-023 works perfectly for D3 click events — no stale closures
|
||||
- Pre-existing lint issues: `_sectionId` error + 2 context warnings — all unrelated
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
|
||||
### Iteration 26 — US-027: Restyle LoginScreen with teal accents
|
||||
**Status:** Complete
|
||||
**Changes:**
|
||||
- Updated `src/components/LoginScreen.tsx`:
|
||||
- Replaced all `#005EB8` (NHS Blue) with `#0D6E6E` (teal accent): shield icon color, active field borders, cursor color, button default bg, focus ring
|
||||
- Replaced `#004D9F` (hover) with `#0A8080` (teal hover)
|
||||
- Replaced `#004494` (pressed) with `#085858` (teal pressed)
|
||||
- Background color: `#1E293B` → `#1A2B2A` (warmer, cohesive with dashboard palette)
|
||||
- Shield icon container: `rgba(0, 94, 184, 0.07)` → `rgba(13, 110, 110, 0.08)` (teal-tinted)
|
||||
**Learnings:**
|
||||
- LoginScreen had 6 instances of `#005EB8` — all replaced for consistency
|
||||
- The background change from `#1E293B` (slate) to `#1A2B2A` (dark teal-green) creates visual cohesion with the teal accent palette
|
||||
- Button states follow the teal gradient: default #0D6E6E → hover #0A8080 → pressed #085858 (progressively darker)
|
||||
**Quality checks:** typecheck ✓, lint ✓ (1 pre-existing error + 2 warnings), build ✓
|
||||
**Visual review:** Skipped — no browser tools available.
|
||||
### LoginScreen.tsx Key Lines
|
||||
- Line 20: connectionState useState
|
||||
- Line 43: canLogin derived state
|
||||
- Line 60-101: startLoginSequence (typing animation)
|
||||
- Line 110-137: useEffect with connectionTimeout (2000ms) and startLoginSequence delay (400ms)
|
||||
- Line 145-415: JSX render (card, form, button, status indicator)
|
||||
- Line 213-218: Shield icon (to be replaced)
|
||||
- Line 229: "CareerRecord PMR" title
|
||||
- Line 240: "Clinical Information System" subtitle
|
||||
- Line 367-388: Connection status indicator (6px dot, 10px text)
|
||||
|
||||
---
|
||||
|
||||
## 2026-02-14 - US-028
|
||||
- **What was implemented:** Changed login username from A.CHARLWOOD to a.recruiter, added connection status indicator with red→green transition, updated button disabled logic to require both typing complete AND connection established.
|
||||
- **Files changed:**
|
||||
- `src/components/LoginScreen.tsx` — new `connectionState` state, connection timer (2000ms), connection status indicator UI (6px dot + Geist Mono text), `canLogin` derived state replacing `typingComplete` for button control
|
||||
- `src/components/DashboardLayout.tsx` — fixed pre-existing lint error (unused `_sectionId` parameter, added eslint-disable comment)
|
||||
- **Learnings for future iterations:**
|
||||
- The DashboardLayout had a pre-existing lint error with `_sectionId` — ESLint config doesn't respect underscore-prefix unused var convention, needed `eslint-disable-next-line` comment. TypeScript `tsc -b` (used in build) DOES respect underscore prefix though.
|
||||
- Connection status uses CSS `transition: 300ms` for the color change — matches the spec for smooth dot/text color transition
|
||||
- `canLogin` is a derived value (not state) combining `typingComplete && connectionState === 'connected'` — cleaner than adding another state variable
|
||||
|
||||
---
|
||||
|
||||
## 2026-02-14 - US-029
|
||||
- **What was implemented:** Added post-login loading state with CSS spinner (~600ms) that replaces the login card content after clicking Log In. Updated TopBar session display name from "Dr. A.CHARLWOOD" to "A.RECRUITER".
|
||||
- **Files changed:**
|
||||
- `src/components/LoginScreen.tsx` — new `isLoading` state, handleLogin now sets isLoading before isExiting, card content conditionally renders either login form or spinner + "Loading clinical records..." text. Spinner uses CSS `login-spin` animation.
|
||||
- `src/components/TopBar.tsx` — changed session name from "Dr. A.CHARLWOOD" to "A.RECRUITER"
|
||||
- `src/index.css` — added `@keyframes login-spin` and `.login-spinner` class, plus `prefers-reduced-motion` override (static indicator, no spin)
|
||||
- **Learnings for future iterations:**
|
||||
- The loading state replaces card content via conditional rendering (`isLoading ? spinner : form`) rather than an overlay — keeps the card dimensions stable
|
||||
- The sequence is: buttonPressed (100ms) → isLoading (600ms) → isExiting (200ms) → onComplete. With reduced motion, loading and exit delays are 0ms.
|
||||
- Spinner uses pure CSS animation (`border-top-color` trick) — no library needed
|
||||
|
||||
---
|
||||
|
||||
## 2026-02-14 - US-030
|
||||
- **What was implemented:** Updated CommandPalette search index to include all 21 skills (not just 5), added `panel` action type to PaletteAction union, and wired skill/KPI/project palette results to open detail panels directly.
|
||||
- **Files changed:**
|
||||
- `src/lib/search.ts` — Added `panel` action type with `DetailPanelContent` payload. Skills section now iterates all 21 skills from `skills.ts` (was hardcoded to 5). Project results find matching `Investigation` by ID and use `panel` action. Achievement results find matching `KPI` by ID and use `panel` action. Imported `kpis` and `DetailPanelContent` type.
|
||||
- `src/components/DashboardLayout.tsx` — Added `panel` case to `handlePaletteAction` switch that calls `openPanel(action.panelContent)`. Imported `useDetailPanel` from context.
|
||||
- **Learnings for future iterations:**
|
||||
- The `panel` action type carries a full `DetailPanelContent` discriminated union payload — this means any palette item can open any detail panel type without intermediate mapping
|
||||
- Achievement "Team of 12 Led" was updated to "1.2M Population Served" to match the KPI data change from US-006
|
||||
- For projects, a fallback to `scroll` action is used when the investigation ID doesn't match — defensive pattern for data mismatches
|
||||
|
||||
---
|
||||
|
||||
## 2026-02-14 - US-031
|
||||
- **What was implemented:** Responsive testing and fixes for all new components. Audited DetailPanel, SubNav, CareerConstellation, dashboard grid, CoreSkillsTile, touch targets, and 375px overflow.
|
||||
- **Files changed:**
|
||||
- `src/components/SubNav.tsx` — Added `overflowX: auto`, `scrollbarWidth: 'none'`, horizontal padding, `flexShrink: 0` on tab buttons, `minHeight: 36px` for touch targets, flex layout for vertical centering
|
||||
- `src/index.css` — Added `.subnav-scroll::-webkit-scrollbar { display: none }` for WebKit scrollbar hiding
|
||||
- `src/components/DetailPanel.tsx` — Enlarged close button from 32x32px to 44x44px for mobile touch target compliance
|
||||
- `src/components/tiles/CoreSkillsTile.tsx` — Added `minHeight: 44px` to SkillRow and "View all" button for touch target compliance
|
||||
- `src/components/tiles/ProjectsTile.tsx` — Added `minHeight: 44px` to ProjectItem for touch target compliance
|
||||
- `src/components/tiles/LastConsultationTile.tsx` — Added `minHeight: 44px` to "View full record" button
|
||||
- **Audit results (already passing):**
|
||||
- DetailPanel: `@media (max-width: 767px)` already set both widths to 100vw ✓
|
||||
- CareerConstellation: `getHeight()` already returns 400/300/250px by breakpoint ✓
|
||||
- Dashboard grid: mobile-first 1fr → 2fr at 768px, KPIs + Projects stack correctly ✓
|
||||
- CoreSkillsTile: `full` prop spans both columns at all breakpoints ✓
|
||||
- No horizontal overflow at 375px: TopBar search hidden <768px, no problematic nowrap on wide content ✓
|
||||
- **Learnings for future iterations:**
|
||||
- `scrollbarWidth: 'none'` (Firefox) + `::-webkit-scrollbar { display: none }` (Chrome/Safari) together hide scrollbars cross-browser
|
||||
- WCAG touch target minimum is 44x44px — check all `role="button"`, `<button>`, and clickable elements
|
||||
- SubNav at 375px has ~345px available (375 - 2*16px padding) — 5 short labels with 24px gaps fit without scroll, but the scroll fallback is good insurance
|
||||
|
||||
## 2026-02-14 — US-032
|
||||
- **What was implemented:** Reduced motion audit, final cleanup, and visual review
|
||||
- **Files changed:**
|
||||
- `src/index.css` — Added prefers-reduced-motion overrides for SubNav button transitions and smooth scroll behavior. Removed 18 unused `--pmr-*` legacy CSS variables and `.pmr-theme` utility class.
|
||||
- `src/components/LoginScreen.tsx` — Connection status dot and text transitions now respect `prefersReducedMotion` (instant when enabled).
|
||||
- `src/components/detail/ProjectDetail.tsx` — Created missing ProjectDetail renderer (project name, year, status badge, methodology, tech stack tags, results bullets, external link button).
|
||||
- `src/components/DetailPanel.tsx` — Wired ProjectDetail for `content.type === 'project'`. Removed placeholder fallback (all content types now have renderers).
|
||||
- Deleted `src/hooks/useBreakpoint.ts` (unused)
|
||||
- Deleted `src/data/profile.ts` (unused — PatientSummaryTile has profile text hardcoded)
|
||||
- **Learnings for future iterations:**
|
||||
- ProjectDetail was missing despite US-019 being marked as passed — always verify file existence, not just PRD status
|
||||
- `profile.ts` was created but never imported — PatientSummaryTile hardcodes the profile text instead
|
||||
- `useBreakpoint.ts` was orphaned after its consumers were deleted in US-001
|
||||
- Legacy `--pmr-*` CSS variables were all superseded by the new design token system and safe to remove
|
||||
- `pmr-scrollbar` class is still actively used (Sidebar, DashboardLayout, CommandPalette) — do not remove
|
||||
- SubNav inline transitions need CSS `!important` override in prefers-reduced-motion since they're set via inline styles
|
||||
- The `html { scroll-behavior: smooth }` also needs a reduced-motion override to `auto`
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user