chore: auto-commit before merge (loop primary)

This commit is contained in:
2026-02-16 14:36:25 +00:00
parent 9276955fa8
commit aca57714e4
23 changed files with 1859 additions and 513 deletions
+137 -69
View File
@@ -1,87 +1,155 @@
# Task: D3 Career Constellation Remediation (Hover, Timeline Parity, Visual Alignment)
# Task: CareerConstellation Overhaul
Implement a full remediation of the career constellation chart and its linked timeline UI so interactions are reliable, timeline semantics are correct, and styling aligns with the rest of the site typography/tokens.
## Context
Recent chart refresh work did not fully resolve key issues:
- Hover highlighting is still not consistently activating on chart nodes.
- Timeline behavior in the chart is now more broken versus the work-experience timeline.
- Styling in the chart layer is not fully aligned with the main design system (including font token consistency).
The implementation should be grounded in the current codebase and preserve existing UX intent where possible.
Refactor, visually improve, and add chronological animation to the CareerConstellation D3 force chart — the centrepiece of the portfolio's Patient Pathway section.
## Requirements
- Fix hover interaction reliability in the D3 chart:
- Ensure node hover consistently triggers graph highlighting on desktop.
- Preserve touch behavior (tap-to-pin and clear interactions).
- Preserve keyboard accessibility interactions.
- Remove interaction-layer conflicts:
- Resolve any pointer interception between invisible accessibility overlays and SVG node hit targets.
- Ensure focus-only controls do not break pointer hover behavior.
- Correct timeline data/semantic parity:
- Ensure constellation role nodes map to the intended work-experience scope.
- Prevent unintended education entries from being treated as role nodes unless explicitly intended.
- Align ordering semantics between the chart timeline and work-experience timeline.
- Stabilize highlight state behavior:
- Ensure graph highlight state and linked timeline card highlighting remain coherent when hovering roles vs skills.
- Avoid reset/flicker edge cases on mouseleave/blur transitions.
- Align chart styling with site design system:
- Use canonical font tokens consistently (UI vs mono usage should match the broader app).
- Remove or replace invalid/undefined font token usage impacting timeline/chart-adjacent components.
- Keep visual treatment consistent with existing dashboard cards/tokens (no unrelated redesign).
- Keep architecture maintainable:
- Clarify data exports for timeline consumers (career-only, education-only, combined) where needed.
- Avoid duplicate or dead timeline component paths if they create inconsistency.
### Phase 1 — Refactor the Monolith
## Validation Requirements
Decompose `src/components/CareerConstellation.tsx` (1102 lines) into focused modules:
Run and pass:
- `npm run lint`
- `npm run typecheck`
- `npm run build`
```
src/components/constellation/
CareerConstellation.tsx -- Orchestrator (< 300 lines)
MobileAccordion.tsx -- Mobile tap-to-expand accordion
ConstellationLegend.tsx -- Domain legend with node counts
AccessibleNodeOverlay.tsx -- Keyboard navigation button overlay
constants.ts -- All magic numbers as named exports
types.ts -- SimNode, SimLink, LayoutParams, local interfaces
Also perform manual behavioral checks and record concise notes in `.ralph/review.md`:
- Desktop hover on role nodes and skill nodes.
- Cross-highlight behavior between chart and timeline cards.
- Touch/coarse-pointer behavior (tap-to-pin and clear).
- Keyboard focus navigation and activation behavior.
- Timeline order parity sanity-check against work-experience content.
src/hooks/
useForceSimulation.ts -- D3 simulation lifecycle (setup, forces, tick, cleanup)
useConstellationHighlight.ts -- applyGraphHighlight + connectedMap + highlight refs
useConstellationInteraction.ts -- Mouse/touch/pin handlers, callback refs
```
## Likely Files In Scope
- [ ] Constants extracted (forces, sizes, opacities, durations)
- [ ] Types extracted (SimNode, SimLink, LayoutParams)
- [ ] MobileAccordion extracted as standalone component
- [ ] ConstellationLegend extracted
- [ ] AccessibleNodeOverlay extracted
- [ ] useForceSimulation hook created
- [ ] useConstellationHighlight hook created
- [ ] useConstellationInteraction hook created
- [ ] Orchestrator composed from hooks + sub-components (< 300 lines)
- [ ] All existing behaviour preserved (hover, click, tap, keyboard, mobile, detail panel)
- [ ] `npm run lint && npm run typecheck && npm run build` passes
- `src/components/CareerConstellation.tsx`
- `src/components/DashboardLayout.tsx`
- `src/components/TimelineInterventionsSubsection.tsx`
- `src/components/WorkExperienceSubsection.tsx` (if retained, removed, or reintegrated)
- `src/data/timeline.ts`
- `src/data/constellation.ts`
- `src/index.css`
- Related types in `src/types/pmr.ts` if needed
### Phase 2 — Visual Improvements
Enhance the chart aesthetics while maintaining the PMR design language:
**Links:**
- [ ] Strength-weighted stroke width at rest: `0.5 + strength * 1.5` (range 0.52px)
- [ ] Domain-colored at rest (very low opacity: `0.08 + strength * 0.12`)
- [ ] Improved bezier curves: offset control point by vertical distance (`cx = (sx+tx)/2 + (ty-sy)*0.15`)
- [ ] On highlight: width `1 + strength * 2`, domain color at higher opacity
**Skill nodes:**
- [ ] Thin domain-colored stroke at rest (`stroke-width: 1, stroke-opacity: 0.4`)
- [ ] Size encoding by connected role count: `baseRadius + roleCount * 0.8`
- [ ] On highlight: subtle glow filter (feGaussianBlur, 23px stdDeviation, domain color)
**Role nodes:**
- [ ] Fill gradient: left-to-right from orgColor@0.08 to orgColor@0.18
- [ ] On highlight: fill-opacity 0.25, stroke-width 2, shadow-md filter
**Entry animation (mount, replaced by over-time animation in Phase 3):**
- [ ] Timeline guides fade in (200ms)
- [ ] Role nodes slide in from left along connectors (staggered 80ms, 300ms each)
- [ ] Skill nodes scale up from 0 (staggered 30ms, 250ms each)
- [ ] Links draw on via stroke-dashoffset (after source+target visible)
- [ ] Skipped entirely when `prefers-reduced-motion`
**Legend:**
- [ ] Domain node counts displayed: "Technical (8) · Clinical (6) · Leadership (7)"
### Phase 3 — Over-Time Animation
Build the constellation chronologically from 2009 to present:
**Data changes:**
- [ ] Modify `buildConstellationData()` in `src/data/timeline.ts` to include education entities
- [ ] Education entities appear as nodes on the timeline (use `type: 'role'` with education styling, or add `type: 'education'`)
- [ ] Update `src/types/pmr.ts` if new node types are needed
- [ ] Timeline order (oldest first): A-Levels (2009) → MPharm (2011) → Pre-Reg (2015) → Duty Manager (2016) → Pharmacy Manager (2017) → High Cost Drugs (2022) → Deputy Head (2024) → Interim Head (2025)
**Animation architecture:**
- [ ] Create `useTimelineAnimation` hook in `src/hooks/`
- [ ] All nodes present in simulation from start but hidden (opacity: 0) — stable positions, no layout jitter
- [ ] Reveal chronologically: each role/education entity appears, then its skills animate in
- [ ] Skills already visible from earlier roles just get new links (reinforcement pulse: scale 1.3x → 1.0x over 350ms)
- [ ] Uses requestAnimationFrame + timestamp scheduler (not setTimeout chains)
- [ ] Animation state machine in refs: IDLE → PLAYING → PAUSED → HOLDING → RESETTING → loop back to PLAYING
- [ ] Auto-plays on load (after force simulation settles)
- [ ] Loops continuously: hold 3s at end → fade all 400ms → pause 200ms → restart
**Visual effects during reveal:**
- [ ] Role/education nodes scale from 0 with ease-out-back
- [ ] New skill nodes scale from 0 with ease-out
- [ ] Links draw on via stroke-dashoffset animation
- [ ] Year indicator overlay (top-left of SVG, monospace font, var(--text-tertiary))
**Accessibility:**
- [ ] `prefers-reduced-motion`: skip animation entirely, show final state immediately
- [ ] Play/pause button with appropriate aria-label
### Phase 4 — Animation + Interaction Integration
Wire the animation to the existing highlight system:
- [ ] Hover/tap pauses animation, applies highlight normally (on visible nodes only)
- [ ] Highlight only operates on revealed nodes — unrevealed nodes stay at opacity 0
- [ ] Multiplicative opacity: animation visibility (0 or target) × highlight emphasis (1.0 or 0.15)
- [ ] Resume animation 800ms after last interaction ends (mouseout / background tap)
- [ ] Explicit pause via button stays paused until user clicks play again
- [ ] Play/pause toggle button (bottom-right of SVG area, subtle styling, larger touch target on mobile)
- [ ] Mobile accordion works during paused state
- [ ] Keyboard navigation works during paused state
- [ ] Click → detail panel works during paused state
## Success Criteria
All of the following must be true:
- [ ] Constellation hover highlighting works reliably with pointer input.
- [ ] Accessibility/focus affordances remain functional without breaking pointer interactions.
- [ ] Timeline/role mapping in the chart is semantically correct and aligned with work-experience content.
- [ ] Highlight synchronization between chart and timeline cards behaves predictably.
- [ ] Font/token usage in chart and timeline-adjacent components is consistent with the app's design tokens.
- [ ] Any legacy/duplicate timeline path that causes divergence is resolved or clearly justified.
- [ ] `npm run lint` passes.
- [ ] `npm run typecheck` passes.
- [ ] `npm run build` passes.
- [ ] Reviewer records manual verification outcomes in `.ralph/review.md`.
All of the following must be true for LOOP_COMPLETE:
- [ ] `npm run lint && npm run typecheck && npm run build` passes with zero errors
- [ ] CareerConstellation orchestrator is < 300 lines
- [ ] Education entities (A-Levels, MPharm) appear in the constellation
- [ ] Animation auto-plays on load and loops continuously
- [ ] Network builds chronologically from 2009 through to present
- [ ] Skills accumulate visually — existing skills get new links, not duplicated
- [ ] Hover/tap pauses animation and shows highlight on visible nodes
- [ ] Animation resumes after 800ms of no interaction
- [ ] Play/pause button visible and functional
- [ ] Existing interactions preserved: click → detail panel, keyboard nav, mobile accordion
- [ ] `prefers-reduced-motion` shows final state immediately with no animation
- [ ] Links show domain colors and strength-weighted width at rest
- [ ] No TypeScript `any` types introduced
- [ ] No dead code or commented-out blocks
## Constraints
- Use the existing TypeScript + React + Vite stack and project conventions.
- Keep changes scoped to constellation/timeline correctness and visual consistency.
- Do not introduce broad unrelated refactors.
- Prioritize correctness and maintainability over cosmetic novelty.
- TypeScript strict mode (`noUnusedLocals`, `noUnusedParameters`)
- Path alias: `@/*``src/*`
- Styling: Tailwind utilities + CSS custom properties for design tokens
- D3 v6 (already installed)
- Framer Motion for non-D3 animations; respect `prefers-reduced-motion`
- Design tokens: Primary teal #00897B, Accent coral #FF6B6B, PMR greens/teals/greys
- Font tokens: `--font-ui` (Elvaro), `--font-geist-mono` (monospace), `--font-primary` / `--font-secondary`
- No automated tests — quality gates are lint + typecheck + build
- D3 patterns: reference `.claude/skills/d3-visualization/` for force layout examples
## Key Architecture Decisions
1. **"All nodes hidden" for animation** — every node participates in the force simulation from the start (positions are stable). Reveal via opacity transitions only. Do NOT dynamically add/remove nodes from the simulation.
2. **Ref-based animation state** — the animation state machine lives in refs (not React state) to avoid re-renders in the rAF loop. Only sync to React state for UI controls (play/pause button).
3. **Multiplicative opacity model** — animation controls visibility (0 or target), highlight controls emphasis (1.0 or 0.15). Final opacity = animation × highlight. This prevents the two systems from conflicting.
4. **Imperative D3 + React hybrid** — D3 manages SVG rendering and force simulation imperatively via refs. React manages keyboard overlay buttons and UI controls. Follow the existing pattern in the codebase.
## Status
Track progress in `.ralph/plan.md` and keep it updated.
When all success criteria are met, print `LOOP_COMPLETE`.
Track progress here. Mark items complete as you go.
When ALL success criteria are met, print LOOP_COMPLETE.