93 lines
8.3 KiB
Plaintext
93 lines
8.3 KiB
Plaintext
# Progress Log — Career Constellation Clinical Pathway Overhaul
|
|
# Branch: ralph/constellation-overhaul
|
|
# Started: 2026-02-16
|
|
|
|
## Codebase Patterns
|
|
- CareerConstellation.tsx is a D3 force-directed graph rendered in an SVG with React overlay buttons for accessibility
|
|
- D3 simulation uses forceSimulation with charge, link, x, y, and collide forces
|
|
- Module-level window.matchMedia reads for prefersReducedMotion and supportsCoarsePointer
|
|
- DashboardLayout manages constellation state: highlightedNodeId, pinnedNodeId via callbacks
|
|
- Work experience data in src/data/consultations.ts, skills in src/data/skills.ts, constellation-specific data in src/data/constellation.ts
|
|
- CSS layout: .pathway-columns is a grid that switches from 1fr (mobile) to minmax(0,1.15fr) minmax(0,1.5fr) at desktop breakpoint
|
|
- .pathway-graph-sticky has position: sticky; top: 12px; min-height: 100% for the graph column
|
|
- containerHeight prop drives graph height on desktop; on mobile (viewport < 1024px) uses MOBILE_FALLBACK_HEIGHT (360px)
|
|
- Use window.innerWidth for breakpoint checks, not container.clientWidth — the SVG container overflows on mobile
|
|
- Design tokens in index.css :root — use var(--accent), var(--border-light), var(--text-tertiary), etc.
|
|
- Use the d3-viz skill for all D3 rendering stories
|
|
- yScale domain reversal automatically flows through all timeline elements (guides, dots, labels, role positions, simulation forces) — no per-element changes needed
|
|
- Always use CSS custom properties (var(--border), var(--surface), var(--text-tertiary), etc.) for colours in D3 — never hardcode hex values
|
|
- SVG shadows: use <filter> with <feDropShadow> in <defs>, apply to <g> groups via .attr('filter', 'url(#filter-id)'), clear with .attr('filter', null)
|
|
- Role nodes are already pill-shaped rects (ROLE_WIDTH=104, ROLE_HEIGHT=32, ROLE_RX=16) with orgColor badge styling — check before re-implementing
|
|
|
|
## 2026-02-16 - US-001
|
|
- Reversed yScale domain from [minYear, maxYear] to [maxYear, minYear] so 2025 appears at top
|
|
- Updated buildScreenReaderDescription() to mention reverse-chronological order
|
|
- Files changed: src/components/CareerConstellation.tsx
|
|
- **Learnings for future iterations:**
|
|
- The yScale is the single source of truth for vertical positioning — reversing its domain is a one-line change that cascades to all D3 elements using it
|
|
- Year guide lines, year dots, year labels, role initial positions, and simulation forceY all reference yScale — no individual element updates needed
|
|
- buildScreenReaderDescription() is defined at module level (line ~63), not inside the component
|
|
---
|
|
|
|
## 2026-02-16 - US-002
|
|
- Removed fixed DESKTOP_HEIGHT/TABLET_HEIGHT/MOBILE_HEIGHT constants, replaced with MIN_HEIGHT (400) and MOBILE_FALLBACK_HEIGHT (360)
|
|
- Added containerHeight prop to CareerConstellation — DashboardLayout measures .chronology-stream via ResizeObserver and passes height
|
|
- getHeight() now takes containerHeight param: on mobile uses fallback, on desktop uses measured height with MIN_HEIGHT floor
|
|
- Used window.innerWidth for mobile breakpoint detection (container.clientWidth is unreliable due to SVG overflow)
|
|
- Files changed: src/components/CareerConstellation.tsx, src/components/DashboardLayout.tsx, src/index.css
|
|
- **Learnings for future iterations:**
|
|
- The CareerConstellation container div overflows on mobile — its clientWidth reports desktop-sized values even at 375px viewport. Always use window.innerWidth for responsive breakpoint checks in this component.
|
|
- ResizeObserver on .chronology-stream fires when cards expand/collapse, triggering height update in the graph — this is the key mechanism for dynamic sync.
|
|
- The dimensions useEffect depends on [containerHeight] so it re-runs when the measured height changes, updating the D3 scales.
|
|
- CSS grid column ratio was adjusted to minmax(0,1.15fr) minmax(0,1.5fr) to give the graph more horizontal space.
|
|
---
|
|
|
|
## 2026-02-16 - US-003
|
|
- Removed radial gradient background, replaced with clean var(--surface) fill
|
|
- Added 1px solid var(--border-light) border to the container div
|
|
- Refined timeline vertical rule to 1px stroke using var(--border) colour
|
|
- Replaced year dots (circles) with horizontal tick marks (6-8px lines extending right from timeline)
|
|
- Updated year labels fill to var(--text-tertiary)
|
|
- Made horizontal guide lines subtle: stroke-opacity 0.25, stroke-dasharray '3 4', using var(--border-light)
|
|
- Removed the entire SVG legend group (replacement HTML legend comes in US-008)
|
|
- Files changed: src/components/CareerConstellation.tsx
|
|
- **Learnings for future iterations:**
|
|
- All colours should use CSS custom property values (var(--border), var(--surface), etc.) rather than hardcoded hex values — the design system tokens are defined in index.css :root
|
|
- The legend was ~47 lines of D3 code; removing it is a significant net reduction. The HTML replacement in US-008 will be simpler React JSX
|
|
- Year ticks as horizontal lines are positioned with x1=timelineX, x2=timelineX+width — they extend right from the timeline axis, not centred on it
|
|
- The container div border + borderRadius + overflow:hidden creates a clean framed look for the SVG without needing an SVG-level border
|
|
---
|
|
|
|
## 2026-02-16 - US-004
|
|
- Added SVG filter defs for drop shadows: shadow-sm-filter (subtle, for hover/connected) and shadow-md-filter (stronger, for active/pinned)
|
|
- Updated applyGraphHighlight to apply shadow filters on role node `<g>` elements during highlight states
|
|
- Resting state: no filter; connected role: shadow-sm; active/pinned role: shadow-md with stroke-opacity 1 and stroke-width 1.5
|
|
- Note: most of US-004 (pill shape, orgColor styling, connector lines, focus rings, collision detection) was already implemented in prior iterations
|
|
- Files changed: src/components/CareerConstellation.tsx, Ralph/prd.json
|
|
- **Learnings for future iterations:**
|
|
- SVG drop shadows use `<filter>` with `<feDropShadow>` — apply to the parent `<g>` group, not the individual shape, for proper rendering
|
|
- Filter bounds need generous overflow (x/y -20%, width/height 140%+) to avoid clipping the shadow
|
|
- When clearing a filter, use `.attr('filter', null)` — not empty string
|
|
- The role node pill rendering (rect with rx/ry, orgColor fill at 0.12, border at 0.4) was built incrementally across US-003 and US-004 — check existing code before implementing to avoid duplication
|
|
- Skill nodes use SKILL_RADIUS_DEFAULT (7) for resting state and SKILL_RADIUS_ACTIVE (11) for highlighted state — controlled via applyGraphHighlight, not CSS transitions (SVG `r` doesn't transition via CSS)
|
|
- Skill labels default to opacity 0 and are shown/hidden via D3 transitions in applyGraphHighlight — the old updateSkillLabelVisibility collision-based approach was removed
|
|
- Link lines use var(--border-light) at opacity 0.08 for resting state — highlighted links use the skill's domain colour from domainColorMap with strength-proportional opacity
|
|
---
|
|
|
|
## 2026-02-16 - US-005
|
|
- Replaced SKILL_RADIUS (14) with SKILL_RADIUS_DEFAULT (7) and SKILL_RADIUS_ACTIVE (11)
|
|
- Skill nodes now default to small (r=7), low opacity (0.2), no stroke, hidden labels (opacity 0)
|
|
- On hover/pin: connected skills grow to r=11, fill-opacity 0.85, labels fade in; unconnected nodes dim to opacity 0.06
|
|
- Link lines default to var(--border-light) at opacity 0.08; highlighted links use domain colour with strength-proportional opacity (0.35-0.65)
|
|
- Removed updateSkillLabelVisibility function — label visibility now fully controlled by applyGraphHighlight
|
|
- D3 transitions (180ms) used for skill radius and opacity changes, respecting prefers-reduced-motion
|
|
- Updated collision force and boundary clamping to use SKILL_RADIUS_ACTIVE
|
|
- Skill labels styled: font-geist-mono, 10px, var(--text-secondary)
|
|
- Files changed: src/components/CareerConstellation.tsx, Ralph/prd.json
|
|
- **Learnings for future iterations:**
|
|
- SVG `r` attribute cannot be animated via CSS transitions — must use D3 `.transition().duration()` for radius changes
|
|
- The applyGraphHighlight function is the single source of truth for all visual states (resting, highlighted, dimmed) — keep all styling logic there, not split between initial rendering and highlight
|
|
- D3 transition on a selection that already has a pending transition interrupts it — this is fine for hover interactions where the latest state wins
|
|
- domainColorMap hex values are needed for D3 attrs (can't use CSS custom properties for computed color values in stroke/fill of highlighted links)
|
|
---
|