From 9fd8389c2692a7f7916421ea0e69d90da4f35044 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sun, 10 May 2026 17:49:05 +0000 Subject: [PATCH] =?UTF-8?q?fire-planner:=20UX=20review=20pass=202=20?= =?UTF-8?q?=E2=80=94=20health=20URL,=20Progress=20in=20shell,=20Gantt=20si?= =?UTF-8?q?ngle-year=20drag,=20Settings=20highlight,=20Dashboard=20empty?= =?UTF-8?q?=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- frontend/src/App.tsx | 6 ++- frontend/src/api/client.ts | 7 +++- frontend/src/components/EventGantt.tsx | 52 +++++++++++++++----------- frontend/src/components/Sidebar.tsx | 2 +- frontend/src/components/TabBar.tsx | 4 +- frontend/src/pages/Dashboard.tsx | 7 ++++ frontend/src/pages/ProgressPage.tsx | 7 +--- frontend/src/pages/SettingsTab.tsx | 2 +- 8 files changed, 53 insertions(+), 34 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6a680f8..62521fa 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,6 +2,8 @@ import { useQuery } from '@tanstack/react-query'; import { NavLink, Route, Routes, Link } from 'react-router-dom'; import { api } from '@/api/client'; +import { Navigate } from 'react-router-dom'; + import { Compare } from '@/pages/Compare'; import { Dashboard } from '@/pages/Dashboard'; import { CashflowTab } from '@/pages/CashflowTab'; @@ -53,9 +55,9 @@ export function App() { } /> } /> } /> - } /> }> } /> + } /> } /> } /> }> - } /> + } /> } /> } /> (path: string, init?: RequestInit): Promise { } export const api = { - health: () => request<{ status: string; queue_depth: number }>('/healthz'), + // /healthz is mounted at root by the FastAPI app (so k8s probes hit + // it without the /api prefix). Bypass API_BASE to match. + health: () => + fetch('/healthz') + .then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status))))) + .then((d) => d as { status: string; queue_depth: number }), recompute: (body?: Record) => request<{ status: string; depth: number }>('/recompute', { method: 'POST', diff --git a/frontend/src/components/EventGantt.tsx b/frontend/src/components/EventGantt.tsx index eedf839..42e7ea9 100644 --- a/frontend/src/components/EventGantt.tsx +++ b/frontend/src/components/EventGantt.tsx @@ -288,6 +288,12 @@ function Inner({ const fill = CATEGORY_FILL[ev.category] ?? CATEGORY_FILL.essential; const border = CATEGORY_BORDER[ev.category] ?? CATEGORY_BORDER.essential; + // Edge handles are only useful on multi-year ranges. For + // single-year (point) events the bar is too narrow to + // distinguish "edge" from "middle" so the whole bar + // moves and the user resizes via the popover. + const showHandles = w >= 24 && yearEnd > ev.year_start; + const handleW = Math.min(8, w / 4); return ( startDrag(e, ev, 'move')} /> - {/* Left handle */} - startDrag(e, ev, 'left')} - style={{ cursor: 'ew-resize' }} - /> - {/* Right handle */} - startDrag(e, ev, 'right')} - style={{ cursor: 'ew-resize' }} - /> + {showHandles && ( + <> + startDrag(e, ev, 'left')} + style={{ cursor: 'ew-resize' }} + /> + startDrag(e, ev, 'right')} + style={{ cursor: 'ew-resize' }} + /> + + )} Dashboard - {activeScenarioId != null && ( + {activeScenarioId != null && Number.isFinite(activeScenarioId) && ( Progress )} diff --git a/frontend/src/components/TabBar.tsx b/frontend/src/components/TabBar.tsx index 16b0edf..ee5d8ba 100644 --- a/frontend/src/components/TabBar.tsx +++ b/frontend/src/components/TabBar.tsx @@ -26,7 +26,9 @@ export function TabBar({ tabs }: { tabs: TabSpec[] }) { [ 'py-3 px-1 -mb-px border-b-2 whitespace-nowrap', isActive - ? 'border-slate-900 text-slate-900 font-medium' + ? t.stub + ? 'border-slate-300 text-slate-500 font-medium' + : 'border-slate-900 text-slate-900 font-medium' : t.stub ? 'border-transparent text-slate-300 hover:text-slate-500' : 'border-transparent text-slate-500 hover:text-slate-800', diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 0b2706c..9cebc12 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -66,6 +66,13 @@ export function Dashboard() {

Loading…

) : history.isError || !history.data ? (

History unavailable.

+ ) : history.data.points.length < 2 ? ( +

+ Only {history.data.points.length} snapshot in the window — the + chart needs at least two daily points to draw a line. The + Wealthfolio CronJob writes one snapshot per day, so this fills + in over time. +

) : ( )} diff --git a/frontend/src/pages/ProgressPage.tsx b/frontend/src/pages/ProgressPage.tsx index d1d6599..f7863c7 100644 --- a/frontend/src/pages/ProgressPage.tsx +++ b/frontend/src/pages/ProgressPage.tsx @@ -3,7 +3,7 @@ * fan, with a variance side panel. */ import { useQuery } from '@tanstack/react-query'; -import { Link, useParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { api } from '@/api/client'; import { ProgressOverlay } from '@/components/ProgressOverlay'; @@ -24,11 +24,6 @@ export function ProgressPage() { return (
-
- - ← Plan - -

Progress

diff --git a/frontend/src/pages/SettingsTab.tsx b/frontend/src/pages/SettingsTab.tsx index d47db96..4731d5d 100644 --- a/frontend/src/pages/SettingsTab.tsx +++ b/frontend/src/pages/SettingsTab.tsx @@ -9,7 +9,7 @@ export function SettingsTab() { const params = useParams<{ id: string }>(); const id = Number(params.id); const items = [ - { to: `/scenarios/${id}/settings`, label: 'Milestones', end: true }, + { to: `/scenarios/${id}/settings/milestones`, label: 'Milestones' }, { to: `/scenarios/${id}/settings/rates`, label: 'Rates' }, { to: `/scenarios/${id}/settings/notes`, label: 'Notes' }, { to: `/scenarios/${id}/settings/dividends`, label: 'Dividends', stub: true },