From d4e6076ea7abb554e46b974fe25a95ea848d883f Mon Sep 17 00:00:00 2001 From: Tobias Studer Date: Thu, 21 May 2026 16:39:38 +0200 Subject: [PATCH 1/5] feat(budget): workflow + detail redesign (spec 031) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /budget lands directly on the active fiscal year's detail view; the all-budgets list moves to /budget/history (reachable from the header "All budgets" link + breadcrumb). - Replace the 5 flat KPI tiles with a budget-health hero: multi-marker progress bar (planned/expected/actual against the annual ceiling), status pill (under / on-track / at-risk / over / no-data), narrative, and 4 stat tiles (Billed YTD / Actual YTD / Projected year-end / Variance YTD). - Add a past-month spotlight card for the most recent completed period with billed or running spend. - Period table: current-month accent + "Current" pill, distinct over / under / future treatments, dialed-down "live" sky-tinted Anthropic API row. - Drop the per-tool spending breakdown from the detail page (Reports owns that view). - Refactor BudgetDetailClient (~813 → ~245 lines) into composable sections: BudgetDetailHeader, BudgetHealthHero, PastMonthSpotlight, PeriodAllocationsTable, BilledCostDialog (mode="add"|"edit"), DeleteBilledCostDialog. All server actions unchanged. - Delete orphaned getPerToolSpend (no remaining callers; Reports has its own per-tool query). Spec, mockup, plan, and running implementation notes under specs/031-budget-workflow-and-redesign/. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../README.md | 84 ++ .../detail-mock.html | 793 +++++++++++++++++ .../implementation-notes.html | 519 +++++++++++ .../plan.html | 764 ++++++++++++++++ src/actions/budget.ts | 30 - src/app/budget/[id]/budget-detail-client.tsx | 813 ------------------ src/app/budget/[id]/page.tsx | 24 +- .../components/budget-detail-client.tsx | 249 ++++++ .../components/budget-detail-header.tsx | 75 ++ .../budget/components/budget-health-hero.tsx | 326 +++++++ .../components/dialogs/billed-cost-dialog.tsx | 152 ++++ .../components/dialogs/billed-cost-form.ts | 15 + .../dialogs/delete-billed-cost-dialog.tsx | 57 ++ src/app/budget/components/dialogs/index.ts | 2 + .../components/past-month-spotlight.tsx | 94 ++ .../components/period-allocations-table.tsx | 384 +++++++++ src/app/budget/components/stat-tile.tsx | 26 + src/app/budget/history/page.tsx | 73 ++ src/app/budget/page.tsx | 233 ++--- tests/e2e/budget-period-running-costs.spec.ts | 27 +- 20 files changed, 3698 insertions(+), 1042 deletions(-) create mode 100644 specs/031-budget-workflow-and-redesign/README.md create mode 100644 specs/031-budget-workflow-and-redesign/detail-mock.html create mode 100644 specs/031-budget-workflow-and-redesign/implementation-notes.html create mode 100644 specs/031-budget-workflow-and-redesign/plan.html delete mode 100644 src/app/budget/[id]/budget-detail-client.tsx create mode 100644 src/app/budget/components/budget-detail-client.tsx create mode 100644 src/app/budget/components/budget-detail-header.tsx create mode 100644 src/app/budget/components/budget-health-hero.tsx create mode 100644 src/app/budget/components/dialogs/billed-cost-dialog.tsx create mode 100644 src/app/budget/components/dialogs/billed-cost-form.ts create mode 100644 src/app/budget/components/dialogs/delete-billed-cost-dialog.tsx create mode 100644 src/app/budget/components/dialogs/index.ts create mode 100644 src/app/budget/components/past-month-spotlight.tsx create mode 100644 src/app/budget/components/period-allocations-table.tsx create mode 100644 src/app/budget/components/stat-tile.tsx create mode 100644 src/app/budget/history/page.tsx diff --git a/specs/031-budget-workflow-and-redesign/README.md b/specs/031-budget-workflow-and-redesign/README.md new file mode 100644 index 0000000..83dfd6b --- /dev/null +++ b/specs/031-budget-workflow-and-redesign/README.md @@ -0,0 +1,84 @@ +# Spec 031 — Budget Workflow & Detail Redesign + +Status: research / mockup phase +Created: 2026-05-21 +Owner: tobias.studer@unic.com + +## TL;DR + +Sidebar "Budget" should land directly on the current fiscal year's detail view, not a summary card that requires a second "View Details" click. The all-budgets list moves to `/budget/history`. The detail view itself gets a visual redesign — see [`detail-mock.html`](detail-mock.html). + +## The workflow problem + +Today, `src/app/budget/page.tsx` (lines 86–155) renders a Card summarising the active FY with six KPI tiles and a "View Details" button that links to `/budget/[activeBudget.id]`. The detail page (`src/app/budget/[id]/page.tsx`) re-fetches and re-renders the same totals with more depth. 95% of admin visits to "Budget" want the current year — they pay one extra click and a full second page render to get there. The "All Budgets" card below (line 169, only shown when `allBudgets.length > 1`) is the only thing the index page provides that the detail view doesn't, and it's the rarer use case. + +## Proposed workflow + +Sidebar "Budget" → `/budget` resolves to the **current active budget detail view**. No intermediate summary card, no second click. + +**Recommendation: fold, don't redirect.** Replace the contents of `src/app/budget/page.tsx` with the detail-view rendering, fetching `getActiveBudget()` and treating the result as the page's primary data. Keep `src/app/budget/[id]/page.tsx` as the route for *specific* (typically past / archived) budgets reached from history. The redirect alternative — `redirect('/budget/' + activeBudgetId)` from the index — is cleaner URL-wise but adds a server round-trip on every sidebar click and creates a non-deterministic "what is /budget?" answer. Folding pays for itself by making the landing instant; the duplication is one shared client component (`BudgetDetailClient`) imported from both routes, not two implementations. + +The all-budgets list (currently the second Card on `/budget`) moves to **`/budget/history`**. "History" is preferred over "all" because it signals *past + archived* — the active budget is right there on `/budget`, not on the list page. Surface it from the detail view in two places: + +- A small `All budgets →` link in the page header next to the FY title. +- A breadcrumb row above the H1: `Budget / FY 2026` where `Budget` deep-links to `/budget/history`. + +The "New Budget" button stays on the detail view header (admin-only, already gated). + +**Edge case — no active budget exists:** `/budget` renders an empty state with two CTAs: "Create Annual Budget" (admin-only) and "View past budgets →" pointing at `/budget/history`. Without the second link, an admin landing on an empty `/budget` would have no way to discover that archived budgets exist. + +### New URL structure + +```text +/budget → active FY detail view (was: summary card + View Details) +/budget/history → all budgets table (was: bottom Card on /budget) +/budget/[id] → a specific (past/archived) budget detail view (unchanged route, same client) +/budget/new → create-budget form (unchanged) +``` + +## Visual redesign of the detail view + +See [`detail-mock.html`](detail-mock.html) for the proposed visual treatment (dark-themed, matches the codebase styling from 028/029). The redesign is **visual only** — every server action, dialog, allocation editor, and CRUD path in `BudgetDetailClient` (`src/app/budget/[id]/budget-detail-client.tsx`) keeps its current behaviour. What's changing: + +- **Replace the five flat KPI cards** (lines 271–316: Total Budget / Allocated / Expected / Actual / Variance) with a single **budget-health hero**: planned, expected, and actual rendered as overlaid markers on one multi-segment progress bar against the annual ceiling, plus a status pill (on-track / at-risk / over-budget) and a one-sentence narrative ("Tracking $14k under expected as of mid-May"). +- **Collapse the conceptual overlap** of `Total Budget` / `Allocated` / `Unallocated` — the same number expressed three ways. Surface "Unallocated $X remaining" as a small annotation under the hero progress bar, not its own card. +- **Highlight the current month** in the periods table (line 338 onwards). Today there's no visual cue that "May" is *now* — all 12 rows look identical until one happens to be `bg-destructive/10`. +- **Strengthen the over-budget signal.** `bg-destructive/10` is the only cue today (line 359) — easy to miss against the muted table background. Add a left-edge accent bar plus a status chip in the variance column. +- **Keep the expandable period rows** (billed-cost line items + Anthropic API running breakdown) — they're the most useful part of the current page. Just style the indent and the "Anthropic API (running)" callout consistently with the new palette. + +What's **not** changing: the add/edit/delete billed-cost dialogs, allocation save flow, the `runningCosts` data shape, archived-budget read-only behaviour, or any server action. + +## Today vs proposed + +| Area | Today | Proposed | Why | +|---|---|---|---| +| Landing page | `/budget` shows a summary Card + "View Details" button | `/budget` renders the active FY detail view directly | One click less for the common case; the summary card duplicated data already on the detail page | +| All-budgets list | Second Card on `/budget`, only visible when >1 budget exists | Dedicated `/budget/history` page, reachable from header + breadcrumb | Frees the landing page for the active budget; discoverable regardless of count | +| KPI strip | 5 (detail) / 6 (index) flat tiles, conceptually overlapping | One budget-health hero with multi-marker progress bar, status pill, narrative | Tells the user the answer ("on track / not on track") in one glance; current tiles require mental arithmetic | +| Period highlighting | Single `bg-destructive/10` row tint when over budget | Current-month accent + stronger over-budget signal (left-edge bar + variance chip) | "When is now?" and "what's broken?" should be answerable in <1 second | + +## Out of scope + +- Data model: no schema changes. All redesign uses existing `annual_budgets`, `budget_periods`, `billed_costs`, `anthropic_workspace_costs`. +- Server actions: `getActiveBudget`, `getBudgets`, `getBudgetWithCosts`, `getPerToolSpend`, `getRunningCostsForPeriod`, `updateBudgetAllocations`, `createBilledCost`, `updateBilledCost`, `deleteBilledCost` — all unchanged. +- Validation, the new-budget creation flow (`/budget/new`), and the budget edit/archive actions in `BudgetListActions`. +- The Reports → Budget tab — that's spec 028 and lives at `/reports`. +- Per-tool spending breakdown — removed from the detail view entirely. It's already covered by the Reports → Budget page (spec 028); duplicating it here was noise. +- Per-tool budgets (still derived from `expected_spend_cents`; spec 028 open question still applies but is not blocked by this). + +## Implementation sketch + +- `src/app/budget/page.tsx` — replace the index rendering with the same logic currently in `src/app/budget/[id]/page.tsx`, sourced from `getActiveBudget()`. Empty state gets a "View past budgets →" link to `/budget/history`. +- `src/app/budget/[id]/page.tsx` — unchanged route handler; keeps serving specific budgets reached from history. The client component is shared. +- `src/app/budget/[id]/budget-detail-client.tsx` — refactor into composable sections: `BudgetHealthHero`, `PeriodAllocationsTable`. Each takes the same props it computes from today. Dialogs (`AddBilledCostDialog`, `EditBilledCostDialog`, `DeleteBilledCostDialog`) extracted into siblings to keep the parent under 300 lines. +- `src/app/budget/history/page.tsx` — new server component. Renders an `

Budget History

` + the existing `BudgetTable` from `src/app/budget/budget-table.tsx`. Reuses `getBudgets()`. +- `src/app/budget/budget-table.tsx` — unchanged. +- `src/components/app-sidebar.tsx` line 55 — unchanged (`Budget` still points at `/budget`). +- Breadcrumb component — either use a shared `` if one exists, or inline a two-segment `