chore: auto-commit before merge (loop primary)
This commit is contained in:
@@ -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.5–2px)
|
||||
- [ ] 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, 2–3px 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.
|
||||
|
||||
Reference in New Issue
Block a user