Files
portfolio/Ralph/prd.json
T

166 lines
19 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"project": "Portfolio — Career Constellation Refinement",
"branchName": "ralph/constellation-refinement",
"description": "Visual and interaction refinements for the career constellation: improved skill visibility, viewport-proportional scaling, hover-based interaction, mobile accordion, 4 new timeline entries (roles + education), and org-colour-matched work experience cards.",
"userStories": [
{
"id": "US-001",
"title": "Add Duty Pharmacy Manager and Pre-Reg Pharmacist roles + fix Pharmacy Manager colour",
"description": "As a visitor, I want to see the Duty Pharmacy Manager (2016-2017) and Pre-Registration Pharmacist (2015-2016) roles in the constellation, and the existing Pharmacy Manager should use Tesco red instead of teal.",
"acceptanceCriteria": [
"Add role node to constellation.ts: id 'duty-pharmacy-manager-2016', label 'Duty Pharmacy Manager', shortLabel 'Duty Pharm Mgr', organisation 'Tesco PLC', startYear 2016, endYear 2017, orgColor '#E53935'",
"Add role-skill links for duty-pharmacy-manager-2016: medicines-optimisation (0.8), data-analysis (0.5), excel (0.6), change-management (0.5), stakeholder-engagement (0.4)",
"Add consultation entry to consultations.ts for Duty Pharmacy Manager: org 'Tesco PLC', duration 'Aug 2016 Oct 2017', location 'Great Yarmouth, Norfolk', achievements: service development leadership (NMS/asthma referrals), national clinical innovation (quality payments solution), clinical foundation building",
"Add role node to constellation.ts: id 'pre-reg-pharmacist-2015', label 'Pre-Registration Pharmacist', shortLabel 'Pre-Reg', organisation 'Paydens Pharmacy', startYear 2015, endYear 2016, orgColor '#66BB6A'",
"Add role-skill links for pre-reg-pharmacist-2015: medicines-optimisation (0.7), change-management (0.4), stakeholder-engagement (0.3)",
"Add consultation entry to consultations.ts for Pre-Reg Pharmacist: org 'Paydens Pharmacy', duration 'Jul 2015 Jul 2016', location 'Tunbridge Wells & Ashford, Kent', achievements: PGD clinical service expansion (NRT, EHC, chlamydia), NMS audit improvement (under 10% to 50-60%), palliative care screening, operational learning",
"Update existing pharmacy-manager-2017 orgColor from '#00897B' to '#E53935' in both constellation.ts and consultations.ts",
"Screen reader description (buildScreenReaderDescription in CareerConstellation.tsx) automatically includes new roles since it iterates constellationNodes",
"Typecheck passes (npm run typecheck)"
],
"priority": 1,
"passes": true,
"notes": "Follow existing patterns exactly. Current roles: interim-head-2025, deputy-head-2024, high-cost-drugs-2022, pharmacy-manager-2017. New roles slot chronologically below pharmacy-manager. In constellation.ts: add nodes to constellationNodes array (ConstellationNode with type: 'role'), add roleSkillMappings entries, add links to constellationLinks. In consultations.ts: add Consultation entries with id matching constellation node id. Consultation shape: { id, date, organization, orgColor, role, duration, isCurrent: false, history, examination: string[], plan: string[], codedEntries: CodedEntry[] }. Follow same narrative style as existing entries. For the Pharmacy Manager colour fix: search for '#00897B' in both files and replace with '#E53935'. buildScreenReaderDescription() is at module level in CareerConstellation.tsx (~line 63) and iterates constellationNodes automatically. Use the d3-viz skill."
},
{
"id": "US-002",
"title": "Add UEA MPharm and Highworth A-Levels education entries",
"description": "As a visitor, I want to see the University of East Anglia MPharm degree (2011-2015) and Highworth Grammar School A-Levels (2009-2011) on the timeline as education entries.",
"acceptanceCriteria": [
"Add node to constellation.ts: id 'uea-mpharm-2011', type 'role', label 'MPharm (Hons) 2:1', shortLabel 'MPharm', organisation 'University of East Anglia', startYear 2011, endYear 2015, orgColor '#7B2D8E'",
"Add role-skill links for uea-mpharm-2011: medicines-optimisation (0.5), data-analysis (0.3)",
"Add consultation entry to consultations.ts for MPharm: org 'University of East Anglia', duration '2011 2015', location 'Norwich', achievements: independent research project on drug delivery and cocrystals (75.1%, Distinction), 4th year OSCE 80%, President of UEA Pharmacy Society",
"Add node to constellation.ts: id 'highworth-alevels-2009', type 'role', label 'A-Levels: Maths A*, Chem B', shortLabel 'A-Levels', organisation 'Highworth Grammar School', startYear 2009, endYear 2011, orgColor '#9C27B0'",
"Add single link for highworth-alevels-2009: data-analysis (0.2)",
"Add consultation entry to consultations.ts for A-Levels: org 'Highworth Grammar School', duration '2009 2011', location 'Ashford, Kent', results: Mathematics A*, Chemistry B, Politics C",
"Education entries appear at the bottom of the timeline (2009-2015 range) below all professional roles",
"Typecheck passes (npm run typecheck)"
],
"priority": 2,
"passes": true,
"notes": "Education entries use type 'role' — the constellation treats them identically to work roles for layout. They have deliberately few skill connections (2 for UEA, 1 for Highworth) to keep the lower timeline clean. The yScale computes domain from min/max startYear of role nodes, so adding 2009 entries automatically extends the range. Follow exact same data patterns as US-001. Education consultations may use simpler codedEntries and adapted examination content (results rather than workplace achievements). The consultations array should be ordered reverse-chronologically (newest first) — add education entries at the end. Use the d3-viz skill."
},
{
"id": "US-003",
"title": "Increase default skill visibility and reduce constellation column width",
"description": "As a visitor, I want skill nodes more visible by default so I can see the full constellation without interacting, and more horizontal space for work experience content.",
"acceptanceCriteria": [
"In applyGraphHighlight resting state: skill circle fill-opacity changed from 0.2 to 0.35",
"In applyGraphHighlight active state: skill circle fill-opacity changed from 0.85 to 0.9",
"Unconnected node dimming changed from opacity 0.06 to opacity 0.15",
"Skill labels default opacity changed from 0 to 0.5 (partially visible at rest), fully visible at 1.0 when highlighted",
"Default link stroke-opacity increased from 0.08 to 0.15",
"Change .pathway-columns desktop grid in index.css from 'minmax(0, 1.15fr) minmax(0, 1.5fr)' to 'minmax(0, 1.85fr) minmax(0, 1fr)' — first column is work experience chronology, second is constellation graph",
"Constellation graph adapts to narrower container without clipping or overflow",
"Typecheck passes (npm run typecheck)",
"Verify in browser: skills recognisable at a glance without hovering; work experience column visibly wider"
],
"priority": 3,
"passes": true,
"notes": "Two independent changes in one story. Skill visibility: applyGraphHighlight in CareerConstellation.tsx has two branches — the 'no activeNodeId' resting state and the activeNodeId highlighted state. In the resting branch, change skill fill-opacity from 0.2 to 0.35, skill label opacity from 0 to 0.5, link stroke-opacity from 0.08 to 0.15. In the highlighted branch, change active skill fill-opacity from 0.85 to 0.9, dimmed node opacity from 0.06 to 0.15. Column width: in index.css @media (min-width: 1024px) for .pathway-columns, change grid-template-columns. The containerHeight/ResizeObserver system adapts the graph SVG automatically. Column order: first child is .chronology-stream (work experience), second is .pathway-graph-sticky (constellation). Use the d3-viz skill."
},
{
"id": "US-004",
"title": "Viewport-proportional scaling for large screens",
"description": "As a visitor on a 1440p+ display, I want constellation elements to scale proportionally so they aren't tiny relative to the screen.",
"acceptanceCriteria": [
"Compute scale factor: scaleFactor = Math.max(1, Math.min(1.6, viewportWidth / 1440)) — 1.0x at 1440px, up to 1.6x at 2560px+",
"Apply scale factor to SKILL_RADIUS_DEFAULT (7 → ~11), SKILL_RADIUS_ACTIVE (11 → ~18), ROLE_WIDTH (104 → ~166), ROLE_HEIGHT (32 → ~51)",
"Skill label font-size: base 11px minimum (up from 10px), scales proportionally up to ~18px at max scale",
"Role label font-size: base 12px minimum (up from 11px), scales proportionally up to ~19px at max scale",
"Year label font-size: base 11px minimum (up from 10px), scales proportionally",
"Padding, gaps, and force simulation parameters (charge, link distance, collision radius) scale proportionally with the factor",
"Mobile breakpoint (< 640px) is unaffected — scaling only applies at >= 1024px viewport width",
"Scale factor computed once per resize via the existing dimensions useEffect, not per render tick",
"Typecheck passes (npm run typecheck)",
"Verify in browser at 1440px and 2560px widths: elements clearly legible and well-proportioned"
],
"priority": 4,
"passes": true,
"notes": "Compute scaleFactor in the dimensions useEffect that already handles containerHeight and resize. Use window.innerWidth (not container.clientWidth — known overflow issue on mobile). Create scaled constants: const scaledRoleWidth = Math.round(ROLE_WIDTH * scaleFactor), etc. Apply throughout D3 rendering where base constants are used. Force simulation parameters also scale: charge strength, link distance, collision radius. The isMobile check (window.innerWidth < 640) bypasses scaling entirely, using MOBILE_ constants as-is. The existing MOBILE_ROLE_WIDTH (80), MOBILE_SKILL_RADIUS_DEFAULT (6), MOBILE_SKILL_RADIUS_ACTIVE (9) remain unchanged. Store scaleFactor in a ref or state so D3 code can access it. Use the d3-viz skill."
},
{
"id": "US-005",
"title": "Hover-to-highlight interaction on desktop",
"description": "As a desktop visitor, I want hovering a role to highlight connected skills and hovering away to reset, without needing to click to toggle.",
"acceptanceCriteria": [
"On desktop (fine pointer via supportsCoarsePointer === false): hovering a role node highlights connected skills, shows labels, colorises links — same visual as current click behaviour",
"Moving mouse away from a role resets to default state (all nodes at baseline opacity per US-003 values)",
"Remove click-to-pin toggle behaviour on desktop — clicking a role node should NOT pin the highlight",
"Hovering a skill node still highlights that skill and its connected roles",
"pinnedNodeId state only set for touch/keyboard interactions, not desktop hover",
"Keyboard navigation still works: Tab focuses a node and highlights it, Enter/Space triggers detail action",
"On touch devices (coarse pointer): existing tap-to-pin behaviour preserved unchanged",
"No 'stuck' highlight states — hover on/off cycles cleanly",
"Typecheck passes (npm run typecheck)",
"Verify in browser: hover on/off roles cycles highlight cleanly with no stuck states"
],
"priority": 5,
"passes": true,
"notes": "The interaction handlers are in the D3 useEffect where mouseenter/mouseleave/click are attached to node groups. supportsCoarsePointer is a module-level window.matchMedia('(pointer: coarse)').matches check. For fine pointer (desktop): mouseenter calls applyGraphHighlight(nodeId) + fires onNodeHover(nodeId), mouseleave calls applyGraphHighlight(null) + fires onNodeHover(null). Remove the click handler's pin/unpin toggle for fine pointer. For coarse pointer (touch): keep existing tap-to-pin unchanged. The pinnedNodeId useState remains but only gets set on coarse pointer or keyboard interactions. The callbacksRef pattern prevents stale closures — use it for onNodeHover. The onNodeHover callback propagates to DashboardLayout for bidirectional highlighting (graph→timeline). Use the d3-viz skill."
},
{
"id": "US-006",
"title": "Mobile accordion expansion for role details",
"description": "As a mobile visitor, I want tapping a role to expand an accordion below the constellation showing condensed role details, rather than opening a side panel.",
"acceptanceCriteria": [
"On touch devices (coarse pointer): first tap on a role highlights connected skills AND expands an accordion panel below the constellation SVG",
"Accordion shows condensed details: role title, organisation, date range, and top 3 key achievements from consultation.examination array",
"Accordion includes a 'Show more' button that reveals the full examination and plan arrays",
"Tapping a different role switches highlight and accordion content (auto-collapses 'Show more' back to summary)",
"Tapping the same role again or tapping empty space collapses the accordion and resets highlights",
"Accordion uses height-only animation, 200ms ease-out (matching existing tile expansion pattern)",
"No slide-out sidebar panel on mobile for role details",
"Tapping a skill node highlights it but does not open the accordion",
"Accordion hidden entirely on desktop (fine pointer)",
"Typecheck passes (npm run typecheck)",
"Verify in browser at mobile viewport: tap role → accordion expands with details, tap again → collapses"
],
"priority": 6,
"passes": true,
"notes": "New JSX inside CareerConstellation container div, below the SVG and HTML legend. Import consultations from '@/data/consultations'. When pinnedNodeId matches a consultation.id on a coarse pointer device, render the accordion. Use a local showMore state for the expand toggle. Consultation data provides: role (title), organization, duration, examination (string[] achievements), plan (string[] outcomes). Show first 3 examination items collapsed, all when expanded. Animation: use max-height + overflow hidden with CSS transition (200ms ease-out), or measure content height dynamically. Add click handler on SVG background rect to clear pinnedNodeId for 'tap elsewhere to close'. Hide accordion entirely when !supportsCoarsePointer. Style with the same font and spacing as WorkExperienceSubsection for consistency. Use the d3-viz skill."
},
{
"id": "US-007",
"title": "Colour-match work experience cards to constellation node colours",
"description": "As a visitor, I want work experience cards to use matching employer colours from their constellation nodes, creating a visual link between the card list and the graph.",
"acceptanceCriteria": [
"Dot indicator on each work experience card uses consultation.orgColor instead of hardcoded '#0D6E6E'",
"Expanded card left border uses consultation.orgColor instead of var(--accent)",
"Bullet point dots in expanded detail use consultation.orgColor at 0.5 opacity instead of var(--accent)",
"Coded entry tags use consultation.orgColor for text and a lightened variant (rgba at 0.08 opacity) for background",
"'View full record' link uses consultation.orgColor instead of var(--accent)",
"Highlight background from graph uses rgba(r,g,b,0.03) of consultation.orgColor instead of hardcoded rgba(10,128,128,0.03)",
"Hover/expanded border uses consultation.orgColor variant instead of var(--accent-border)",
"CardHeader dot for 'WORK EXPERIENCE' section title remains teal (section accent, not per-card)",
"All colour changes maintain readable text contrast",
"Typecheck passes (npm run typecheck)",
"Verify in browser: NHS roles show blue-tinted cards, Tesco roles red-tinted, Paydens green, education purple"
],
"priority": 7,
"passes": true,
"notes": "All changes in WorkExperienceSubsection.tsx (~299 lines). consultation.orgColor already exists on each consultation object but is not currently used for card styling. Create a helper function hexToRgba(hex: string, opacity: number): string that converts hex to rgba — needed for tinted backgrounds and borders. Replace hardcoded values: '#0D6E6E' for dot (line ~82), 'rgba(10,128,128,0.03)' for highlight bg, 'var(--accent-border)' for border, 'var(--accent)' for links/text. Each RoleItem already receives its consultation — use consultation.orgColor. For coded entry tags: text in orgColor, bg in hexToRgba(orgColor, 0.08), border in hexToRgba(orgColor, 0.2). Also update LastConsultationSubsection in DashboardLayout.tsx if it has hardcoded teal colours. The WORK EXPERIENCE CardHeader dot stays teal. Use the d3-viz skill."
},
{
"id": "US-008",
"title": "Re-tune force simulation for 8 timeline entries in narrower column",
"description": "As a developer, I need the force simulation to produce a clean layout with 8 entries (6 roles + 2 education) spanning 2009-2025 in the narrower ~35% column.",
"acceptanceCriteria": [
"y-scale range accommodates 8 entries spanning 2009-2025 without excessive cramping",
"Timeline year labels show the full range from 2009 to 2025",
"Role/education nodes don't overlap each other on the timeline",
"Skill nodes distribute cleanly in available horizontal space to the right of role pills",
"Charge, collision, and link forces adjusted for additional nodes in narrower space",
"Links don't create an unreadable tangle — connections remain traceable",
"Education nodes at bottom (2009-2015) have fewer connections so lower portion stays clean",
"Graph works at mobile viewport widths (375px, 430px) with 8 entries",
"Typecheck passes (npm run typecheck)",
"Verify in browser at both desktop and mobile: all 8 entries visible, no overlaps, clean layout"
],
"priority": 8,
"passes": true,
"notes": "The yScale domain is computed from min/max startYear — adding 2009 entries extends it automatically. Key challenge: vertical spacing for 8 entries over 16 years. The 2015-2017 range has 3 entries close together (Pre-Reg 2015, Duty Pharm Mgr 2016, Pharmacy Manager 2017). May need increased topPadding/bottomPadding. Current force simulation params from prior overhaul: role forceY ~0.98, charge -120 (roles)/-55 (skills), link distance 72, collision ~52-65px for roles. With 8 entries in ~35% column (vs previous ~57%): consider reducing ROLE_WIDTH slightly for the narrower space, adjusting charge to allow tighter packing, ensuring skill nodes don't overflow horizontally. The viewport-proportional scaling from US-004 must also work with 8 entries. Mobile params (MOBILE_ROLE_WIDTH 80, charge -80/-35, link distance 48) need separate tuning for 8 entries in ~260px width. Test at 375px, 1440px, and 2560px. Use the d3-viz skill."
}
]
}