merge codex/kpi (prefer codex/kpi on conflicts)
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
# Agent Output to Ralph Files Integration Plan
|
||||
|
||||
## Overview
|
||||
|
||||
This plan documents how to decode the agent output files from `AgentOutput/` and place the design recommendations into the relevant Ralph reference files in `Tasks/07-the-clinical-record/Ralph/refs/`.
|
||||
|
||||
---
|
||||
|
||||
## Agent to Ralph File Mapping
|
||||
|
||||
| Agent File | Component | Target Ralph File | Status |
|
||||
|------------|-----------|-------------------|--------|
|
||||
| `agent-a19ebc6.jsonl` | LoginScreen | `ref-transition-login.md` | ✅ Has existing design guidance |
|
||||
| `agent-a1dd546.jsonl` | PatientBanner | `ref-banner-sidebar.md` | Needs design guidance appended |
|
||||
| `agent-aa811d1.jsonl` | PatientBanner (detailed) | `ref-banner-sidebar.md` | Use for design decisions |
|
||||
| `agent-aff857c.jsonl` | ClinicalSidebar | `ref-banner-sidebar.md` | Add to same file |
|
||||
| `agent-a8a7a9b.jsonl` | PMRInterface + Breadcrumb | `ref-banner-sidebar.md` | Add layout guidance |
|
||||
| `agent-a2bbd7e.jsonl` | InvestigationsView + DocumentsView | `ref-investigations-documents.md` | Needs design guidance |
|
||||
| `agent-acompact-a5ee2e.jsonl` | Investigations + Documents (compact) | `ref-investigations-documents.md` | Alternative source |
|
||||
| `agent-a32017e.jsonl` | SummaryView + Clinical Alert | `ref-summary-alert.md` | Needs alert interaction details |
|
||||
| `agent-a403d5f.jsonl` | ReferralsView | `ref-referrals.md` | Needs form styling guidance |
|
||||
| `agent-aab6185.jsonl` | MedicationsView | `ref-medications.md` | Needs table styling guidance |
|
||||
| `agent-aeb60ce.jsonl` | ProblemsView | `ref-problems.md` | Needs traffic light guidance |
|
||||
| `agent-afebbe0.jsonl` | ConsultationsView | `ref-consultations.md` | Needs H/E/P structure guidance |
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance Structure to Add
|
||||
|
||||
Each Ralph file should have a "## Design Guidance (from /frontend-design)" section appended with:
|
||||
|
||||
1. **Aesthetic Direction** - The design philosophy/tone
|
||||
2. **Key Design Decisions** - Specific implementation choices
|
||||
3. **Implementation Patterns** - Code snippets and patterns
|
||||
|
||||
---
|
||||
|
||||
## File-by-File Plan
|
||||
|
||||
### 1. ref-transition-login.md
|
||||
|
||||
**Already contains design guidance** - verify it matches agent output:
|
||||
|
||||
From agent-a19ebc6:
|
||||
- ✅ Aesthetic: Institutional Utilitarian
|
||||
- ✅ Shadow: `0 1px 2px rgba(0,0,0,0.03)`
|
||||
- ✅ Border: `1px solid #E5E7EB`
|
||||
- ✅ Font: Geist Mono for credentials
|
||||
- ✅ Accessibility: role="status", aria-label, prefers-reduced-motion
|
||||
|
||||
**Action**: Already complete - no changes needed.
|
||||
|
||||
---
|
||||
|
||||
### 2. ref-banner-sidebar.md
|
||||
|
||||
**Current content**: Patient Banner specs, Sidebar navigation, URL routing
|
||||
|
||||
**Needs to add from agents**:
|
||||
|
||||
From agent-aa811d1 (PatientBanner detailed):
|
||||
- Animation refinements: Framer Motion AnimatePresence for condensed/full state swap
|
||||
- Badge styling: True pill shape (rounded-full)
|
||||
- NHS Number tooltip: Custom styled tooltip with Framer Motion
|
||||
- Mobile overflow menu: AnimatePresence for enter/exit
|
||||
- Action buttons: Loading state with spinner
|
||||
- Layout measurements: Exact padding, margin values
|
||||
|
||||
From agent-aff857c (ClinicalSidebar):
|
||||
- Keyboard navigation details: Alt+1-7, arrow keys, / for search
|
||||
- Search input integration
|
||||
- Tooltip positioning for tablet mode
|
||||
- Focus management patterns
|
||||
- Accessibility: aria-current for active item
|
||||
|
||||
From agent-a8a7a9b (PMRInterface + Breadcrumb):
|
||||
- Layout timing: Patient banner (200ms), sidebar (250ms, 50ms delay), content (300ms, 100ms delay)
|
||||
- View switching: INSTANT - no crossfade
|
||||
- Breadcrumb deepening: When item expanded, show item name
|
||||
- Breadcrumb styling: Inter 400, 13px, gray-400, chevron separators
|
||||
|
||||
**Action**: Append comprehensive design guidance section.
|
||||
|
||||
---
|
||||
|
||||
### 3. ref-investigations-documents.md
|
||||
|
||||
**Current content**: Table layouts, status badges, expanded views
|
||||
|
||||
**Needs to add from agent-a2bbd7e/acompact**:
|
||||
|
||||
Shared Pattern: ExpandableRow
|
||||
- Component structure for row expansion
|
||||
- Height animation only (200ms ease-out) - no opacity fade
|
||||
- Chevron rotation (180 degrees)
|
||||
- Only one expanded at a time
|
||||
|
||||
InvestigationsView specific:
|
||||
- Status badges: Complete (green), Ongoing (amber), Live (pulsing green)
|
||||
- Pulsing animation for Live status (CSS keyframes)
|
||||
- Tree-indented structure with box-drawing characters
|
||||
- View Results button pattern for external links
|
||||
|
||||
DocumentsView specific:
|
||||
- Lucide icons mapping: FileText, Award, GraduationCap, FlaskConical
|
||||
- Document type indicators
|
||||
- Same tree-indented structure
|
||||
|
||||
**Action**: Append design guidance with expandable row patterns.
|
||||
|
||||
---
|
||||
|
||||
### 4. ref-summary-alert.md
|
||||
|
||||
**Current content**: Summary cards layout, Clinical Alert behavior
|
||||
|
||||
**Needs to add from agent-a32017e**:
|
||||
|
||||
Clinical Alert (signature interaction):
|
||||
- Spring animation (not ease-out) for slide down
|
||||
- Acknowledge button flow:
|
||||
1. Warning icon cross-fades to green checkmark (200ms)
|
||||
2. Holds 200ms
|
||||
3. Alert height collapses (200ms ease-out)
|
||||
- Session-only state (resets on refresh)
|
||||
|
||||
Summary Cards:
|
||||
- Two-column key-value layout exact specs
|
||||
- Card header styling: Inter 600, 14px, uppercase, #F9FAFB bg
|
||||
- Traffic light dots: 8px circles with text labels (WCAG)
|
||||
- "View Full List" link patterns
|
||||
|
||||
Second Alert (Consultations view):
|
||||
- Trigger: First navigation to Consultations
|
||||
- Same dismiss pattern as main alert
|
||||
- Positioning beneath patient banner
|
||||
|
||||
**Action**: Append alert animation details and card styling specs.
|
||||
|
||||
---
|
||||
|
||||
### 5. ref-referrals.md
|
||||
|
||||
**Current content**: Form layout, priority toggle, direct contact
|
||||
|
||||
**Needs to add from agent-a403d5f**:
|
||||
|
||||
Referral Form:
|
||||
- Priority radio button styling: Urgent (red), Routine (blue), Two-Week Wait (amber)
|
||||
- Tooltip content: "All enquiries are welcome..." and "NHS cancer referral pathway..."
|
||||
- Input focus state: NHS blue border + box-shadow `0 0 0 3px rgba(0,94,184,0.15)`
|
||||
- Form validation patterns
|
||||
|
||||
Submit Button States:
|
||||
- Default: NHS blue (#005EB8)
|
||||
- Loading: Spinner icon
|
||||
- Success: Checkmark + reference number generation (REF-YYYY-MMDD-NNN)
|
||||
- Success message styling: 24-48 hours response time
|
||||
|
||||
**Action**: Append form interaction and state management patterns.
|
||||
|
||||
---
|
||||
|
||||
### 6. ref-medications.md
|
||||
|
||||
**Current content**: Table layout, medication categories, prescribing history
|
||||
|
||||
**Needs to add from agent-aab6185**:
|
||||
|
||||
Table Styling:
|
||||
- Semantic HTML: table, thead, th (scope=col), tbody, tr, td
|
||||
- Headers: Inter 600, 13px, uppercase, #F9FAFB bg
|
||||
- Row height: 40px exactly
|
||||
- Alternating backgrounds: #FFFFFF / #F9FAFB
|
||||
- Hover: #EFF6FF (no transform, no lift)
|
||||
- Status dots: 6px green circles + 'Active' text
|
||||
|
||||
Category Tabs:
|
||||
- Active tab: White bg + NHS blue bottom border
|
||||
- Inactive tab: Transparent bg
|
||||
- Tab switching animation (instant)
|
||||
|
||||
Sortable Columns:
|
||||
- Click header to sort
|
||||
- Arrow indicator (up/down) in active column
|
||||
- Default: category grouping
|
||||
|
||||
Prescribing History:
|
||||
- Expand animation: Height only, 200ms ease-out
|
||||
- Geist Mono 12px for history entries
|
||||
- Year markers bold, descriptions regular
|
||||
|
||||
**Action**: Append table semantics and sorting patterns.
|
||||
|
||||
---
|
||||
|
||||
### 7. ref-problems.md
|
||||
|
||||
**Current content**: Active/resolved problems tables, traffic lights
|
||||
|
||||
**Needs to add from agent-aeb60ce**:
|
||||
|
||||
Traffic Light Status:
|
||||
- 8px circles: Green (resolved), Amber (in-progress)
|
||||
- MUST ALWAYS be paired with text labels (WCAG requirement)
|
||||
- Never dots alone
|
||||
- Code column: Geist Mono (e.g., [EFF001], [MGT001])
|
||||
|
||||
Expandable Rows:
|
||||
- Full narrative: Problem, approach, tools, quantified outcome
|
||||
- 'Linked consultations' buttons that navigate to Consultations view
|
||||
- Row hover: #EFF6FF
|
||||
|
||||
Table Structure:
|
||||
- Active Problems: Status | Code | Problem | Since
|
||||
- Resolved Problems: Status | Code | Problem | Resolved | Outcome
|
||||
|
||||
**Action**: Append traffic light accessibility requirements.
|
||||
|
||||
---
|
||||
|
||||
### 8. ref-consultations.md
|
||||
|
||||
**Current content**: Journal layout, H/E/P structure, coded entries
|
||||
|
||||
**Needs to add from agent-afebbe0**:
|
||||
|
||||
Collapsed Entry:
|
||||
- Date in Geist Mono 13px gray-500
|
||||
- Organization in Inter 400 13px NHS blue
|
||||
- Role in Inter 600 15px gray-900
|
||||
- 'Key:' prefix in Inter 500 gray-500
|
||||
- Status dot: Green (current), Gray (historical)
|
||||
- 3px left border color-coded: NHS blue (#005EB8) or Tesco teal (#00897B)
|
||||
|
||||
Expanded Entry:
|
||||
- Duration line
|
||||
- HISTORY / EXAMINATION / PLAN section headers
|
||||
- Headers: Inter 600, 12px, uppercase, letter-spacing 0.05em, gray-400
|
||||
- Plan items as bullet lists
|
||||
- CODED ENTRIES section at bottom: Geist Mono 12px, gray-500, bracket codes
|
||||
|
||||
Animation:
|
||||
- Height-only expand animation, 200ms ease-out
|
||||
- NO opacity fade on content
|
||||
- Only one expanded at a time
|
||||
- Chevron rotates 180 degrees when expanded
|
||||
|
||||
Second Alert:
|
||||
- Appears on first navigation to Consultations view
|
||||
- Same Acknowledge pattern as main alert
|
||||
- Warning icon → Checkmark → Collapse
|
||||
|
||||
**Action**: Append H/E/P styling and animation details.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. **Start with**: ref-banner-sidebar.md (most complex, multiple agents)
|
||||
2. **Then**: ref-investigations-documents.md (shared patterns)
|
||||
3. **Then**: ref-summary-alert.md (signature interaction)
|
||||
4. **Then**: ref-consultations.md (core content view)
|
||||
5. **Then**: ref-medications.md, ref-problems.md, ref-referrals.md (specialized views)
|
||||
|
||||
---
|
||||
|
||||
## Token-Efficient Approach
|
||||
|
||||
For each file:
|
||||
|
||||
1. Read the agent output file (focused read, only design guidance section)
|
||||
2. Extract: Aesthetic direction, Key design decisions, Implementation patterns
|
||||
3. Read the target Ralph file
|
||||
4. Append the design guidance in the standard format
|
||||
5. Write back
|
||||
6. Clear context, move to next
|
||||
|
||||
This keeps context window usage minimal by processing one file at a time.
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance Section Template
|
||||
|
||||
```markdown
|
||||
|
||||
---
|
||||
|
||||
## Design Guidance (from /frontend-design)
|
||||
|
||||
> Pre-baked design direction. Do NOT invoke `/frontend-design` at runtime — this section contains the output.
|
||||
|
||||
### Aesthetic Direction: [Name]
|
||||
|
||||
[Description of the design philosophy]
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **[Decision name]**: [Description with specific values]
|
||||
2. **[Decision name]**: [Description with specific values]
|
||||
...
|
||||
|
||||
### Implementation Patterns
|
||||
|
||||
```tsx
|
||||
// Key code patterns and snippets
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Completion Checklist
|
||||
|
||||
- [ ] ref-transition-login.md - Verify existing content (no changes needed)
|
||||
- [ ] ref-banner-sidebar.md - Add PatientBanner, ClinicalSidebar, PMRInterface guidance
|
||||
- [ ] ref-investigations-documents.md - Add InvestigationsView + DocumentsView guidance
|
||||
- [ ] ref-summary-alert.md - Add SummaryView + Clinical Alert guidance
|
||||
- [ ] ref-referrals.md - Add ReferralsView guidance
|
||||
- [ ] ref-medications.md - Add MedicationsView guidance
|
||||
- [ ] ref-problems.md - Add ProblemsView guidance
|
||||
- [ ] ref-consultations.md - Add ConsultationsView guidance
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- All design guidance should be appending (not replacing) existing content
|
||||
- Existing content in Ralph files has the "what" and "why"
|
||||
- Design guidance adds the "how" with specific implementation details
|
||||
- Keep code snippets minimal but complete enough to guide implementation
|
||||
- Reference specific values: colors, fonts, sizes, timing functions
|
||||
@@ -0,0 +1,982 @@
|
||||
# Component Architecture Design: Adding Depth
|
||||
|
||||
> Design document — Feb 2026
|
||||
> Follows requirements in `Ralph/depth-requirements.md`
|
||||
> Based on audit of current codebase architecture
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture Overview
|
||||
|
||||
### Current Component Tree
|
||||
```
|
||||
App.tsx (Phase: boot → ecg → login → pmr)
|
||||
└── AccessibilityProvider
|
||||
├── BootSequence (locked)
|
||||
├── ECGAnimation (locked)
|
||||
├── LoginScreen
|
||||
└── DashboardLayout
|
||||
├── TopBar
|
||||
├── Sidebar
|
||||
├── Main Content Grid
|
||||
│ ├── PatientSummaryTile
|
||||
│ ├── LatestResultsTile + CoreSkillsTile
|
||||
│ ├── LastConsultationTile
|
||||
│ ├── CareerActivityTile
|
||||
│ ├── EducationTile
|
||||
│ └── ProjectsTile
|
||||
└── CommandPalette
|
||||
```
|
||||
|
||||
### Proposed Component Tree
|
||||
```
|
||||
App.tsx (Phase: boot → ecg → login → pmr)
|
||||
└── AccessibilityProvider
|
||||
├── BootSequence (locked)
|
||||
├── ECGAnimation (locked)
|
||||
├── LoginScreen ← MODIFIED (visual refresh, a.recruiter, connection status)
|
||||
└── DetailPanelProvider ← NEW (context for panel state)
|
||||
└── DashboardLayout ← MODIFIED (sub-nav, new tile order)
|
||||
├── TopBar ← MODIFIED (session shows a.recruiter)
|
||||
├── SubNav ← NEW (section jump bar)
|
||||
├── Sidebar (unchanged)
|
||||
├── Main Content Grid ← REORDERED
|
||||
│ ├── PatientSummaryTile ← MODIFIED (CV_v4.md profile)
|
||||
│ ├── LatestResultsTile ← MODIFIED (bigger numbers, panel trigger)
|
||||
│ ├── ProjectsTile ← MOVED UP (card grid with thumbnails)
|
||||
│ ├── CoreSkillsTile ← MOVED, FULL WIDTH (categorised groups)
|
||||
│ ├── LastConsultationTile ← MODIFIED (panel trigger)
|
||||
│ ├── CareerActivityTile ← MODIFIED (constellation embedded)
|
||||
│ │ └── CareerConstellation ← NEW (D3.js force graph)
|
||||
│ └── EducationTile ← MODIFIED (richer content, panel trigger)
|
||||
├── DetailPanel ← NEW (slide-in from right)
|
||||
└── CommandPalette ← UPDATED (new panel actions)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. New Components
|
||||
|
||||
### 2.1 DetailPanel (`src/components/DetailPanel.tsx`)
|
||||
|
||||
The primary mechanism for depth. A slide-in panel from the right edge.
|
||||
|
||||
**Props Interface:**
|
||||
```typescript
|
||||
interface DetailPanelProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
width: 'narrow' | 'wide' // narrow: 400px, wide: 60vw
|
||||
title: string // Header text
|
||||
dotColor: CardHeaderProps['dotColor'] // Matches tile dot color
|
||||
children: React.ReactNode // Content rendered inside
|
||||
}
|
||||
```
|
||||
|
||||
**Behaviour:**
|
||||
- Renders a full-screen backdrop (`rgba(26,43,42,0.15)` + `backdrop-filter: blur(4px)`) and a panel div
|
||||
- Panel slides in from `translateX(100%)` → `translateX(0)` over 250ms ease-out
|
||||
- Backdrop fades in over 150ms
|
||||
- Close: click backdrop, press Escape, or click X button
|
||||
- Focus trap: first focusable element receives focus on open; Tab cycles within panel; focus returns to trigger element on close
|
||||
- `aria-modal="true"`, `role="dialog"`, `aria-labelledby` pointing to title
|
||||
- `prefers-reduced-motion`: skip slide animation, instant appear
|
||||
|
||||
**Layout:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Blurred backdrop (click to close) │
|
||||
│ ┌────────────────────────────┐│
|
||||
│ │ ── X close button ──────── ││
|
||||
│ │ ││
|
||||
│ │ [dot] SECTION TITLE ││
|
||||
│ │ ││
|
||||
│ │ {children} ││
|
||||
│ │ ││
|
||||
│ │ (scrollable) ││
|
||||
│ │ ││
|
||||
│ └────────────────────────────┘│
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**CSS Custom Properties to add (index.css):**
|
||||
```css
|
||||
--panel-narrow: 400px;
|
||||
--panel-wide: 60vw;
|
||||
--backdrop-blur: 4px;
|
||||
--backdrop-bg: rgba(26,43,42,0.15);
|
||||
```
|
||||
|
||||
**Responsive:**
|
||||
- On mobile (< 768px), both `narrow` and `wide` become full-width (100vw)
|
||||
|
||||
---
|
||||
|
||||
### 2.2 DetailPanelContext (`src/contexts/DetailPanelContext.tsx`)
|
||||
|
||||
Manages what content is displayed in the detail panel. Any tile can trigger it.
|
||||
|
||||
**Interface:**
|
||||
```typescript
|
||||
// Union type for all possible detail panel content
|
||||
type DetailPanelContent =
|
||||
| { type: 'kpi'; kpi: KPI }
|
||||
| { type: 'skill'; skill: SkillMedication }
|
||||
| { type: 'skills-all'; category?: SkillCategory }
|
||||
| { type: 'consultation'; consultation: Consultation }
|
||||
| { type: 'project'; investigation: Investigation }
|
||||
| { type: 'education'; document: Document }
|
||||
| { type: 'career-role'; consultation: Consultation } // from constellation click
|
||||
|
||||
interface DetailPanelContextValue {
|
||||
content: DetailPanelContent | null
|
||||
openPanel: (content: DetailPanelContent) => void
|
||||
closePanel: () => void
|
||||
isOpen: boolean
|
||||
}
|
||||
```
|
||||
|
||||
**Width mapping** (deterministic from content type):
|
||||
```typescript
|
||||
const widthMap: Record<DetailPanelContent['type'], 'narrow' | 'wide'> = {
|
||||
'kpi': 'narrow',
|
||||
'skill': 'narrow',
|
||||
'skills-all': 'narrow',
|
||||
'consultation': 'wide',
|
||||
'project': 'wide',
|
||||
'education': 'narrow',
|
||||
'career-role': 'wide',
|
||||
}
|
||||
```
|
||||
|
||||
**Title mapping** (from content type + data):
|
||||
```typescript
|
||||
function getPanelTitle(content: DetailPanelContent): string {
|
||||
switch (content.type) {
|
||||
case 'kpi': return content.kpi.label
|
||||
case 'skill': return content.skill.name
|
||||
case 'skills-all': return 'All Medications'
|
||||
case 'consultation': return content.consultation.role
|
||||
case 'project': return content.investigation.name
|
||||
case 'education': return content.document.title
|
||||
case 'career-role': return content.consultation.role
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Integration:** Wraps `DashboardLayout` in `App.tsx`. The `DetailPanel` component reads from this context and renders the appropriate content.
|
||||
|
||||
---
|
||||
|
||||
### 2.3 SubNav (`src/components/SubNav.tsx`)
|
||||
|
||||
Section jump bar positioned between TopBar and content.
|
||||
|
||||
**Props Interface:**
|
||||
```typescript
|
||||
interface SubNavProps {
|
||||
activeSection: string
|
||||
onSectionClick: (sectionId: string) => void
|
||||
}
|
||||
|
||||
interface NavSection {
|
||||
id: string
|
||||
label: string
|
||||
tileId: string // data-tile-id to scroll to
|
||||
}
|
||||
```
|
||||
|
||||
**Sections:**
|
||||
```typescript
|
||||
const sections: NavSection[] = [
|
||||
{ id: 'overview', label: 'Overview', tileId: 'patient-summary' },
|
||||
{ id: 'skills', label: 'Skills', tileId: 'core-skills' },
|
||||
{ id: 'experience', label: 'Experience', tileId: 'career-activity' },
|
||||
{ id: 'projects', label: 'Projects', tileId: 'projects' },
|
||||
{ id: 'education', label: 'Education', tileId: 'education' },
|
||||
]
|
||||
```
|
||||
|
||||
**Behaviour:**
|
||||
- Fixed/sticky position below TopBar (top: 48px)
|
||||
- Click → smooth-scroll to `[data-tile-id="${tileId}"]`
|
||||
- Active section determined by `useActiveSection` hook (IntersectionObserver on tile elements)
|
||||
- Active tab: teal underline (2px), text colour shifts to `var(--accent)`
|
||||
- Inactive tabs: `var(--text-secondary)`
|
||||
|
||||
**Style:**
|
||||
- Height: 36px
|
||||
- Background: `var(--surface)` with bottom border `var(--border-light)`
|
||||
- Tabs: 13px, font-weight 500, horizontal gap 24px, centred text
|
||||
- Teal underline on active (2px, slides with 200ms transition)
|
||||
- z-index: 99 (below TopBar at 100, above content)
|
||||
|
||||
**Existing hook to extend:** `src/hooks/useActiveSection.ts` — currently exists but may need updating to observe the correct tile IDs.
|
||||
|
||||
**CSS to add (index.css):**
|
||||
```css
|
||||
--subnav-height: 36px;
|
||||
```
|
||||
|
||||
**Layout impact:** `marginTop` on the flex container below TopBar changes from `var(--topbar-height)` to `calc(var(--topbar-height) + var(--subnav-height))`.
|
||||
|
||||
---
|
||||
|
||||
### 2.4 CareerConstellation (`src/components/CareerConstellation.tsx`)
|
||||
|
||||
D3.js force-directed network graph embedded in the CareerActivityTile.
|
||||
|
||||
**Props Interface:**
|
||||
```typescript
|
||||
interface CareerConstellationProps {
|
||||
onRoleClick: (consultationId: string) => void
|
||||
onSkillClick: (skillId: string) => void
|
||||
}
|
||||
```
|
||||
|
||||
**Data Model:**
|
||||
```typescript
|
||||
interface ConstellationNode {
|
||||
id: string
|
||||
type: 'role' | 'skill'
|
||||
label: string
|
||||
// Role-specific:
|
||||
organization?: string
|
||||
startYear?: number
|
||||
endYear?: number
|
||||
orgColor?: string
|
||||
// Skill-specific:
|
||||
domain?: 'clinical' | 'technical' | 'leadership'
|
||||
}
|
||||
|
||||
interface ConstellationLink {
|
||||
source: string // node id
|
||||
target: string // node id
|
||||
strength: number // 0-1, how strongly connected
|
||||
}
|
||||
```
|
||||
|
||||
**Node Data (from consultations + skills + new mapping data):**
|
||||
|
||||
Role nodes (6, positioned chronologically):
|
||||
1. Pre-Reg Pharmacist, Paydens (2015-2016)
|
||||
2. Duty Pharmacy Manager, Tesco (2016-2017)
|
||||
3. Pharmacy Manager, Tesco (2017-2022)
|
||||
4. High-Cost Drugs Pharmacist, NHS (2022-2024)
|
||||
5. Deputy Head, NHS (2024-present)
|
||||
6. Interim Head, NHS (2025)
|
||||
|
||||
Skill nodes (drawn from skills.ts + new expanded skills data):
|
||||
- Technical: Python, SQL, Power BI, JavaScript/TypeScript, Data Analysis, Algorithm Design, Excel
|
||||
- Clinical: Medicines Optimisation, Clinical Pathways, Controlled Drugs, NICE TAs, Patient Safety
|
||||
- Leadership: Budget Management, Team Development, Stakeholder Engagement, Change Management
|
||||
|
||||
Links connect skills to the roles where they were used/developed.
|
||||
|
||||
**D3 Integration Pattern:**
|
||||
- Use a `useRef<SVGSVGElement>` to get the SVG container
|
||||
- D3 operates on the SVG imperatively via `useEffect`
|
||||
- React handles the wrapper container, D3 handles the graph rendering
|
||||
- No React state for individual node positions (performance)
|
||||
- Tooltip/hover state managed via D3 event handlers dispatching to React state for the detail panel
|
||||
|
||||
**Force Simulation Configuration:**
|
||||
```typescript
|
||||
d3.forceSimulation(nodes)
|
||||
.force('charge', d3.forceManyBody().strength(-200))
|
||||
.force('link', d3.forceLink(links).distance(80).strength(d => d.strength))
|
||||
.force('x', d3.forceX(d => xScale(d.startYear)).strength(0.3)) // chronological
|
||||
.force('y', d3.forceY(height / 2).strength(0.1))
|
||||
.force('collision', d3.forceCollide(30))
|
||||
```
|
||||
|
||||
The `forceX` with a time scale ensures roles flow left-to-right chronologically. Skill nodes cluster around their associated roles.
|
||||
|
||||
**Visual Design:**
|
||||
- Role nodes: 24px radius circles, filled with `orgColor`, white text label
|
||||
- Skill nodes: 10px radius, colour-coded by domain (clinical=`var(--success)`, technical=`var(--accent)`, leadership=`var(--amber)`)
|
||||
- Links: thin lines (1px), `var(--border)` colour, opacity 0.3
|
||||
- Hover role: connected skill nodes scale up, links brighten to `var(--accent)`, non-connected nodes fade to 0.15 opacity
|
||||
- Hover skill: all connected role nodes highlight, link paths illuminate
|
||||
- Click: dispatches to `onRoleClick` / `onSkillClick` → opens detail panel
|
||||
|
||||
**Container:**
|
||||
- Full width of the CareerActivityTile
|
||||
- Height: 400px (desktop), 300px (tablet), 250px (mobile)
|
||||
- Background: subtle radial gradient from `var(--bg-dashboard)` centre to `var(--surface)` edge
|
||||
- SVG fills the container with viewBox for responsiveness
|
||||
|
||||
**New dependency:** `d3` (specifically `d3-force`, `d3-selection`, `d3-scale`, `d3-transition`)
|
||||
|
||||
**New data file:** `src/data/constellation.ts` — defines the role-skill mapping:
|
||||
```typescript
|
||||
export interface RoleSkillMapping {
|
||||
roleId: string // matches consultation.id
|
||||
skillIds: string[] // matches skill IDs
|
||||
}
|
||||
|
||||
export const roleSkillMappings: RoleSkillMapping[] = [
|
||||
{
|
||||
roleId: 'duty-pharmacist-2016',
|
||||
skillIds: ['patient-care', 'medicines-optimisation', 'team-development'],
|
||||
},
|
||||
{
|
||||
roleId: 'pharmacy-manager-2017',
|
||||
skillIds: ['patient-care', 'medicines-optimisation', 'team-development', 'data-analysis', 'excel', 'change-management', 'budget-management'],
|
||||
},
|
||||
// ... etc for all roles
|
||||
]
|
||||
```
|
||||
|
||||
**Accessibility:**
|
||||
- `role="img"` on SVG with `aria-label="Career constellation showing roles and skills across career timeline"`
|
||||
- Screen-reader-only text description of the graph structure
|
||||
- Keyboard navigation: Tab through role nodes, Enter to open detail panel
|
||||
- `prefers-reduced-motion`: disable force simulation animation, render static layout
|
||||
|
||||
---
|
||||
|
||||
## 3. Modified Components
|
||||
|
||||
### 3.1 DashboardLayout — Modifications
|
||||
|
||||
**Changes:**
|
||||
1. **Import and render SubNav** between TopBar and content flex container
|
||||
2. **Reorder tiles:** PatientSummary → LatestResults + Projects → CoreSkills → LastConsultation → CareerActivity → Education
|
||||
3. **Wrap in DetailPanelProvider** (or this wraps from App.tsx)
|
||||
4. **Render DetailPanel** alongside CommandPalette
|
||||
5. **Adjust marginTop** to account for SubNav height
|
||||
|
||||
**Updated grid in DashboardLayout:**
|
||||
```tsx
|
||||
<div className="dashboard-grid">
|
||||
<PatientSummaryTile /> {/* full width */}
|
||||
<LatestResultsTile /> {/* half width (left) */}
|
||||
<ProjectsTile /> {/* half width (right) — MOVED UP */}
|
||||
<CoreSkillsTile /> {/* full width — MOVED, was half */}
|
||||
<LastConsultationTile /> {/* full width */}
|
||||
<CareerActivityTile /> {/* full width — now includes constellation */}
|
||||
<EducationTile /> {/* full width */}
|
||||
</div>
|
||||
```
|
||||
|
||||
**SubNav integration:**
|
||||
```tsx
|
||||
<motion.div initial="hidden" animate="visible" variants={topbarVariants}>
|
||||
<TopBar onSearchClick={handleSearchClick} />
|
||||
</motion.div>
|
||||
<SubNav activeSection={activeSection} onSectionClick={handleSectionClick} />
|
||||
{/* ... rest of layout with adjusted marginTop */}
|
||||
```
|
||||
|
||||
**New CSS variable reference:**
|
||||
- Content area `marginTop`: `calc(var(--topbar-height) + var(--subnav-height))`
|
||||
- Content area `height`: `calc(100vh - var(--topbar-height) - var(--subnav-height))`
|
||||
|
||||
---
|
||||
|
||||
### 3.2 TopBar — Modifications
|
||||
|
||||
**Changes:**
|
||||
1. Session user name: `Dr. A.CHARLWOOD` → `A.RECRUITER`
|
||||
2. No structural changes otherwise
|
||||
|
||||
**Specific change:**
|
||||
```tsx
|
||||
// Line ~172: Change display name
|
||||
<span ...>A.RECRUITER</span>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 LoginScreen — Modifications
|
||||
|
||||
**Changes:**
|
||||
1. **Username:** `A.CHARLWOOD` → `A.RECRUITER`
|
||||
2. **Visual refresh:** Teal accents replacing NHS blue (`#005EB8` → `var(--accent)` / `#0D6E6E`)
|
||||
3. **Connection status indicator:** New state machine below the login button
|
||||
4. **Post-login loading state:** Brief "System loading..." before dashboard materialises
|
||||
5. **Background colour:** `#1E293B` → consider matching `var(--bg-dashboard)` or a darker variant
|
||||
|
||||
**New state additions:**
|
||||
```typescript
|
||||
type ConnectionState = 'connecting' | 'connected'
|
||||
|
||||
const [connectionState, setConnectionState] = useState<ConnectionState>('connecting')
|
||||
const [isLoading, setIsLoading] = useState(false) // post-click loading state
|
||||
```
|
||||
|
||||
**Connection status flow:**
|
||||
1. On mount + 400ms: start typing animation (existing)
|
||||
2. After ~2000ms: `connectionState` transitions to `'connected'`
|
||||
3. Login button is disabled until BOTH `typingComplete` AND `connectionState === 'connected'`
|
||||
4. On login click: `isLoading = true`, show "System loading..." state for ~600ms, then `onComplete()`
|
||||
|
||||
**Connection indicator JSX (below login button, above footer):**
|
||||
```tsx
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginTop: '12px' }}>
|
||||
<div style={{
|
||||
width: '6px',
|
||||
height: '6px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: connectionState === 'connected' ? 'var(--success)' : 'var(--alert)',
|
||||
transition: 'background-color 300ms ease-out',
|
||||
}} />
|
||||
<span style={{ fontSize: '10px', fontFamily: 'var(--font-geist-mono)', color: 'var(--text-tertiary)' }}>
|
||||
{connectionState === 'connected' ? 'Secure connection established' : 'Awaiting secure connection...'}
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Loading state (replaces card content after click):**
|
||||
```tsx
|
||||
{isLoading && (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<div className="loading-spinner" /> {/* CSS animated spinner */}
|
||||
<span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>
|
||||
Loading clinical records...
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
**Colour changes throughout LoginScreen:**
|
||||
- `#005EB8` → `#0D6E6E` (accent colour for shield icon bg, active field border, cursor, button)
|
||||
- `#004D9F` → `#0A8080` (button hover)
|
||||
- `#004494` → `#085858` (button pressed)
|
||||
- Background: `#1E293B` → keep as-is or lighten slightly to `#1A2B2A` (matches `--text-primary`)
|
||||
|
||||
---
|
||||
|
||||
### 3.4 CoreSkillsTile — Modifications (now full-width, categorised)
|
||||
|
||||
**Changes:**
|
||||
1. **Full width** (add `full` prop to Card)
|
||||
2. **Categorised display** with 3 groups: Technical, Healthcare Domain, Strategic & Leadership
|
||||
3. **Show top 3-5 per category** on the dashboard
|
||||
4. **"View all" button** triggers detail panel with full list
|
||||
5. **Individual skill click** → detail panel for that skill
|
||||
|
||||
**New internal structure:**
|
||||
```tsx
|
||||
<Card full tileId="core-skills">
|
||||
<CardHeader dotColor="amber" title="REPEAT MEDICATIONS" rightText="Active prescriptions" />
|
||||
|
||||
{/* Category tabs or grouped sections */}
|
||||
{categories.map(category => (
|
||||
<div key={category.id}>
|
||||
<CategoryHeader label={category.label} count={category.skills.length} />
|
||||
{category.skills.slice(0, 4).map(skill => (
|
||||
<SkillItem
|
||||
key={skill.id}
|
||||
skill={skill}
|
||||
onClick={() => openPanel({ type: 'skill', skill })}
|
||||
/>
|
||||
))}
|
||||
{category.skills.length > 4 && (
|
||||
<ViewMoreButton
|
||||
count={category.skills.length - 4}
|
||||
onClick={() => openPanel({ type: 'skills-all', category: category.id })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</Card>
|
||||
```
|
||||
|
||||
**CategoryHeader sub-component (inline):**
|
||||
- Thin divider line with category label
|
||||
- Styled like sidebar section dividers: 10px, uppercase, tertiary, with extending line
|
||||
|
||||
---
|
||||
|
||||
### 3.5 LatestResultsTile — Modifications
|
||||
|
||||
**Changes:**
|
||||
1. **Bigger headline numbers** — increase value font size from 22px to 28-32px
|
||||
2. **Remove flip animation** — replace with click → detail panel
|
||||
3. **Each KPI card is clickable** → `openPanel({ type: 'kpi', kpi })`
|
||||
4. **Visual enhancement:** stronger contrast, bolder presentation
|
||||
|
||||
**KPI card redesign (no more flip):**
|
||||
```tsx
|
||||
<button
|
||||
onClick={() => openPanel({ type: 'kpi', kpi })}
|
||||
className="text-left w-full"
|
||||
style={{
|
||||
padding: '16px',
|
||||
background: 'var(--surface)',
|
||||
border: '1px solid var(--border-light)',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 150ms, box-shadow 150ms',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '28px', fontWeight: 700, color: colorMap[kpi.colorVariant] }}>
|
||||
{kpi.value}
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', fontWeight: 500, color: 'var(--text-primary)', marginTop: '4px' }}>
|
||||
{kpi.label}
|
||||
</div>
|
||||
<div style={{ fontSize: '10px', fontFamily: 'var(--font-geist-mono)', color: 'var(--text-tertiary)', marginTop: '2px' }}>
|
||||
{kpi.sub}
|
||||
</div>
|
||||
</button>
|
||||
```
|
||||
|
||||
**CSS cleanup:** Remove `.metric-card`, `.metric-card-inner`, `.metric-card-front`, `.metric-card-back` classes from `index.css` (no longer needed once flip is removed).
|
||||
|
||||
---
|
||||
|
||||
### 3.6 ProjectsTile — Modifications (now half-width, card grid)
|
||||
|
||||
**Changes:**
|
||||
1. **Half width** (remove `full` prop) — positioned in right column alongside LatestResults
|
||||
2. **Card grid layout** with thumbnails, title, status, tech tags
|
||||
3. **Click → detail panel (wide)** for full project info
|
||||
4. **Compact display** to fit in half-width tile
|
||||
|
||||
**New layout:**
|
||||
```tsx
|
||||
<Card tileId="projects">
|
||||
<CardHeader dotColor="amber" title="ACTIVE PROJECTS" rightText="Investigations" />
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
{investigations.map(inv => (
|
||||
<ProjectCard
|
||||
key={inv.id}
|
||||
investigation={inv}
|
||||
onClick={() => openPanel({ type: 'project', investigation: inv })}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
```
|
||||
|
||||
**ProjectCard sub-component:**
|
||||
- Compact row: status dot + name + year (right-aligned)
|
||||
- Tech stack as small inline tags
|
||||
- Hover: border colour shift, shadow deepens
|
||||
- Click: opens wide detail panel
|
||||
|
||||
---
|
||||
|
||||
### 3.7 CareerActivityTile — Modifications
|
||||
|
||||
**Changes:**
|
||||
1. **Embed CareerConstellation** component within the tile
|
||||
2. **Timeline items click → detail panel** (instead of in-place accordion)
|
||||
3. **Extended timeline** back to school (2009)
|
||||
4. **Hover preview** on timeline items (slight expand with preview text)
|
||||
|
||||
**New structure:**
|
||||
```tsx
|
||||
<Card full tileId="career-activity">
|
||||
<CardHeader dotColor="teal" title="CAREER ACTIVITY" rightText="Full timeline" />
|
||||
|
||||
{/* Career Constellation D3 graph */}
|
||||
<CareerConstellation
|
||||
onRoleClick={(id) => {
|
||||
const consultation = consultations.find(c => c.id === id)
|
||||
if (consultation) openPanel({ type: 'career-role', consultation })
|
||||
}}
|
||||
onSkillClick={(id) => {
|
||||
const skill = allSkills.find(s => s.id === id)
|
||||
if (skill) openPanel({ type: 'skill', skill })
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Existing timeline below */}
|
||||
<div className="activity-grid" style={{ marginTop: '24px' }}>
|
||||
{/* ... timeline items, now with click → panel instead of accordion */}
|
||||
</div>
|
||||
</Card>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.8 EducationTile — Modifications
|
||||
|
||||
**Changes:**
|
||||
1. **Richer inline content** — show research project score, OSCE score, A-level grades
|
||||
2. **Click → detail panel (narrow)** for full education detail
|
||||
3. Each education entry is a clickable row
|
||||
|
||||
---
|
||||
|
||||
### 3.9 LastConsultationTile — Modifications
|
||||
|
||||
**Changes:**
|
||||
1. **Click → detail panel (wide)** for full role details
|
||||
2. Add a "View full record" link/button at the bottom
|
||||
|
||||
---
|
||||
|
||||
### 3.10 PatientSummaryTile — Modifications
|
||||
|
||||
**Changes:**
|
||||
1. **Content:** Replace current personalStatement with the exact profile text from CV_v4.md
|
||||
2. **Structured presentation:** Consider pulling highlight stats into a visual strip
|
||||
|
||||
The profile.ts data is already the CV_v4.md text, so this may just be a presentation change.
|
||||
|
||||
---
|
||||
|
||||
## 4. Type System Extensions
|
||||
|
||||
### 4.1 New types (`src/types/pmr.ts` additions)
|
||||
|
||||
```typescript
|
||||
// Skill categories for grouped display
|
||||
export type SkillCategory = 'Technical' | 'Domain' | 'Leadership'
|
||||
|
||||
// Extended KPI with story content for detail panel
|
||||
export interface KPIStory {
|
||||
context: string // What this number covers
|
||||
role: string // Your role / what you did
|
||||
outcomes: string[] // Key decisions or results
|
||||
period?: string // Time period
|
||||
}
|
||||
|
||||
// Extended KPI type (augment existing)
|
||||
export interface KPI {
|
||||
id: string
|
||||
value: string
|
||||
label: string
|
||||
sub: string
|
||||
colorVariant: 'green' | 'amber' | 'teal'
|
||||
explanation: string
|
||||
story?: KPIStory // NEW: rich detail for panel
|
||||
}
|
||||
|
||||
// Constellation-specific types
|
||||
export interface ConstellationNode {
|
||||
id: string
|
||||
type: 'role' | 'skill'
|
||||
label: string
|
||||
shortLabel?: string // abbreviated for small nodes
|
||||
organization?: string
|
||||
startYear?: number
|
||||
endYear?: number | null
|
||||
orgColor?: string
|
||||
domain?: 'clinical' | 'technical' | 'leadership'
|
||||
}
|
||||
|
||||
export interface ConstellationLink {
|
||||
source: string
|
||||
target: string
|
||||
strength: number
|
||||
}
|
||||
|
||||
// Detail panel content union
|
||||
export type DetailPanelContent =
|
||||
| { type: 'kpi'; kpi: KPI }
|
||||
| { type: 'skill'; skill: SkillMedication }
|
||||
| { type: 'skills-all'; category?: SkillCategory }
|
||||
| { type: 'consultation'; consultation: Consultation }
|
||||
| { type: 'project'; investigation: Investigation }
|
||||
| { type: 'education'; document: Document }
|
||||
| { type: 'career-role'; consultation: Consultation }
|
||||
|
||||
// Education extras (for detail panel)
|
||||
export interface EducationExtra {
|
||||
documentId: string
|
||||
extracurriculars?: string[]
|
||||
researchDescription?: string
|
||||
programmeDetail?: string
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Data Extensions
|
||||
|
||||
### 5.1 Extended Skills (`src/data/skills.ts`)
|
||||
|
||||
Expand from 5 → ~20 skills across 3 categories. Source: CV_v4.md Core Competencies.
|
||||
|
||||
```typescript
|
||||
// Technical (8 skills)
|
||||
'data-analysis', 'python', 'sql', 'power-bi', 'javascript-typescript',
|
||||
'excel', 'algorithm-design', 'data-pipelines'
|
||||
|
||||
// Healthcare Domain (6 skills)
|
||||
'medicines-optimisation', 'population-health', 'nice-ta',
|
||||
'health-economics', 'clinical-pathways', 'controlled-drugs'
|
||||
|
||||
// Strategic & Leadership (7 skills)
|
||||
'budget-management', 'stakeholder-engagement', 'pharma-negotiation',
|
||||
'team-development', 'change-management', 'financial-modelling', 'executive-comms'
|
||||
```
|
||||
|
||||
Each retains the medication metaphor: frequency, startYear, yearsOfExperience, proficiency, status.
|
||||
|
||||
### 5.2 KPI Stories (`src/data/kpis.ts`)
|
||||
|
||||
Add `story` field to each existing KPI:
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: 'budget',
|
||||
value: '£220M',
|
||||
// ... existing fields ...
|
||||
story: {
|
||||
context: 'Total prescribing budget for NHS Norfolk & Waveney ICB, covering primary care prescriptions for a population of 1.2 million across the integrated care system.',
|
||||
role: 'Managed with sophisticated forecasting models, identifying cost pressures and enabling proactive financial planning. Full analytical accountability to ICB board.',
|
||||
outcomes: [
|
||||
'Sophisticated forecasting models identifying cost pressures',
|
||||
'Proactive financial planning enabled across the system',
|
||||
'Interactive dashboard tracking expenditure in real-time',
|
||||
],
|
||||
period: 'Jul 2024 — Present',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Constellation Mapping (`src/data/constellation.ts`)
|
||||
|
||||
New file mapping roles to skills for the D3 graph. Defines which skills connect to which roles.
|
||||
|
||||
### 5.4 Education Extras (`src/data/educationExtras.ts`)
|
||||
|
||||
New file with expanded detail for the education detail panel:
|
||||
|
||||
```typescript
|
||||
export const educationExtras: EducationExtra[] = [
|
||||
{
|
||||
documentId: 'doc-mpharm',
|
||||
extracurriculars: [
|
||||
'President of UEA Pharmacy Society',
|
||||
'Secretary & Vice-President of UEA Ultimate Frisbee',
|
||||
'Publicity Officer for UEA Alzheimer\'s Society',
|
||||
],
|
||||
researchDescription: 'Final year research project investigating cocrystal formation for improved drug delivery properties.',
|
||||
},
|
||||
{
|
||||
documentId: 'doc-mary-seacole',
|
||||
programmeDetail: 'Formal NHS leadership qualification providing theoretical grounding in healthcare leadership approaches, change management, and system-level thinking.',
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Detail Panel Content Renderers
|
||||
|
||||
The `DetailPanel` component delegates rendering to content-specific sub-components based on `content.type`:
|
||||
|
||||
### 6.1 KPIDetail (`src/components/detail/KPIDetail.tsx`)
|
||||
- Headline number (large, coloured)
|
||||
- Context paragraph
|
||||
- "Your role" paragraph
|
||||
- Outcome bullets
|
||||
- Period badge
|
||||
|
||||
### 6.2 SkillDetail (`src/components/detail/SkillDetail.tsx`)
|
||||
- Skill name + frequency + status badge
|
||||
- Proficiency bar
|
||||
- Years of experience
|
||||
- Prescribing history timeline (reuse existing pattern from CoreSkillsTile)
|
||||
- "Used in" section: list of roles that used this skill (from constellation mapping)
|
||||
|
||||
### 6.3 SkillsAllDetail (`src/components/detail/SkillsAllDetail.tsx`)
|
||||
- Full categorised list of all skills
|
||||
- Grouped by Technical / Healthcare Domain / Strategic & Leadership
|
||||
- Each skill clickable to switch panel to individual skill detail
|
||||
|
||||
### 6.4 ConsultationDetail (`src/components/detail/ConsultationDetail.tsx`)
|
||||
- Role title + organisation + dates
|
||||
- History paragraph (from `consultation.history`)
|
||||
- Achievement bullets (from `consultation.examination`)
|
||||
- Plan/outcomes (from `consultation.plan`)
|
||||
- Coded entries badges (from `consultation.codedEntries`)
|
||||
- Technical environment list
|
||||
|
||||
### 6.5 ProjectDetail (`src/components/detail/ProjectDetail.tsx`)
|
||||
- Project name + year + status
|
||||
- Methodology description
|
||||
- Tech stack tags
|
||||
- Results bullets
|
||||
- External link button (if available)
|
||||
|
||||
### 6.6 EducationDetail (`src/components/detail/EducationDetail.tsx`)
|
||||
- Title + institution + dates + classification
|
||||
- Research project description (if MPharm)
|
||||
- Extracurricular activities
|
||||
- Programme detail (if Mary Seacole)
|
||||
- Notes
|
||||
|
||||
---
|
||||
|
||||
## 7. Hook Modifications
|
||||
|
||||
### 7.1 `useActiveSection` (existing, to update)
|
||||
|
||||
Currently may observe legacy view IDs. Update to observe the new tile `data-tile-id` attributes and map them to SubNav section IDs:
|
||||
|
||||
```typescript
|
||||
const sectionTileMap: Record<string, string> = {
|
||||
'patient-summary': 'overview',
|
||||
'core-skills': 'skills',
|
||||
'career-activity': 'experience',
|
||||
'projects': 'projects',
|
||||
'education': 'education',
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 `useFocusTrap` (new hook, `src/hooks/useFocusTrap.ts`)
|
||||
|
||||
For the DetailPanel. Traps Tab key focus within the panel when open.
|
||||
|
||||
```typescript
|
||||
export function useFocusTrap(containerRef: RefObject<HTMLElement>, isActive: boolean): void
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. New Dependency
|
||||
|
||||
```bash
|
||||
npm install d3 @types/d3
|
||||
```
|
||||
|
||||
Only `d3-force`, `d3-selection`, `d3-scale`, `d3-transition` are needed. Can import selectively:
|
||||
```typescript
|
||||
import { forceSimulation, forceManyBody, forceLink, forceX, forceY, forceCollide } from 'd3-force'
|
||||
import { select } from 'd3-selection'
|
||||
import { scaleLinear } from 'd3-scale'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. CSS Additions (`src/index.css`)
|
||||
|
||||
```css
|
||||
/* Sub-nav bar */
|
||||
--subnav-height: 36px;
|
||||
|
||||
/* Detail panel */
|
||||
--panel-narrow: 400px;
|
||||
--panel-wide: 60vw;
|
||||
--backdrop-blur: 4px;
|
||||
--backdrop-bg: rgba(26,43,42,0.15);
|
||||
|
||||
/* Detail panel slide animation */
|
||||
@keyframes panel-slide-in {
|
||||
from { transform: translateX(100%); }
|
||||
to { transform: translateX(0); }
|
||||
}
|
||||
|
||||
@keyframes panel-slide-out {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
@keyframes backdrop-fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
@keyframes panel-slide-in { from { transform: none; } to { transform: none; } }
|
||||
@keyframes panel-slide-out { from { transform: none; } to { transform: none; } }
|
||||
@keyframes backdrop-fade-in { from { opacity: 1; } to { opacity: 1; } }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Implementation Phases
|
||||
|
||||
### Phase 1: Core Infrastructure
|
||||
1. `DetailPanelContext` + `DetailPanel` component
|
||||
2. `SubNav` component + `useActiveSection` update
|
||||
3. `DashboardLayout` restructure (new tile order, SubNav, DetailPanel)
|
||||
4. `useFocusTrap` hook
|
||||
5. CSS additions (panel animations, sub-nav height)
|
||||
|
||||
### Phase 2: Tile Depth (iterative, per tile)
|
||||
6. `LatestResultsTile` — remove flip, bigger numbers, panel trigger
|
||||
7. `CoreSkillsTile` — full width, categorised, expanded data, "view all"
|
||||
8. `ProjectsTile` — half width, card grid, panel trigger
|
||||
9. `LastConsultationTile` — panel trigger
|
||||
10. `CareerActivityTile` — timeline items → panel, hover preview
|
||||
11. `EducationTile` — richer content, panel trigger
|
||||
12. `PatientSummaryTile` — structured presentation
|
||||
|
||||
### Phase 3: Detail Panel Content
|
||||
13. `KPIDetail` renderer + KPI stories data
|
||||
14. `ConsultationDetail` renderer
|
||||
15. `ProjectDetail` renderer
|
||||
16. `SkillDetail` + `SkillsAllDetail` renderers
|
||||
17. `EducationDetail` renderer + extras data
|
||||
18. Update CommandPalette actions to use detail panel
|
||||
|
||||
### Phase 4: Career Constellation
|
||||
19. Install d3, create `constellation.ts` data mapping
|
||||
20. Build `CareerConstellation` component (D3 force graph)
|
||||
21. Integrate into `CareerActivityTile`
|
||||
22. Hover/click interactions → detail panel
|
||||
23. Accessibility (keyboard nav, screen reader, reduced-motion)
|
||||
|
||||
### Phase 5: Login Refresh
|
||||
24. Visual restyle (teal accents, fonts, shadows)
|
||||
25. Username change to `a.recruiter`
|
||||
26. Connection status indicator (red → green dot)
|
||||
27. Post-login loading state
|
||||
28. TopBar session name update
|
||||
|
||||
### Phase 6: Polish
|
||||
29. Responsive testing (mobile: full-width panels, collapsed sub-nav)
|
||||
30. `prefers-reduced-motion` audit across all new components
|
||||
31. Command palette updates for new content/actions
|
||||
32. Search index update for expanded skills data
|
||||
|
||||
---
|
||||
|
||||
## 11. File Inventory
|
||||
|
||||
### New Files (13)
|
||||
```
|
||||
src/contexts/DetailPanelContext.tsx
|
||||
src/components/DetailPanel.tsx
|
||||
src/components/SubNav.tsx
|
||||
src/components/CareerConstellation.tsx
|
||||
src/components/detail/KPIDetail.tsx
|
||||
src/components/detail/SkillDetail.tsx
|
||||
src/components/detail/SkillsAllDetail.tsx
|
||||
src/components/detail/ConsultationDetail.tsx
|
||||
src/components/detail/ProjectDetail.tsx
|
||||
src/components/detail/EducationDetail.tsx
|
||||
src/data/constellation.ts
|
||||
src/data/educationExtras.ts
|
||||
src/hooks/useFocusTrap.ts
|
||||
```
|
||||
|
||||
### Modified Files (14)
|
||||
```
|
||||
src/App.tsx — wrap DashboardLayout with DetailPanelProvider
|
||||
src/components/DashboardLayout.tsx — SubNav, tile reorder, DetailPanel render
|
||||
src/components/TopBar.tsx — session name → A.RECRUITER
|
||||
src/components/LoginScreen.tsx — visual refresh, connection status, username
|
||||
src/components/Card.tsx — no changes needed (already supports full prop)
|
||||
src/components/tiles/LatestResultsTile.tsx — remove flip, bigger numbers, panel
|
||||
src/components/tiles/CoreSkillsTile.tsx — full width, categorised, view all
|
||||
src/components/tiles/ProjectsTile.tsx — half width, card grid, panel
|
||||
src/components/tiles/LastConsultationTile.tsx — add panel trigger
|
||||
src/components/tiles/CareerActivityTile.tsx — constellation embed, panel triggers
|
||||
src/components/tiles/EducationTile.tsx — richer content, panel trigger
|
||||
src/components/tiles/PatientSummaryTile.tsx — structured presentation
|
||||
src/data/skills.ts — expand to ~20 skills with categories
|
||||
src/data/kpis.ts — add story fields
|
||||
src/types/pmr.ts — new types
|
||||
src/index.css — new CSS vars, animations
|
||||
src/hooks/useActiveSection.ts — update for new tile IDs
|
||||
src/lib/search.ts — update palette for new panel actions
|
||||
package.json — add d3 dependency
|
||||
```
|
||||
|
||||
### Unchanged (locked)
|
||||
```
|
||||
src/components/BootSequence.tsx
|
||||
src/components/ECGAnimation.tsx
|
||||
```
|
||||
@@ -0,0 +1,300 @@
|
||||
# Requirements Specification: Adding Depth to the GP Clinical Record
|
||||
|
||||
> Brainstorm session output — Feb 2026
|
||||
> Source of truth for content: `References/CV_v4.md`
|
||||
> ATS PDF is supplementary context only, not to be included wholesale.
|
||||
|
||||
---
|
||||
|
||||
## 1. Problem Statement
|
||||
|
||||
The current dashboard feels flat and light on information. Content is thin, sections feel like footnotes rather than showcases, and there's no mechanism to drill into detail. Projects are buried at the bottom. KPI numbers don't hit hard enough. Skills show only 5 items with no way to see more. The whole experience lacks depth, interactivity, and the sense that there's rich content behind every surface.
|
||||
|
||||
---
|
||||
|
||||
## 2. Core UX Patterns
|
||||
|
||||
### 2.1 Right-Side Detail Panel
|
||||
|
||||
A slide-in panel from the right edge of the screen — the primary mechanism for depth.
|
||||
|
||||
- **Trigger:** "View more" buttons, clickable career items, skills, KPIs, projects
|
||||
- **Entrance:** Slides in from the right. Dashboard content blurs slightly behind via backdrop-filter.
|
||||
- **Adaptive width:**
|
||||
- **Narrow (~400px):** For simple items — individual skills, education entries, single KPI stories
|
||||
- **Wide (~60% viewport):** For complex items — career roles with achievement lists, projects with screenshots/outcomes/tech stacks
|
||||
- **Close:** Click outside the panel, press Escape, or click a close button
|
||||
- **Animation:** 250ms ease-out slide, 150ms backdrop blur transition
|
||||
- **Accessibility:** Focus trap when open, Escape to close, ARIA role="dialog"
|
||||
|
||||
### 2.2 Sub-Navigation Bar
|
||||
|
||||
A fixed navigation strip below the TopBar for section jumping.
|
||||
|
||||
- **Position:** Immediately below TopBar, above the card grid content area
|
||||
- **Labels:** `Overview` | `Skills` | `Experience` | `Projects` | `Education`
|
||||
- **Behaviour:**
|
||||
- Click → smooth-scroll to that section
|
||||
- Active tab highlights based on scroll position (IntersectionObserver)
|
||||
- Sticky — stays visible as user scrolls
|
||||
- **Style:** Clean, understated. Matches GP system tab row aesthetic. Teal underline for active tab.
|
||||
|
||||
### 2.3 Hover + Click Interaction Model
|
||||
|
||||
Everything should feel alive and reactive:
|
||||
|
||||
- **Hover:** Items lift slightly — shadow deepens, subtle border colour shift. Cursor changes to pointer.
|
||||
- **Click:** Opens the detail panel with full content for that item.
|
||||
- **Career activity items:** Expand a small amount inline on hover (preview), then full detail via click → panel.
|
||||
|
||||
---
|
||||
|
||||
## 3. Revised Dashboard Layout
|
||||
|
||||
Tile order changes to prioritise what matters. Projects move up to be prominent. Skills get full width above career history.
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────┐
|
||||
│ TopBar (fixed, 48px) │
|
||||
│ Brand | Search (Ctrl+K) | Session: a.recruiter │
|
||||
├───────────────────────────────────────────────────────┤
|
||||
│ Sub-Nav Bar (fixed/sticky) │
|
||||
│ Overview | Skills | Experience | Projects | Education│
|
||||
├───────────┬───────────────────────────────────────────┤
|
||||
│ │ │
|
||||
│ Sidebar │ 1. Patient Summary (full width) │
|
||||
│ (272px) │ CV_v4.md profile text │
|
||||
│ │ │
|
||||
│ Person │ 2. Latest Results | Projects │
|
||||
│ Header │ (KPIs, left) | (card grid, right) │
|
||||
│ │ │
|
||||
│ Tags │ 3. Repeat Medications (full width) │
|
||||
│ │ Categorised skill groups │
|
||||
│ Alerts │ │
|
||||
│ │ 4. Last Consultation (full width) │
|
||||
│ │ │
|
||||
│ │ 5. Career Activity (full width) │
|
||||
│ │ Timeline + Career Constellation │
|
||||
│ │ │
|
||||
│ │ 6. Education (full width) │
|
||||
│ │ │
|
||||
└───────────┴───────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Changes from current:
|
||||
- **Projects move from bottom → half-width right column** (row 2, alongside KPIs)
|
||||
- **Skills move from half-width right → full-width** (row 3, above career history)
|
||||
- **Sub-nav bar added** between TopBar and content
|
||||
- **TopBar session info** shows `a.recruiter` post-login
|
||||
|
||||
---
|
||||
|
||||
## 4. Section Requirements
|
||||
|
||||
### 4.1 Patient Summary (Profile)
|
||||
|
||||
**Content source:** The `## Profile` section from `CV_v4.md`:
|
||||
|
||||
> "Healthcare leader combining clinical pharmacy expertise with proficiency in Python, SQL, and data analytics, self-taught over the past decade through a drive to find root causes in data and build the most efficient solutions to complex problems. Currently leading population health analytics for NHS Norfolk & Waveney ICB, serving a population of 1.2 million..."
|
||||
|
||||
**Presentation:**
|
||||
- Use the full profile paragraph from CV_v4.md
|
||||
- Structured — consider pulling out key highlights (years of experience, population served, budget managed) as a visual strip alongside the narrative
|
||||
- Not a wall of text — break it up with hierarchy
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Latest Results (KPIs) — Left Column
|
||||
|
||||
**Problem:** Current flip cards feel like footnotes. £220M should be a headline.
|
||||
|
||||
**Dashboard display:**
|
||||
- 4 KPI cards with **bold, large headline numbers** — visually dominant
|
||||
- Stronger contrast, larger type than current implementation
|
||||
- Each card is clearly clickable
|
||||
|
||||
**Click → Detail Panel (narrow):**
|
||||
- Opens with the **story behind the number**
|
||||
- Structure per KPI:
|
||||
- Headline number + label
|
||||
- Context: what it covers, scope, significance
|
||||
- Your role: what you did with this number
|
||||
- Key decisions or outcomes
|
||||
- Optional: supporting visual (mini chart, comparison, timeline)
|
||||
|
||||
**KPIs from CV_v4.md:**
|
||||
1. **£220M** — Prescribing budget managed with sophisticated forecasting models
|
||||
2. **£14.6M** — Efficiency programme identified through data analysis; over-target by Oct 2025
|
||||
3. **9+ Years** — Professional experience (Aug 2016–present)
|
||||
4. **1.2M** — Population served (Norfolk & Waveney ICS)
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Projects (Investigations) — Right Column (NEW POSITION)
|
||||
|
||||
**Problem:** Currently buried at the bottom as small expandable items.
|
||||
|
||||
**Dashboard display:**
|
||||
- Card grid with **thumbnails/screenshots**, title, status badge, tech tags
|
||||
- Prominent placement alongside KPIs draws immediate attention
|
||||
- Each card is clickable
|
||||
|
||||
**Click → Detail Panel (wide):**
|
||||
- Full project description
|
||||
- Outcome metrics and results
|
||||
- Tech stack with tags
|
||||
- Live link / GitHub link where available
|
||||
- Screenshot or demo visual
|
||||
|
||||
**Projects from current data:**
|
||||
1. PharMetrics Interactive Platform (2024, Live) — with external link
|
||||
2. Patient Switching Algorithm (2025, Complete)
|
||||
3. Blueteq Generator (2023, Complete)
|
||||
4. CD Monitoring System (2024, Complete)
|
||||
5. Sankey Chart Analysis Tool (2023, Complete)
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Skills / Repeat Medications — Full Width (NEW POSITION)
|
||||
|
||||
**Problem:** Only 5 skills, no categorisation, no view more.
|
||||
|
||||
**Dashboard display:**
|
||||
- **Categorised groups** (like BNF chapters in a real formulary):
|
||||
- **Technical:** Python, SQL, Power BI, JavaScript/TypeScript, Real-world data analysis, Dashboard/tool development, Algorithm design, Data pipeline development
|
||||
- **Healthcare Domain:** Medicines optimisation, Population health analytics, NICE TA implementation, Health economics & outcomes, Clinical pathway development, Controlled drug assurance
|
||||
- **Strategic & Leadership:** Budget management (£220M), Stakeholder engagement, Pharmaceutical negotiation, Team development & training, Change management, Financial scenario modelling, Executive communication
|
||||
- **Display:** Top 3-5 per category visible on the dashboard tile, with medication-style frequency/dosing metaphor
|
||||
- **"View all" button** per category or for the whole section
|
||||
|
||||
**Click → Detail Panel (narrow):**
|
||||
- Full categorised list of all skills
|
||||
- Each skill with: proficiency level, years of experience, frequency metaphor (daily, twice daily, when required, etc.)
|
||||
- Skills are interactive — clicking a skill could show which roles/projects used it
|
||||
|
||||
---
|
||||
|
||||
### 4.5 Last Consultation — Full Width
|
||||
|
||||
**Dashboard display:**
|
||||
- Most recent role with headline info (title, organisation, dates, type)
|
||||
- Brief preview of key achievements (2-3 bullets)
|
||||
|
||||
**Click → Detail Panel (wide):**
|
||||
- Full role description from CV_v4.md
|
||||
- All achievement bullets
|
||||
- Technical environment
|
||||
- Coded entries (if applicable)
|
||||
|
||||
---
|
||||
|
||||
### 4.6 Career Activity + Career Constellation — Full Width
|
||||
|
||||
#### Timeline (existing, enhanced)
|
||||
- Colour-coded timeline: teal (roles), amber (projects), green (certifications), purple (education)
|
||||
- **Extended back to school (2009)** — Highworth Grammar through university through career
|
||||
- Role entries expand slightly on hover with preview text
|
||||
- Click → detail panel for full role information
|
||||
|
||||
#### Career Constellation (NEW — D3.js)
|
||||
- **Embedded within the Career Activity section** as a large visual (not a separate view)
|
||||
- **Force-directed network graph** built with D3.js:
|
||||
- **Role nodes:** Large, positioned chronologically left-to-right
|
||||
- Pre-Registration Pharmacist, Paydens (2015-2016)
|
||||
- Duty Pharmacy Manager, Tesco (2016-2017)
|
||||
- Pharmacy Manager, Tesco (2017-2022)
|
||||
- High-Cost Drugs & Interface Pharmacist, NHS (2022-2024)
|
||||
- Deputy Head, Population Health & Data Analysis, NHS (2024-present)
|
||||
- Interim Head, Population Health & Data Analysis, NHS (2025)
|
||||
- **Skill nodes:** Smaller, orbit around the roles they belong to
|
||||
- Colour-coded by domain: clinical (green), technical (teal), leadership (amber)
|
||||
- **Bridge connections:** Skills spanning multiple roles create visible links between eras
|
||||
- e.g., Python connects Tesco-era self-teaching → NHS data work
|
||||
- e.g., Clinical pathway knowledge bridges community pharmacy → NHS HCD role
|
||||
- **Interactions:**
|
||||
- Hover role → its skill cluster highlights and radiates outward
|
||||
- Hover skill → all roles that used it illuminate, showing the through-line
|
||||
- Click role/skill → detail panel
|
||||
- **Purpose:** Demonstrates D3.js/data-vis capability as portfolio content itself
|
||||
|
||||
---
|
||||
|
||||
### 4.7 Education — Full Width
|
||||
|
||||
**Dashboard display:**
|
||||
- Education entries with more detail than current:
|
||||
1. MPharm (Hons) 2:1 — University of East Anglia, 2011-2015
|
||||
- Research project: Drug delivery & cocrystals, 75.1% (Distinction)
|
||||
- 4th year OSCE: 80%
|
||||
2. Mary Seacole Programme — NHS Leadership Academy, 2018, 78%
|
||||
3. A-Levels — Highworth Grammar School, 2009-2011
|
||||
- Mathematics (A*), Chemistry (B), Politics (C)
|
||||
|
||||
**Click → Detail Panel (narrow):**
|
||||
- Full education detail including extracurriculars (Pharmacy Society President, Ultimate Frisbee VP, Alzheimer's Society)
|
||||
- Research project description
|
||||
- Mary Seacole programme detail (change management, healthcare leadership, system-level thinking)
|
||||
|
||||
---
|
||||
|
||||
## 5. Login Page Refresh
|
||||
|
||||
### 5.1 Visual Overhaul
|
||||
- Restyle the login card to match the GP dashboard aesthetic:
|
||||
- Teal accents (not the current colour scheme)
|
||||
- Elvaro Grotesque font
|
||||
- Refined shadows matching the three-tier system
|
||||
- Warm palette cohesive with the dashboard it leads into
|
||||
- Background should feel like the system's pre-authenticated state
|
||||
|
||||
### 5.2 Username Change
|
||||
- **Username typed:** `a.recruiter` (the recruiter is logging into your clinical records)
|
||||
- **TopBar post-login:** Session shows `a.recruiter` as the logged-in user
|
||||
- Password typing remains as dots
|
||||
|
||||
### 5.3 "Awaiting Secure Connection" Polish
|
||||
- Below the login button: a status indicator area
|
||||
- **Initial state:** Red dot + "Awaiting secure connection..."
|
||||
- **After ~2 seconds:** Dot transitions to green + "Secure connection established"
|
||||
- **Login button** becomes clearly interactive only after the green state (was previously greyed/inactive)
|
||||
- Optional: subtle smart card or security authentication visual cue (e.g., a small chip card icon or lock icon animating)
|
||||
|
||||
### 5.4 Post-Login Transition
|
||||
- On button click: brief "System loading..." state with a clinical-style progress indicator
|
||||
- Slight delay (500-800ms) to feel purposeful
|
||||
- Then dashboard materialises with the existing staggered entrance animation (TopBar → Sidebar → Content)
|
||||
|
||||
---
|
||||
|
||||
## 6. Technical Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|----------|--------|-----------|
|
||||
| Career Constellation | D3.js | Industry standard. Most impressive as portfolio piece. Demonstrates serious data-vis skill. |
|
||||
| Detail Panel | Custom React component | Slide-in panel with backdrop blur. Adaptive width based on content type. |
|
||||
| Sub-Nav | IntersectionObserver + scroll | Scroll-spy for active state, smooth scroll on click. |
|
||||
| Content source | CV_v4.md | Primary source of truth for all factual content. |
|
||||
|
||||
---
|
||||
|
||||
## 7. Content Source Hierarchy
|
||||
|
||||
1. **`References/CV_v4.md`** — Primary source of truth for all roles, dates, achievements, numbers
|
||||
2. **`References/Andy_Charlwood_CV_ATS_Optimised.pdf`** — Supplementary context only. Do NOT include wholesale. Use only when CV_v4.md lacks specific detail.
|
||||
3. **`cv-website` data** — Reference for interactivity patterns and content structure, not content itself
|
||||
|
||||
---
|
||||
|
||||
## 8. What This Specification Does NOT Cover
|
||||
|
||||
Per `/sc:brainstorm` boundaries, this document covers requirements only:
|
||||
|
||||
- **No architecture decisions** — use `/sc:design` for component architecture
|
||||
- **No implementation code** — use `/sc:implement` for building
|
||||
- **No database schemas or API contracts** — N/A (static SPA)
|
||||
- **No technical specifications beyond requirements** — implementation details deferred
|
||||
|
||||
### Recommended Next Steps
|
||||
1. `/sc:design` — Design component architecture for detail panel, sub-nav, constellation
|
||||
2. `/sc:workflow` — Generate implementation task breakdown
|
||||
3. Implementation — Build in phases (core UX patterns → section depth → constellation → login refresh)
|
||||
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"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."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Ralph Wiggum Loop - Visualization Improvements variant.
|
||||
|
||||
.DESCRIPTION
|
||||
Outer loop for iterative chart improvement (bug fixes, polish, new analytics).
|
||||
Each iteration spawns a fresh `claude --print` invocation.
|
||||
Memory persists via filesystem only: git commits, progress.txt, IMPLEMENTATION_PLAN.md, guardrails.md.
|
||||
|
||||
Runs until completion (<promise>COMPLETE</promise>) or circuit breaker trips.
|
||||
No arbitrary iteration limit — the loop continues until done.
|
||||
|
||||
Circuit breakers prevent runaway costs:
|
||||
- No git changes for N consecutive iterations (stalled)
|
||||
- Same error repeated N consecutive iterations (stuck)
|
||||
|
||||
.PARAMETER Model
|
||||
Initial Claude model to use. Default: "sonnet". The agent can dynamically switch
|
||||
models between iterations via <next-model>opus|sonnet</next-model> signals.
|
||||
|
||||
.PARAMETER BranchName
|
||||
Optional git branch name. If provided, creates/checks out the branch before starting.
|
||||
|
||||
.PARAMETER MaxNoProgress
|
||||
Number of consecutive iterations with no git changes before circuit breaker trips. Default: 3.
|
||||
|
||||
.PARAMETER MaxSameError
|
||||
Number of consecutive iterations with the same error before circuit breaker trips. Default: 3.
|
||||
|
||||
.EXAMPLE
|
||||
.\ralph.ps1 -Model "opus" -BranchName "feature/dash-migration"
|
||||
|
||||
.EXAMPLE
|
||||
.\ralph.ps1 -Model "sonnet" -MaxNoProgress 2
|
||||
#>
|
||||
|
||||
param(
|
||||
[string]$Model = "opus",
|
||||
[string]$BranchName,
|
||||
[int]$MaxNoProgress = 3,
|
||||
[int]$MaxSameError = 3
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$promptFile = Join-Path $scriptDir "RALPH_PROMPT.md"
|
||||
$planFile = Join-Path $scriptDir "IMPLEMENTATION_PLAN.md"
|
||||
$guardrailsFile = Join-Path $scriptDir "guardrails.md"
|
||||
$progressFile = Join-Path $scriptDir "progress.txt"
|
||||
$logDir = Join-Path $scriptDir "logs"
|
||||
|
||||
# --- Validation ---
|
||||
|
||||
if (-not (Test-Path $promptFile)) {
|
||||
Write-Error "RALPH_PROMPT.md not found at $promptFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $planFile)) {
|
||||
Write-Error "IMPLEMENTATION_PLAN.md not found at $planFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $guardrailsFile)) {
|
||||
Write-Warning "guardrails.md not found at $guardrailsFile - loop may miss known failure patterns"
|
||||
}
|
||||
|
||||
# Ensure progress.txt exists
|
||||
if (-not (Test-Path $progressFile)) {
|
||||
@"
|
||||
# Progress Log
|
||||
|
||||
## Design Context
|
||||
<!-- Design decisions and context go here -->
|
||||
|
||||
## Reflex Patterns
|
||||
<!-- Reusable Reflex patterns discovered during development -->
|
||||
|
||||
## Iteration Log
|
||||
<!-- Each iteration appends a structured entry below. See RALPH_PROMPT.md for format. -->
|
||||
"@ | Set-Content -Path $progressFile -Encoding UTF8
|
||||
Write-Host "Created progress.txt"
|
||||
}
|
||||
|
||||
# Ensure logs directory exists
|
||||
if (-not (Test-Path $logDir)) {
|
||||
New-Item -ItemType Directory -Path $logDir | Out-Null
|
||||
Write-Host "Created logs directory"
|
||||
}
|
||||
|
||||
# --- Git Setup ---
|
||||
|
||||
$gitInitialised = $false
|
||||
try {
|
||||
$result = git rev-parse --is-inside-work-tree 2>&1
|
||||
if ($LASTEXITCODE -eq 0 -and $result -eq "true") {
|
||||
$gitInitialised = $true
|
||||
}
|
||||
} catch {
|
||||
# Not a git repo — expected on first run
|
||||
}
|
||||
|
||||
if (-not $gitInitialised) {
|
||||
Write-Host "Initialising git repository..."
|
||||
git init
|
||||
git add -A
|
||||
git commit -m "Initial commit before Ralph loop"
|
||||
}
|
||||
|
||||
if ($BranchName) {
|
||||
$currentBranch = git branch --show-current
|
||||
if ($currentBranch -ne $BranchName) {
|
||||
$branchExists = git branch --list $BranchName
|
||||
if ($branchExists) {
|
||||
Write-Host "Switching to existing branch: $BranchName"
|
||||
git checkout $BranchName
|
||||
} else {
|
||||
Write-Host "Creating branch: $BranchName"
|
||||
git checkout -b $BranchName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Circuit Breaker State ---
|
||||
|
||||
$noProgressCount = 0
|
||||
$lastErrorSignature = ""
|
||||
$sameErrorCount = 0
|
||||
|
||||
# Capture the HEAD commit hash before the loop starts
|
||||
$preLoopHead = git rev-parse HEAD 2>$null
|
||||
|
||||
# --- Main Loop ---
|
||||
|
||||
$promptContent = Get-Content -Path $promptFile -Raw
|
||||
|
||||
# Count existing iterations from progress.txt to track total across runs
|
||||
$existingIterations = 0
|
||||
if (Test-Path $progressFile) {
|
||||
$existingIterations = (Select-String -Path $progressFile -Pattern "## Iteration" -AllMatches | Measure-Object).Count
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "===== Ralph Wiggum Loop (Visualization Improvements) =====" -ForegroundColor Cyan
|
||||
Write-Host "Model: $Model (dynamic switching enabled) | Visual review: Playwright MCP | Runs until COMPLETE" -ForegroundColor Cyan
|
||||
Write-Host "Circuit breakers: no-progress=$MaxNoProgress, same-error=$MaxSameError" -ForegroundColor Cyan
|
||||
if ($BranchName) { Write-Host "Branch: $BranchName" -ForegroundColor Cyan }
|
||||
if ($existingIterations -gt 0) { Write-Host "Previous iterations: $existingIterations" -ForegroundColor Cyan }
|
||||
Write-Host "===========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# --- Dev Server (for visual review via Playwright MCP) ---
|
||||
$devServerPort = 5173
|
||||
$devServerPid = $null
|
||||
|
||||
try {
|
||||
$null = Invoke-WebRequest -Uri "http://localhost:$devServerPort" -TimeoutSec 2 -ErrorAction Stop
|
||||
Write-Host "Dev server detected on port $devServerPort" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "Starting dev server (port $devServerPort)..." -ForegroundColor Cyan
|
||||
$devProc = Start-Process -FilePath "npm.cmd" -ArgumentList "run", "dev" -PassThru -WindowStyle Minimized
|
||||
$devServerPid = $devProc.Id
|
||||
|
||||
for ($w = 1; $w -le 20; $w++) {
|
||||
Start-Sleep -Seconds 1
|
||||
try {
|
||||
$null = Invoke-WebRequest -Uri "http://localhost:$devServerPort" -TimeoutSec 2 -ErrorAction Stop
|
||||
Write-Host "Dev server ready on port $devServerPort" -ForegroundColor Green
|
||||
break
|
||||
} catch {
|
||||
if ($w -eq 20) {
|
||||
Write-Warning "Dev server may not be ready - visual review steps may fail"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
$i = 0
|
||||
while ($true) {
|
||||
$i++
|
||||
$totalIteration = $existingIterations + $i
|
||||
Write-Host ""
|
||||
Write-Host "--- Iteration $i (Total: $totalIteration) ---" -ForegroundColor Yellow
|
||||
|
||||
# Record HEAD before this iteration
|
||||
$headBefore = git rev-parse HEAD 2>$null
|
||||
|
||||
# Show start time and status
|
||||
$iterStart = Get-Date
|
||||
Write-Host " Started: $($iterStart.ToString('HH:mm:ss'))" -ForegroundColor DarkGray
|
||||
Write-Host " Spawning Claude ($Model)..." -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
|
||||
# Spawn fresh Claude instance with stream-json for tool call visibility
|
||||
$logFile = Join-Path $logDir "iteration_$totalIteration.log"
|
||||
$rawLogFile = Join-Path $logDir "iteration_$totalIteration.raw.jsonl"
|
||||
$maxRetries = 10
|
||||
$retryCount = 0
|
||||
$outputString = ""
|
||||
$apiOverloaded = $false
|
||||
|
||||
do {
|
||||
$apiOverloaded = $false
|
||||
$textBuilder = [System.Text.StringBuilder]::new()
|
||||
$toolCount = 0
|
||||
|
||||
# Clear raw log file for this attempt
|
||||
if (Test-Path $rawLogFile) { Remove-Item $rawLogFile -Force }
|
||||
|
||||
if ($retryCount -gt 0) {
|
||||
$backoffSeconds = [Math]::Pow(2, $retryCount - 1)
|
||||
Write-Host " [Retry $retryCount/$maxRetries] API overloaded, waiting $backoffSeconds seconds..." -ForegroundColor DarkYellow
|
||||
Start-Sleep -Seconds $backoffSeconds
|
||||
Write-Host " Retrying Claude invocation..." -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
$promptContent | claude --print --verbose --dangerously-skip-permissions --model $Model --output-format stream-json 2>&1 | ForEach-Object {
|
||||
$line = $_.ToString().Trim()
|
||||
if (-not $line) { return }
|
||||
|
||||
# Save raw event for debugging (with error handling for stream closure)
|
||||
try {
|
||||
Add-Content -Path $rawLogFile -Value $line -Encoding UTF8 -ErrorAction SilentlyContinue
|
||||
} catch {
|
||||
# Stream closed or file locked - ignore and continue
|
||||
}
|
||||
|
||||
try {
|
||||
$evt = $line | ConvertFrom-Json -ErrorAction Stop
|
||||
|
||||
# --- Tool use start (show tool name) ---
|
||||
if ($evt.type -eq 'content_block_start' -and $evt.content_block.type -eq 'tool_use') {
|
||||
$toolCount++
|
||||
$toolName = $evt.content_block.name
|
||||
Write-Host " [$toolName]" -ForegroundColor DarkCyan
|
||||
}
|
||||
# --- Assistant text content (streaming deltas) ---
|
||||
elseif ($evt.type -eq 'content_block_delta' -and $evt.delta.type -eq 'text_delta' -and $evt.delta.text) {
|
||||
Write-Host -NoNewline $evt.delta.text
|
||||
[void]$textBuilder.Append($evt.delta.text)
|
||||
}
|
||||
# --- Result event (error display + text capture for circuit breakers) ---
|
||||
elseif ($evt.type -eq 'result') {
|
||||
if ($evt.subtype -eq 'error_result' -and $evt.error) {
|
||||
Write-Host " [ERROR] $($evt.error)" -ForegroundColor Red
|
||||
[void]$textBuilder.AppendLine("ERROR: $($evt.error)")
|
||||
}
|
||||
elseif ($evt.result) {
|
||||
# Capture for circuit breaker detection; don't print
|
||||
# (text already displayed via streaming deltas above)
|
||||
[void]$textBuilder.AppendLine($evt.result)
|
||||
}
|
||||
}
|
||||
# --- Message-level content (final message summary) ---
|
||||
elseif ($evt.message -and $evt.message.content) {
|
||||
foreach ($block in $evt.message.content) {
|
||||
if ($block.type -eq 'text' -and $block.text) {
|
||||
Write-Host $block.text
|
||||
[void]$textBuilder.AppendLine($block.text)
|
||||
}
|
||||
elseif ($block.type -eq 'tool_use') {
|
||||
$toolCount++
|
||||
Write-Host " [$($block.name)]" -ForegroundColor DarkCyan
|
||||
}
|
||||
# Silently ignore tool_result and other block types
|
||||
}
|
||||
}
|
||||
# All other JSON events (input_json_delta, content_block_stop,
|
||||
# message_start, message_stop, ping, etc.) are silently ignored
|
||||
|
||||
} catch {
|
||||
# Not valid JSON — only print if it looks like meaningful stderr
|
||||
# (filter out JSON fragments from multi-line events)
|
||||
if ($line -and $line -notmatch '^\s*[\{\[\}\]"]') {
|
||||
Write-Host $line -ForegroundColor DarkYellow
|
||||
[void]$textBuilder.AppendLine($line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$outputString = $textBuilder.ToString()
|
||||
|
||||
# Check for 529 overloaded error
|
||||
if ($outputString -match "529.*overloaded|overloaded_error") {
|
||||
$apiOverloaded = $true
|
||||
$retryCount++
|
||||
if ($retryCount -ge $maxRetries) {
|
||||
Write-Host " [ERROR] API overloaded after $maxRetries retries, giving up." -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
# Check for usage limit with cooldown (e.g. "Usage limit reached. Reset at 3 pm")
|
||||
elseif ($outputString -match "(?i)usage limit reached.*reset at (\d{1,2})(?::(\d{2}))?\s*(am|pm)") {
|
||||
$resetHour = [int]$Matches[1]
|
||||
$resetMinute = if ($Matches[2]) { [int]$Matches[2] } else { 0 }
|
||||
$resetAmPm = $Matches[3]
|
||||
|
||||
if ($resetAmPm -ieq "pm" -and $resetHour -ne 12) { $resetHour += 12 }
|
||||
elseif ($resetAmPm -ieq "am" -and $resetHour -eq 12) { $resetHour = 0 }
|
||||
|
||||
$now = Get-Date
|
||||
$resetTime = Get-Date -Hour $resetHour -Minute $resetMinute -Second 0
|
||||
if ($resetTime -le $now) { $resetTime = $resetTime.AddDays(1) }
|
||||
$resetTime = $resetTime.AddMinutes(2)
|
||||
|
||||
$waitSeconds = [Math]::Ceiling(($resetTime - $now).TotalSeconds)
|
||||
$waitMinutes = [Math]::Ceiling($waitSeconds / 60)
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " [USAGE LIMIT] Reset at $($Matches[1]) $resetAmPm. Cooling down ~$waitMinutes minutes (until $($resetTime.ToString('HH:mm')))..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds $waitSeconds
|
||||
Write-Host " [USAGE LIMIT] Cooldown complete. Retrying iteration..." -ForegroundColor Green
|
||||
|
||||
$apiOverloaded = $true
|
||||
# Don't increment retryCount — deterministic wait, not a flaky error
|
||||
}
|
||||
} while ($apiOverloaded -and $retryCount -lt $maxRetries)
|
||||
|
||||
$outputString | Set-Content -Path $logFile -Encoding UTF8
|
||||
|
||||
# Show elapsed time and tool count
|
||||
$elapsed = (Get-Date) - $iterStart
|
||||
Write-Host ""
|
||||
Write-Host " Finished: $(Get-Date -Format 'HH:mm:ss') (elapsed: $($elapsed.ToString('mm\:ss')), tools: $toolCount)" -ForegroundColor DarkGray
|
||||
|
||||
# --- Circuit Breaker: No Progress ---
|
||||
$headAfter = git rev-parse HEAD 2>$null
|
||||
if ($headAfter -eq $headBefore) {
|
||||
$noProgressCount++
|
||||
Write-Host " [Circuit Breaker] No git commits this iteration ($noProgressCount/$MaxNoProgress)" -ForegroundColor DarkYellow
|
||||
if ($noProgressCount -ge $MaxNoProgress) {
|
||||
Write-Host ""
|
||||
Write-Host "===== CIRCUIT BREAKER: NO PROGRESS =====" -ForegroundColor Red
|
||||
Write-Host "No git commits for $MaxNoProgress consecutive iterations. The loop is stalled." -ForegroundColor Red
|
||||
Write-Host "Check progress.txt and logs/ for details on what went wrong." -ForegroundColor Red
|
||||
if ($devServerPid) { taskkill /T /F /PID $devServerPid 2>$null | Out-Null }
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
$noProgressCount = 0
|
||||
}
|
||||
|
||||
# --- Circuit Breaker: Repeated Error ---
|
||||
$errorLines = $outputString | Select-String -Pattern "(?i)(error|exception|failed|fatal)[:.].*" -AllMatches
|
||||
if ($errorLines) {
|
||||
$filteredErrors = $errorLines.Matches | Where-Object { $_.Value -notmatch "529|overloaded" } | Select-Object -First 3
|
||||
$currentErrorSignature = ($filteredErrors | ForEach-Object { $_.Value }) -join "|"
|
||||
if ($currentErrorSignature -and $currentErrorSignature -eq $lastErrorSignature) {
|
||||
$sameErrorCount++
|
||||
Write-Host " [Circuit Breaker] Same error pattern repeated ($sameErrorCount/$MaxSameError)" -ForegroundColor DarkYellow
|
||||
if ($sameErrorCount -ge $MaxSameError) {
|
||||
Write-Host ""
|
||||
Write-Host "===== CIRCUIT BREAKER: REPEATED ERROR =====" -ForegroundColor Red
|
||||
Write-Host "Same error pattern for $MaxSameError consecutive iterations:" -ForegroundColor Red
|
||||
Write-Host " $currentErrorSignature" -ForegroundColor Red
|
||||
Write-Host "Check progress.txt and logs/ for details." -ForegroundColor Red
|
||||
if ($devServerPid) { taskkill /T /F /PID $devServerPid 2>$null | Out-Null }
|
||||
exit 1
|
||||
}
|
||||
} elseif ($currentErrorSignature) {
|
||||
$sameErrorCount = 0
|
||||
}
|
||||
$lastErrorSignature = $currentErrorSignature
|
||||
} else {
|
||||
$sameErrorCount = 0
|
||||
$lastErrorSignature = ""
|
||||
}
|
||||
|
||||
# --- Dynamic Model Selection ---
|
||||
if ($outputString -match "<next-model>(opus|sonnet)</next-model>") {
|
||||
$nextModel = $Matches[1]
|
||||
if ($nextModel -ne $Model) {
|
||||
Write-Host " [Model Switch] $Model -> $nextModel (agent recommendation)" -ForegroundColor Magenta
|
||||
$Model = $nextModel
|
||||
} else {
|
||||
Write-Host " [Model] Staying on $Model" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check for Completion ---
|
||||
if ($outputString -match "<promise>COMPLETE</promise>") {
|
||||
Write-Host ""
|
||||
Write-Host "===== COMPLETE =====" -ForegroundColor Green
|
||||
Write-Host "Visualization improvements finished after $i iteration(s) this run ($totalIteration total)." -ForegroundColor Green
|
||||
if ($devServerPid) { taskkill /T /F /PID $devServerPid 2>$null | Out-Null }
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Brief pause between iterations
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
@@ -0,0 +1,773 @@
|
||||
# Implementation Workflow: Adding Depth to the GP Clinical Record
|
||||
|
||||
> Generated: Feb 2026
|
||||
> Source: `Ralph/depth-requirements.md` + `Ralph/depth-design.md`
|
||||
> Prerequisite: Task 21 (cleanup) from current plan should be completed first
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```
|
||||
Phase 1: Core Infrastructure
|
||||
T1 ─── Types & CSS foundations
|
||||
T2 ─── DetailPanelContext + DetailPanel component ──── depends on T1
|
||||
T3 ─── useFocusTrap hook ─────────────────────────── depends on T1
|
||||
T4 ─── SubNav + useActiveSection update ──────────── depends on T1
|
||||
T5 ─── DashboardLayout restructure ──────────────── depends on T2, T3, T4
|
||||
|
||||
Phase 2: Data Expansion
|
||||
T6 ─── Expand skills.ts (5 → ~20, categorised) ──── depends on T1
|
||||
T7 ─── Add KPI stories to kpis.ts ────────────────── depends on T1
|
||||
T8 ─── Create constellation.ts data ──────────────── depends on T6
|
||||
T9 ─── Create educationExtras.ts ─────────────────── depends on T1
|
||||
|
||||
Phase 3: Tile Modifications (parallel where possible)
|
||||
T10 ─── LatestResultsTile (bigger numbers, panel) ─── depends on T2, T7
|
||||
T11 ─── CoreSkillsTile (full width, categorised) ──── depends on T2, T6
|
||||
T12 ─── ProjectsTile (half width, card grid) ──────── depends on T2
|
||||
T13 ─── LastConsultationTile (panel trigger) ──────── depends on T2
|
||||
T14 ─── CareerActivityTile (panel triggers, hover) ── depends on T2
|
||||
T15 ─── EducationTile (richer content, panel) ─────── depends on T2, T9
|
||||
T16 ─── PatientSummaryTile (structured presentation)─ depends on T5
|
||||
|
||||
Phase 4: Detail Panel Renderers
|
||||
T17 ─── KPIDetail renderer ────────────────────────── depends on T10
|
||||
T18 ─── ConsultationDetail renderer ───────────────── depends on T13, T14
|
||||
T19 ─── ProjectDetail renderer ────────────────────── depends on T12
|
||||
T20 ─── SkillDetail + SkillsAllDetail renderers ───── depends on T11
|
||||
T21 ─── EducationDetail renderer ──────────────────── depends on T15
|
||||
|
||||
Phase 5: Career Constellation (D3.js)
|
||||
T22 ─── Install d3, scaffold CareerConstellation ──── depends on T8
|
||||
T23 ─── D3 force graph rendering ──────────────────── depends on T22
|
||||
T24 ─── Hover/click interactions → detail panel ───── depends on T23, T18, T20
|
||||
T25 ─── Constellation accessibility ───────────────── depends on T23
|
||||
|
||||
Phase 6: Login Refresh
|
||||
T26 ─── LoginScreen visual restyle ────────────────── independent
|
||||
T27 ─── Username → a.recruiter + connection status ── depends on T26
|
||||
T28 ─── Post-login loading state ──────────────────── depends on T27
|
||||
T29 ─── TopBar session name update ────────────────── depends on T28
|
||||
|
||||
Phase 7: Polish & Integration
|
||||
T30 ─── CommandPalette updates for new content ────── depends on T17-T21
|
||||
T31 ─── Responsive testing (panels, sub-nav) ──────── depends on T5, T2
|
||||
T32 ─── prefers-reduced-motion audit ──────────────── depends on all
|
||||
T33 ─── Final visual review + cleanup ─────────────── depends on all
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-Flight: Complete Task 21 (Cleanup)
|
||||
|
||||
Before starting depth work, the current plan's final task must be done:
|
||||
|
||||
- [ ] Remove unused old components (PatientBanner, ClinicalSidebar, Breadcrumb, MobileBottomNav, PMRInterface)
|
||||
- [ ] Remove old view files (`src/components/views/*.tsx`)
|
||||
- [ ] Remove old portfolio components (Contact, Education, Experience, FloatingNav, Footer, Hero, Projects, Skills)
|
||||
- [ ] Remove unused hooks (useScrollCondensation if unused)
|
||||
- [ ] Verify no dead imports
|
||||
- [ ] `npm run build` clean
|
||||
|
||||
**Checkpoint:** Clean build with zero unused components.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Core Infrastructure
|
||||
|
||||
### Task 1: Types & CSS Foundations
|
||||
|
||||
**Files:** `src/types/pmr.ts`, `src/index.css`
|
||||
**Effort:** Small
|
||||
|
||||
Add all new TypeScript types and CSS custom properties needed by subsequent tasks.
|
||||
|
||||
**Types to add (`src/types/pmr.ts`):**
|
||||
- `SkillCategory` — `'Technical' | 'Domain' | 'Leadership'`
|
||||
- `KPIStory` — context, role, outcomes[], period
|
||||
- Augment `KPI` with optional `story?: KPIStory`
|
||||
- `ConstellationNode` — id, type, label, domain, org data
|
||||
- `ConstellationLink` — source, target, strength
|
||||
- `DetailPanelContent` — discriminated union (kpi | skill | skills-all | consultation | project | education | career-role)
|
||||
- `EducationExtra` — documentId, extracurriculars, researchDescription, programmeDetail
|
||||
- Add `category?: SkillCategory` field to `SkillMedication`
|
||||
|
||||
**CSS to add (`src/index.css`):**
|
||||
```css
|
||||
--subnav-height: 36px;
|
||||
--panel-narrow: 400px;
|
||||
--panel-wide: 60vw;
|
||||
--backdrop-blur: 4px;
|
||||
--backdrop-bg: rgba(26,43,42,0.15);
|
||||
```
|
||||
|
||||
Plus panel animation keyframes (`panel-slide-in`, `panel-slide-out`, `backdrop-fade-in`) with `prefers-reduced-motion` overrides.
|
||||
|
||||
**Validation:** `npm run typecheck` passes.
|
||||
|
||||
---
|
||||
|
||||
### Task 2: DetailPanelContext + DetailPanel Component
|
||||
|
||||
**New files:** `src/contexts/DetailPanelContext.tsx`, `src/components/DetailPanel.tsx`
|
||||
**Depends on:** T1
|
||||
**Effort:** Medium
|
||||
|
||||
**DetailPanelContext (`src/contexts/DetailPanelContext.tsx`):**
|
||||
- `DetailPanelContextValue`: `{ content, openPanel, closePanel, isOpen }`
|
||||
- `DetailPanelProvider` wraps children, manages state
|
||||
- Width mapping: deterministic from `content.type` (narrow for kpi/skill/education, wide for consultation/project/career-role)
|
||||
- Title mapping: derived from content data
|
||||
|
||||
**DetailPanel (`src/components/DetailPanel.tsx`):**
|
||||
- Full-screen backdrop (`backdrop-filter: blur(4px)`, click to close)
|
||||
- Panel slides from right (`translateX(100%)` → `translateX(0)`, 250ms ease-out)
|
||||
- Adaptive width: `var(--panel-narrow)` or `var(--panel-wide)` based on content type
|
||||
- Header: close button (X, lucide `X` icon) + dot + section title
|
||||
- Scrollable content area renders `{children}` (or delegates to content renderers)
|
||||
- Close triggers: backdrop click, Escape key, X button
|
||||
- `aria-modal="true"`, `role="dialog"`, `aria-labelledby`
|
||||
- Mobile: both widths become 100vw
|
||||
- `prefers-reduced-motion`: instant appear, no slide
|
||||
|
||||
**Integration:** Initially renders placeholder content ("Detail panel for {type}"). Real content renderers come in Phase 4.
|
||||
|
||||
**Validation:** Panel opens/closes correctly with keyboard and mouse. `npm run typecheck` + `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 3: useFocusTrap Hook
|
||||
|
||||
**New file:** `src/hooks/useFocusTrap.ts`
|
||||
**Depends on:** T1
|
||||
**Effort:** Small
|
||||
|
||||
- `useFocusTrap(containerRef: RefObject<HTMLElement>, isActive: boolean): void`
|
||||
- When active: Tab/Shift+Tab cycle within container, first focusable element receives focus
|
||||
- When deactivated: focus returns to the element that was focused before trap activated
|
||||
- Used by DetailPanel (and already used by CommandPalette — consider if CommandPalette can share this hook)
|
||||
|
||||
**Validation:** Tab cycling confirmed in DetailPanel. Focus returns correctly on close.
|
||||
|
||||
---
|
||||
|
||||
### Task 4: SubNav + useActiveSection Update
|
||||
|
||||
**New file:** `src/components/SubNav.tsx`
|
||||
**Modified file:** `src/hooks/useActiveSection.ts`
|
||||
**Depends on:** T1
|
||||
**Effort:** Medium
|
||||
|
||||
**SubNav component:**
|
||||
- Fixed/sticky below TopBar (`top: 48px`, `z-index: 99`)
|
||||
- 5 sections: Overview | Skills | Experience | Projects | Education
|
||||
- Click → smooth-scroll to `[data-tile-id="${tileId}"]`
|
||||
- Active tab: teal underline (2px), text colour `var(--accent)`
|
||||
- Inactive: `var(--text-secondary)`
|
||||
- Height: 36px, background `var(--surface)`, bottom border
|
||||
- Tabs: 13px, font-weight 500, gap 24px
|
||||
- Teal underline slides with 200ms transition
|
||||
|
||||
**useActiveSection update:**
|
||||
- Observe `data-tile-id` attributes on tile elements
|
||||
- Map tile IDs to section IDs (patient-summary→overview, core-skills→skills, etc.)
|
||||
- Use IntersectionObserver with appropriate thresholds
|
||||
|
||||
**Tile `data-tile-id` attributes:** Ensure each tile's Card has this attribute. May need to add `tileId` prop to Card if not already present.
|
||||
|
||||
**Validation:** Scroll triggers correct active tab. Click scrolls to correct section. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 5: DashboardLayout Restructure
|
||||
|
||||
**Modified file:** `src/components/DashboardLayout.tsx`
|
||||
**Depends on:** T2, T3, T4
|
||||
**Effort:** Medium
|
||||
|
||||
**Changes:**
|
||||
1. Wrap with `DetailPanelProvider` (in App.tsx or DashboardLayout)
|
||||
2. Add `SubNav` between TopBar and content
|
||||
3. Reorder tiles:
|
||||
- PatientSummaryTile (full width)
|
||||
- LatestResultsTile (half) + ProjectsTile (half) — side by side
|
||||
- CoreSkillsTile (full width) — was half, now full
|
||||
- LastConsultationTile (full width)
|
||||
- CareerActivityTile (full width)
|
||||
- EducationTile (full width)
|
||||
4. Render `DetailPanel` alongside CommandPalette
|
||||
5. Adjust margin-top: `calc(var(--topbar-height) + var(--subnav-height))`
|
||||
6. Add `data-tile-id` attributes to tile wrappers
|
||||
|
||||
**Validation:** Layout renders correctly with new tile order. SubNav visible. Detail panel renders. No visual regressions. `npm run build`.
|
||||
|
||||
**Checkpoint:** Core infrastructure complete. Detail panel opens (with placeholder content), sub-nav works, new tile order in place.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Data Expansion
|
||||
|
||||
### Task 6: Expand Skills Data
|
||||
|
||||
**Modified file:** `src/data/skills.ts`
|
||||
**Depends on:** T1
|
||||
**Effort:** Medium
|
||||
|
||||
Expand from 5 → ~20 skills across 3 categories. Each skill retains the medication metaphor.
|
||||
|
||||
**Categories:**
|
||||
- **Technical (8):** Data Analysis, Python, SQL, Power BI, JavaScript/TypeScript, Excel, Algorithm Design, Data Pipelines
|
||||
- **Healthcare Domain (6):** Medicines Optimisation, Population Health, NICE TA Implementation, Health Economics, Clinical Pathways, Controlled Drugs
|
||||
- **Strategic & Leadership (7):** Budget Management, Stakeholder Engagement, Pharmaceutical Negotiation, Team Development, Change Management, Financial Modelling, Executive Communication
|
||||
|
||||
Each skill: `id`, `name`, `genericName`, `frequency`, `startYear`, `yearsOfExperience`, `status`, `proficiency`, `category`
|
||||
|
||||
**Source:** CV_v4.md Core Competencies section.
|
||||
|
||||
**Validation:** Types check. Existing CoreSkillsTile still renders (it will show all skills or first 5 depending on current implementation).
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Add KPI Stories
|
||||
|
||||
**Modified file:** `src/data/kpis.ts`
|
||||
**Depends on:** T1
|
||||
**Effort:** Small
|
||||
|
||||
Add `story` field to each of the 4 KPIs:
|
||||
|
||||
1. **£220M** — prescribing budget, forecasting models, ICB board accountability
|
||||
2. **£14.6M** — efficiency programme, data analysis identification, over-target by Oct 2025
|
||||
3. **9+ Years** — career span Aug 2016–present, progression narrative
|
||||
4. **1.2M** — population served, Norfolk & Waveney ICS scope
|
||||
|
||||
**Source:** CV_v4.md role descriptions.
|
||||
|
||||
**Validation:** Types check. Existing tile unaffected (story field is optional).
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Create Constellation Data
|
||||
|
||||
**New file:** `src/data/constellation.ts`
|
||||
**Depends on:** T6 (needs skill IDs)
|
||||
**Effort:** Medium
|
||||
|
||||
Define role-skill mapping for the D3 graph:
|
||||
|
||||
- 6 role nodes (Paydens → Tesco Duty → Tesco Manager → NHS HCD → NHS Deputy → NHS Interim)
|
||||
- Skill nodes (from expanded skills data)
|
||||
- Links connecting skills to roles with strength values
|
||||
- Colour assignments: role nodes get org colours, skill nodes get domain colours
|
||||
|
||||
**Validation:** Types check. Data importable.
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Create Education Extras
|
||||
|
||||
**New file:** `src/data/educationExtras.ts`
|
||||
**Depends on:** T1
|
||||
**Effort:** Small
|
||||
|
||||
Expanded detail for education entries:
|
||||
- MPharm: extracurriculars (Pharmacy Society President, Ultimate Frisbee VP, Alzheimer's Society), research project description
|
||||
- Mary Seacole: programme detail (change management, healthcare leadership, system-level thinking)
|
||||
- A-Levels: no extras needed
|
||||
|
||||
**Source:** CV_v4.md Education section.
|
||||
|
||||
**Validation:** Types check. Data importable.
|
||||
|
||||
**Checkpoint:** All data expanded and ready for consumption by tiles and detail renderers.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Tile Modifications
|
||||
|
||||
Tasks in this phase can be done in parallel where dependencies allow.
|
||||
|
||||
### Task 10: LatestResultsTile — Remove Flip, Add Panel
|
||||
|
||||
**Modified file:** `src/components/tiles/LatestResultsTile.tsx`
|
||||
**Modified file:** `src/index.css` (remove flip CSS if dedicated)
|
||||
**Depends on:** T2, T7
|
||||
**Effort:** Medium
|
||||
|
||||
**Changes:**
|
||||
1. Remove CSS perspective flip animation entirely
|
||||
2. Remove `.metric-card`, `.metric-card-inner`, `.metric-card-front`, `.metric-card-back` CSS classes
|
||||
3. Replace with clickable KPI cards:
|
||||
- Headline number at 28-32px, bold (700), coloured by variant
|
||||
- Label at 12px, weight 500
|
||||
- Sub-text at 10px, Geist Mono, tertiary
|
||||
4. Click → `openPanel({ type: 'kpi', kpi })`
|
||||
5. Hover: border colour shift + shadow deepens
|
||||
6. Keyboard: Enter/Space triggers panel
|
||||
|
||||
**Validation:** KPIs display with bigger numbers. Click opens detail panel (placeholder). No flip remnants. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 11: CoreSkillsTile — Full Width, Categorised
|
||||
|
||||
**Modified file:** `src/components/tiles/CoreSkillsTile.tsx`
|
||||
**Depends on:** T2, T6
|
||||
**Effort:** Large
|
||||
|
||||
**Changes:**
|
||||
1. Change from half-width to full-width (`full` prop on Card)
|
||||
2. Display skills grouped by category (Technical, Healthcare Domain, Strategic & Leadership)
|
||||
3. Category headers: thin divider line + label (styled like sidebar section dividers)
|
||||
4. Show top 3-4 skills per category on the dashboard
|
||||
5. "View all" button per category → `openPanel({ type: 'skills-all', category })`
|
||||
6. Individual skill click → `openPanel({ type: 'skill', skill })`
|
||||
7. Retain medication metaphor (frequency, status badge)
|
||||
8. Remove single-expand accordion for skills (replaced by panel interaction)
|
||||
|
||||
**Validation:** Skills display in 3 categories. View all opens panel. Individual click opens panel. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 12: ProjectsTile — Half Width, Card Grid
|
||||
|
||||
**Modified file:** `src/components/tiles/ProjectsTile.tsx`
|
||||
**Depends on:** T2
|
||||
**Effort:** Medium
|
||||
|
||||
**Changes:**
|
||||
1. Change from full-width to half-width (remove `full` prop)
|
||||
2. Position alongside LatestResultsTile in the grid (handled by T5 layout reorder)
|
||||
3. Compact card layout: status dot + name + year (right-aligned)
|
||||
4. Tech stack as small inline tags
|
||||
5. Click → `openPanel({ type: 'project', investigation })`
|
||||
6. Remove in-place expansion (replaced by panel)
|
||||
7. Hover: border shift, shadow deepens
|
||||
|
||||
**Validation:** Projects render in half-width alongside KPIs. Click opens panel. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 13: LastConsultationTile — Panel Trigger
|
||||
|
||||
**Modified file:** `src/components/tiles/LastConsultationTile.tsx`
|
||||
**Depends on:** T2
|
||||
**Effort:** Small
|
||||
|
||||
**Changes:**
|
||||
1. Add "View full record" button/link at the bottom
|
||||
2. Click → `openPanel({ type: 'consultation', consultation })`
|
||||
3. Make the tile header area clickable too
|
||||
4. Keep existing inline content (header info row, achievements preview)
|
||||
|
||||
**Validation:** Click opens panel. Existing content unchanged. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 14: CareerActivityTile — Panel Triggers, Hover
|
||||
|
||||
**Modified file:** `src/components/tiles/CareerActivityTile.tsx`
|
||||
**Depends on:** T2
|
||||
**Effort:** Medium
|
||||
|
||||
**Changes:**
|
||||
1. Timeline items: click → `openPanel({ type: 'career-role', consultation })` (for role entries)
|
||||
2. Remove in-place accordion expansion (replaced by panel)
|
||||
3. Hover preview: items lift slightly on hover, show 1-2 lines of preview text
|
||||
4. Keep colour-coded dots and entry type styling
|
||||
5. Reserve space for CareerConstellation embed (Phase 5)
|
||||
|
||||
**Note:** Extended timeline back to school (2009) — add education entries (Highworth Grammar, UEA) to the timeline data if not already present.
|
||||
|
||||
**Validation:** Click opens panel for role items. Hover shows preview. No accordion. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 15: EducationTile — Richer Content, Panel
|
||||
|
||||
**Modified file:** `src/components/tiles/EducationTile.tsx`
|
||||
**Depends on:** T2, T9
|
||||
**Effort:** Small
|
||||
|
||||
**Changes:**
|
||||
1. Show richer inline content: research project score (75.1%), OSCE score (80%), A-level grades
|
||||
2. Each education entry clickable → `openPanel({ type: 'education', document })`
|
||||
3. Hover: border shift
|
||||
|
||||
**Validation:** Richer content visible. Click opens panel. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 16: PatientSummaryTile — Structured Presentation
|
||||
|
||||
**Modified file:** `src/components/tiles/PatientSummaryTile.tsx`
|
||||
**Depends on:** T5
|
||||
**Effort:** Small
|
||||
|
||||
**Changes:**
|
||||
1. Use full profile paragraph from CV_v4.md (verify `profile.ts` has complete text)
|
||||
2. Pull out key highlights as a visual strip (years of experience, population served, budget)
|
||||
3. Break up wall of text with hierarchy (bold key phrases, structured paragraphs)
|
||||
|
||||
**Validation:** Profile reads well, not a wall of text. Highlight strip visible. `npm run build`.
|
||||
|
||||
**Checkpoint:** All tiles modified. Dashboard shows new layout with panel triggers on all interactive elements. Detail panel opens with placeholder content for each type.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Detail Panel Renderers
|
||||
|
||||
### Task 17: KPIDetail Renderer
|
||||
|
||||
**New file:** `src/components/detail/KPIDetail.tsx`
|
||||
**Depends on:** T10
|
||||
**Effort:** Medium
|
||||
|
||||
**Content:**
|
||||
- Headline number (large, coloured by variant)
|
||||
- Context paragraph (from `kpi.story.context`)
|
||||
- "Your role" paragraph (from `kpi.story.role`)
|
||||
- Outcome bullets (from `kpi.story.outcomes`)
|
||||
- Period badge (from `kpi.story.period`)
|
||||
|
||||
**Wire into DetailPanel:** When `content.type === 'kpi'`, render `<KPIDetail kpi={content.kpi} />`.
|
||||
|
||||
**Validation:** Panel renders full KPI story. Content matches CV_v4.md. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 18: ConsultationDetail Renderer
|
||||
|
||||
**New file:** `src/components/detail/ConsultationDetail.tsx`
|
||||
**Depends on:** T13, T14
|
||||
**Effort:** Medium
|
||||
|
||||
**Content:**
|
||||
- Role title + organisation + dates
|
||||
- History paragraph (from `consultation.history`)
|
||||
- Achievement bullets (from `consultation.examination`)
|
||||
- Plan/outcomes (from `consultation.plan`)
|
||||
- Coded entries badges (from `consultation.codedEntries`)
|
||||
|
||||
**Validation:** Panel renders full role detail. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 19: ProjectDetail Renderer
|
||||
|
||||
**New file:** `src/components/detail/ProjectDetail.tsx`
|
||||
**Depends on:** T12
|
||||
**Effort:** Medium
|
||||
|
||||
**Content:**
|
||||
- Project name + year + status badge
|
||||
- Methodology description
|
||||
- Tech stack tags
|
||||
- Results bullets
|
||||
- External link button (if `investigation.link` exists)
|
||||
|
||||
**Validation:** Panel renders full project detail. External link works. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 20: SkillDetail + SkillsAllDetail Renderers
|
||||
|
||||
**New files:** `src/components/detail/SkillDetail.tsx`, `src/components/detail/SkillsAllDetail.tsx`
|
||||
**Depends on:** T11
|
||||
**Effort:** Medium
|
||||
|
||||
**SkillDetail:**
|
||||
- Skill name + frequency + status badge
|
||||
- Proficiency bar (visual)
|
||||
- Years of experience
|
||||
- "Used in" section: roles that used this skill (from constellation mapping, or hardcoded until T8 data available)
|
||||
|
||||
**SkillsAllDetail:**
|
||||
- Full categorised list grouped by Technical / Domain / Leadership
|
||||
- Each skill row clickable → switches panel to individual SkillDetail
|
||||
- Category headers matching tile styling
|
||||
|
||||
**Validation:** Both renderers work. Skill click within SkillsAll switches to SkillDetail. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 21: EducationDetail Renderer
|
||||
|
||||
**New file:** `src/components/detail/EducationDetail.tsx`
|
||||
**Depends on:** T15
|
||||
**Effort:** Small
|
||||
|
||||
**Content:**
|
||||
- Title + institution + dates + classification
|
||||
- Research project description (if MPharm, from `educationExtras`)
|
||||
- Extracurricular activities (from `educationExtras`)
|
||||
- Programme detail (if Mary Seacole, from `educationExtras`)
|
||||
- Notes from document data
|
||||
|
||||
**Validation:** Panel renders education detail with extras. `npm run build`.
|
||||
|
||||
**Checkpoint:** All detail panel content renderers complete. Every interactive element in the dashboard opens its corresponding rich detail view.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Career Constellation (D3.js)
|
||||
|
||||
### Task 22: Install D3, Scaffold CareerConstellation
|
||||
|
||||
**Modified file:** `package.json` (add `d3`, `@types/d3`)
|
||||
**New file:** `src/components/CareerConstellation.tsx` (scaffold)
|
||||
**Depends on:** T8
|
||||
**Effort:** Small
|
||||
|
||||
- `npm install d3 @types/d3`
|
||||
- Create component with `useRef<SVGSVGElement>` for the SVG container
|
||||
- Render an empty SVG with viewBox, correct container sizing
|
||||
- Import constellation data
|
||||
|
||||
**Validation:** Component renders empty SVG. d3 imports resolve. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 23: D3 Force Graph Rendering
|
||||
|
||||
**Modified file:** `src/components/CareerConstellation.tsx`
|
||||
**Depends on:** T22
|
||||
**Effort:** Large
|
||||
|
||||
**Implement the force-directed graph:**
|
||||
- `d3.forceSimulation` with charge, link, x (chronological), y (centred), collision forces
|
||||
- Role nodes: 24px radius, org colour fill, white text
|
||||
- Skill nodes: 10px radius, domain colour-coded (clinical=green, technical=teal, leadership=amber)
|
||||
- Links: thin lines (1px), `var(--border)`, opacity 0.3
|
||||
- Container: full width of CareerActivityTile, 400px desktop / 300px tablet / 250px mobile
|
||||
- SVG with responsive viewBox
|
||||
- Subtle radial gradient background
|
||||
|
||||
**D3 integration pattern:**
|
||||
- D3 operates imperatively via `useEffect` on the SVG ref
|
||||
- React handles wrapper, D3 handles graph
|
||||
- No React state for node positions (performance)
|
||||
|
||||
**Validation:** Graph renders with nodes and links. Nodes positioned chronologically. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 24: Constellation Interactions → Detail Panel
|
||||
|
||||
**Modified file:** `src/components/CareerConstellation.tsx`
|
||||
**Depends on:** T23, T18, T20
|
||||
**Effort:** Medium
|
||||
|
||||
**Hover interactions:**
|
||||
- Hover role → connected skill nodes scale up, links brighten to `var(--accent)`, non-connected nodes fade to 0.15 opacity
|
||||
- Hover skill → all connected role nodes highlight, link paths illuminate
|
||||
- Tooltip with node name on hover
|
||||
|
||||
**Click interactions:**
|
||||
- Click role → `onRoleClick(id)` → opens ConsultationDetail panel
|
||||
- Click skill → `onSkillClick(id)` → opens SkillDetail panel
|
||||
|
||||
**Validation:** Hover highlighting works correctly. Click opens correct detail panels.
|
||||
|
||||
---
|
||||
|
||||
### Task 25: Constellation Accessibility
|
||||
|
||||
**Modified file:** `src/components/CareerConstellation.tsx`
|
||||
**Depends on:** T23
|
||||
**Effort:** Medium
|
||||
|
||||
- `role="img"` on SVG with `aria-label`
|
||||
- Screen-reader-only text description of graph structure
|
||||
- Keyboard navigation: Tab through role nodes, Enter to open detail
|
||||
- `prefers-reduced-motion`: disable force simulation animation, render static final layout
|
||||
- Focus indicators on nodes when keyboard-navigating
|
||||
|
||||
**Validation:** Screen reader describes graph. Keyboard nav works. Reduced motion shows static layout. `npm run build`.
|
||||
|
||||
**Checkpoint:** Career Constellation complete and integrated into CareerActivityTile. Interactive, accessible, visually impressive.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Login Refresh
|
||||
|
||||
### Task 26: LoginScreen Visual Restyle
|
||||
|
||||
**Modified file:** `src/components/LoginScreen.tsx`
|
||||
**Depends on:** None (independent)
|
||||
**Effort:** Medium
|
||||
|
||||
**Colour changes:**
|
||||
- `#005EB8` → `#0D6E6E` (shield icon bg, active field border, cursor, button)
|
||||
- `#004D9F` → `#0A8080` (button hover)
|
||||
- `#004494` → `#085858` (button pressed)
|
||||
- Background: `#1E293B` → keep or lighten to `#1A2B2A`
|
||||
|
||||
**Typography:**
|
||||
- Ensure Elvaro Grotesque is used (not DM Sans or system defaults)
|
||||
- Shadows should match three-tier system
|
||||
|
||||
**Validation:** Login looks cohesive with dashboard. Teal accents throughout. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 27: Username → a.recruiter + Connection Status
|
||||
|
||||
**Modified file:** `src/components/LoginScreen.tsx`
|
||||
**Depends on:** T26
|
||||
**Effort:** Medium
|
||||
|
||||
**Username change:**
|
||||
- Typed username: `a.recruiter` (not `A.CHARLWOOD`)
|
||||
- Password typing remains as dots
|
||||
|
||||
**Connection status indicator (below login button):**
|
||||
- New state: `ConnectionState = 'connecting' | 'connected'`
|
||||
- Initial: red dot + "Awaiting secure connection..."
|
||||
- After ~2000ms: green dot + "Secure connection established"
|
||||
- Dot: 6px circle, colour transitions with 300ms ease-out
|
||||
- Text: 10px, Geist Mono, tertiary colour
|
||||
- Login button disabled until BOTH `typingComplete` AND `connectionState === 'connected'`
|
||||
|
||||
**Validation:** Username types as `a.recruiter`. Connection dot transitions red→green. Button enables correctly.
|
||||
|
||||
---
|
||||
|
||||
### Task 28: Post-Login Loading State
|
||||
|
||||
**Modified file:** `src/components/LoginScreen.tsx`
|
||||
**Depends on:** T27
|
||||
**Effort:** Small
|
||||
|
||||
- On login click: `isLoading = true`
|
||||
- Card content replaces with: spinner + "Loading clinical records..."
|
||||
- Duration: ~600ms
|
||||
- Then calls `onComplete()` → dashboard materialises
|
||||
|
||||
**Validation:** Brief loading state visible between login click and dashboard. Feels purposeful, not slow.
|
||||
|
||||
---
|
||||
|
||||
### Task 29: TopBar Session Name Update
|
||||
|
||||
**Modified file:** `src/components/TopBar.tsx`
|
||||
**Depends on:** T28
|
||||
**Effort:** Tiny
|
||||
|
||||
- Change session display: `Dr. A.CHARLWOOD` → `A.RECRUITER`
|
||||
- Geist Mono font (should already be the case)
|
||||
|
||||
**Validation:** TopBar shows `A.RECRUITER`. `npm run build`.
|
||||
|
||||
**Checkpoint:** Login flow refreshed with teal aesthetic, recruiter narrative, connection status, and loading state.
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Polish & Integration
|
||||
|
||||
### Task 30: CommandPalette Updates
|
||||
|
||||
**Modified file:** `src/components/CommandPalette.tsx`, `src/lib/search.ts`
|
||||
**Depends on:** T17-T21
|
||||
**Effort:** Medium
|
||||
|
||||
- Update search index to include expanded skills (20 skills vs 5)
|
||||
- Add "View [X] detail" actions that open the detail panel directly
|
||||
- Ensure palette results link to panel opens, not just scroll-to-section
|
||||
- Update grouping if new content types warrant it
|
||||
|
||||
**Validation:** Search finds all 20 skills. Selecting a result opens the detail panel. `npm run build`.
|
||||
|
||||
---
|
||||
|
||||
### Task 31: Responsive Testing
|
||||
|
||||
**Modified file:** Various
|
||||
**Depends on:** T5, T2
|
||||
**Effort:** Medium
|
||||
|
||||
- DetailPanel: both `narrow` and `wide` become 100vw on mobile (<768px)
|
||||
- SubNav: test on tablet/mobile (may need horizontal scroll or hamburger)
|
||||
- Constellation: test at 300px/250px heights on smaller screens
|
||||
- Projects + KPIs: stack vertically on mobile (grid fallback)
|
||||
- Touch targets: all interactive elements ≥48px
|
||||
|
||||
**Validation:** Test at 375px, 768px, 1024px, 1440px breakpoints. No overflow, no hidden content.
|
||||
|
||||
---
|
||||
|
||||
### Task 32: prefers-reduced-motion Audit
|
||||
|
||||
**Modified file:** Various
|
||||
**Depends on:** All phases
|
||||
**Effort:** Small
|
||||
|
||||
Verify every new animation respects `prefers-reduced-motion: reduce`:
|
||||
- DetailPanel slide → instant appear
|
||||
- Backdrop fade → instant
|
||||
- SubNav underline transition → instant
|
||||
- Constellation force simulation → static layout
|
||||
- Connection status dot transition → instant
|
||||
- Post-login spinner → static indicator
|
||||
- Hover shadows/borders → can keep (non-motion)
|
||||
|
||||
**Validation:** Enable `prefers-reduced-motion` in browser. No animations visible except hover state changes.
|
||||
|
||||
---
|
||||
|
||||
### Task 33: Final Visual Review + Cleanup
|
||||
|
||||
**Depends on:** All phases
|
||||
**Effort:** Medium
|
||||
|
||||
- Visual review against `References/GPSystemconcept.html` (where applicable)
|
||||
- Content verification against `References/CV_v4.md`
|
||||
- Dead import cleanup
|
||||
- Unused CSS removal (old flip card styles)
|
||||
- Console warning check
|
||||
- `npm run typecheck` — zero errors
|
||||
- `npm run lint` — pass (pre-existing warning OK)
|
||||
- `npm run build` — clean
|
||||
|
||||
**Final checkpoint:** Complete depth enhancement. All features working, accessible, responsive, and polished.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Phase | Tasks | New Files | Modified Files | Effort |
|
||||
|-------|-------|-----------|----------------|--------|
|
||||
| 1. Core Infrastructure | T1-T5 | 3 | 3 | Medium-Large |
|
||||
| 2. Data Expansion | T6-T9 | 2 | 2 | Medium |
|
||||
| 3. Tile Modifications | T10-T16 | 0 | 7 | Large |
|
||||
| 4. Detail Renderers | T17-T21 | 6 | 1 | Medium |
|
||||
| 5. Career Constellation | T22-T25 | 1 | 1 | Large |
|
||||
| 6. Login Refresh | T26-T29 | 0 | 2 | Medium |
|
||||
| 7. Polish | T30-T33 | 0 | Several | Medium |
|
||||
| **Total** | **33 tasks** | **12 new files** | **~16 modified** | |
|
||||
|
||||
### Parallelisation Opportunities
|
||||
|
||||
- **T2, T3, T4** can be built in parallel (all depend only on T1)
|
||||
- **T6, T7, T9** can be built in parallel (all depend only on T1)
|
||||
- **T10-T15** can be built in parallel (all depend on T2 + their data task)
|
||||
- **T17-T21** can be built in parallel (each depends on its tile task)
|
||||
- **T26-T29** (login refresh) is independent of Phases 2-5, can run in parallel
|
||||
|
||||
### Critical Path
|
||||
|
||||
T1 → T2 → T5 → T10 → T17 (shortest path to first visible depth feature)
|
||||
T1 → T6 → T8 → T22 → T23 → T24 (path to constellation)
|
||||
|
||||
### New Dependency
|
||||
|
||||
```bash
|
||||
npm install d3 @types/d3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Step
|
||||
|
||||
Use `/sc:implement` or begin manual implementation following this workflow phase by phase.
|
||||
Reference in New Issue
Block a user