single-year drag, Settings highlight, Dashboard empty state
Round 2 of agent-driven UX review.
- api status badge was always "unreachable" because client called
/api/healthz but FastAPI mounts /healthz at root (so k8s probes
hit it without the SPA prefix). Client now calls /healthz directly.
- Progress page rendered without the shell sidebar/tabs because its
route was sibling to ScenarioShell; moved it inside as a nested
route (also drops the redundant "← Plan" breadcrumb since the tab
bar handles that now).
- EventGantt: single-year (point) events no longer render edge handles
since the bar is too narrow to distinguish "edge grab" from "middle
grab" — for points the whole bar moves; resize via the popover.
Bars wider than 24px keep their edge handles.
- Settings sub-nav: Milestones now points at /settings/milestones
(consistent active highlight); /settings index redirects there.
- Dashboard "Last 12 months" chart shows an explainer when the
history has fewer than 2 snapshots instead of an empty axis.
- Stub tabs that are currently active get a slate-300 underline +
slate-500 text rather than the bold slate-900 — visually honest
about what's a placeholder.
Frontend typecheck/test/build green.
Round-1 fixes from the headless UI review:
Backend
- scenarios PATCH now allows config_json/name/description on cartesian
scenarios (so users can pin flex_rules + notes that recompute will
preserve). Core fields (jurisdiction/strategy/etc.) still blocked
because they're rebuilt on recompute. Existing test updated.
Frontend
- Sidebar Plans switcher: drop the kind=user filter so the switcher
surfaces all 120 cartesian scenarios that ship out of the box.
- Settings → Milestones now reachable at both /settings (index) and
/settings/milestones (explicit) — the agent navigated to the latter
and got a blank page.
- EventGantt background click capture: explicit pointerEvents="all" +
fillOpacity=0 so click-to-add reliably fires on empty regions
between bars.
- Plan tab stat badges moved out of the chart card into a dedicated
row above the fan — previously they overlapped the chart's title,
legend caption ("p10/p50/p..."), and right-side withdrawal axis.
- Stub tabs (Tax Analytics / Compare / Reports / Estate) and stub
Settings sub-pages (Dividends / Bonds / Tax / Metrics / Other) get
a "soon" badge + slate-300 styling so they're clearly placeholders.
- New "Portfolio depleted at this year" pill renders in the badge
row when the scrubbed year's NW is 0 — previously the badges
silently went to £0 with no UI cue.
- Test life-event from the smoke run cleaned up from prod DB.
246 pytest pass; mypy/ruff clean; frontend typecheck/test/build green.
events, interactive Visx Gantt + spending-profile chart
Charts are now the primary editor for life events. The Plan-tab body
re-orders to make charts ~80% of viewport real-estate; legacy form
sections are collapsed into a drawer.
Backend:
- alembic 0004: life_event.category enum (essential / discretionary /
not_spending). Defaults to essential so existing rows keep their
full spending impact.
- Simulator gains discretionary_outflows + flex_rules params. Tracks
per-path running ATH, applies the deepest applicable cut to
discretionary outflows when portfolio drops vs ATH (PLab-style flex
spending). Cut amount stays in the portfolio (refund pattern).
- New flex_spending module with FlexRule + applicable_cut +
cuts_per_year (vectorised). Sortable rules; "deepest cut wins" so
users specify cumulative cuts at each tier.
- New /scenarios/{id}/spending-profile endpoint returning per-year
base / essential / discretionary / flex_cut / total breakdown.
- SimulateRequest gains flex_rules + life_event.category roundtrip.
- 8 new tests; 246 total pytest pass; mypy + ruff clean.
Frontend (Visx + ECharts):
- Installed @visx/{scale,shape,group,axis,event,responsive,tooltip}
for native SVG drag interactions.
- New <SpendingProfileChart> — Visx stacked-area of base/essential/
discretionary with red flex-cut overlay, hover tooltip, click-to-
scrub-year.
- New <EventGantt> — interactive Visx Gantt:
* Click empty space → popover create at that year (default
essential spending event)
* Click a bar → inline edit popover (name, kind, range, £/y,
category) with delete button
* Drag bar middle → moves the whole event (year-resolution snap)
* Drag bar edges → resizes year_start / year_end
* All gestures persist via PATCH /life-events/{id}
- New <FlexRulesEditor> — list of {from_ath_pct, cut} tiers, save-on-
change to scenario.config_json.flex_rules.
- Plan-tab redesign: NW fan dominant top with floating stat badges
(Year/Age/NW/Δ NW/Spending/Eff. tax) over the chart; spending-
profile chart middle; Gantt bottom; flex-rules editor; legacy form
sections in a collapsed <details> drawer.
- Frontend typecheck + 7 vitest tests + production build all clean.