Skip to content

feat: Budget Panel page (P&L dashboard, breakdown charts, forecast)#890

Merged
Aureliolo merged 6 commits intomainfrom
feat/budget-panel-page
Mar 27, 2026
Merged

feat: Budget Panel page (P&L dashboard, breakdown charts, forecast)#890
Aureliolo merged 6 commits intomainfrom
feat/budget-panel-page

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Budget page (/budget) -- full P&L management dashboard replacing placeholder, with budget remaining gauge, spend burn area chart (h-80, actual + forecast + threshold reference lines), cost breakdown donut chart (first PieChart in codebase) with agent/dept/provider toggle, threshold alert banners (amber/red/critical), agent spending sortable table (5 columns), period selector (hourly/daily/weekly), category breakdown stacked bar (productive/coordination/system), and CFO optimization events feed
  • Budget forecast sub-page (/budget/forecast) -- projected total, confidence, avg daily spend metric cards, shared SpendBurnChart, and daily projections table with cumulative totals
  • Zustand store (stores/budget.ts) -- Promise.allSettled parallel fetch with graceful degradation, WebSocket event processing (budget + system channels), 30s polling, agent name/department mapping from listAgents()
  • Data hook (hooks/useBudgetData.ts) -- composition hook mirroring useDashboardData pattern with individual selectors, polling, and WebSocket bindings
  • Utilities (utils/budget.ts) -- 10 pure computation functions + 6 exported types with readonly fields, companion _VALUES arrays, extracted CategoryBucket interface
  • 9 sub-components in pages/budget/ -- BudgetSkeleton, PeriodSelector, ThresholdAlerts, BudgetGauge, SpendBurnChart, CostBreakdownChart, CategoryBreakdown, AgentSpendingTable, CfoActivityFeed
  • 9 Storybook stories with multiple variants each (data, loading, error, empty, threshold zones)
  • 163 tests across 14 test files (unit + fast-check property tests), all passing
  • Default display currency EUR throughout

Test plan

  • npm --prefix web run type-check -- 0 errors
  • npm --prefix web run lint -- 0 errors (12 pre-existing warnings in other files)
  • npm --prefix web run test -- 1280 tests passing across 109 files
  • npm --prefix web run dev -- verify budget page renders at /budget
  • npm --prefix web run storybook -- verify all 9 budget story files render
  • Visual: gauge color changes green -> amber -> red with increasing usage
  • Visual: period selector changes chart data on click
  • Visual: cost breakdown toggles between Agent/Dept/Provider
  • Visual: agent table sorts on column header click

Review coverage

Pre-reviewed by 4 agents (docs-consistency, frontend-reviewer, issue-resolution-verifier, type-design-analyzer). 7 findings addressed in follow-up commit.

Closes #781

🤖 Generated with Claude Code

Aureliolo and others added 2 commits March 27, 2026 20:23
#781)

Budget page replaces placeholder with a full P&L management dashboard:
- Budget remaining gauge (circular ProgressGauge with inverted color semantics)
- Spend burn area chart (h-80, actual + forecast, threshold reference lines)
- Cost breakdown donut chart (first PieChart in codebase) with agent/dept/provider toggle
- Threshold alert banners (amber/red/critical zones)
- Agent spending sortable table (5 columns, flexbox-based with aria-sort)
- Period selector (hourly/daily/weekly aggregation toggle)
- Category breakdown stacked bar (productive/coordination/system/uncategorized)
- CFO optimization events feed (reuses ActivityFeedItem from Dashboard)
- Budget forecast sub-page (/budget/forecast) with projections table
- Zustand store with WebSocket (budget, system channels) + 30s polling
- useBudgetData composition hook mirroring useDashboardData pattern
- Pure utility functions for all computations (budget.ts)
- BudgetSkeleton loading state, ErrorBoundary per section, EmptyState fallbacks
- 163 unit + property tests across 14 test files
- Storybook stories for all 9 sub-components + page (multiple variants each)
- Default display currency EUR throughout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-reviewed by 4 agents, 7 findings addressed:
- Fix wrong prop name `cols` -> `columns` on SkeletonTable
- Fix storybook import from internal path to @storybook/react
- Precompute cumulative values with useMemo (O(n) vs O(n^2))
- Extract CategoryBucket interface from repeated inline type
- Add AGGREGATION_PERIOD_VALUES and BREAKDOWN_DIMENSION_VALUES arrays
- Add readonly to AgentSpendingRow and BreakdownSlice fields
- Add JSDoc on ThresholdZone ordering and BreakdownSlice.color

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4c391b41-ab73-40e3-8876-2fcecaf071e4

📥 Commits

Reviewing files that changed from the base of the PR and between 9659eb8 and 19288b8.

📒 Files selected for processing (2)
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (3)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
web/src/**/__tests__/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Files:

  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/__tests__/**/*.{tsx,ts} : Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Applied to files:

  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to web/src/__tests__/**/*.{ts,js} : Dashboard testing: Vitest unit tests organized by feature under `web/src/__tests__/`. Use fast-check for property-based testing (`fc.assert` + `fc.property`).

Applied to files:

  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • web/src/pages/budget/AgentSpendingTable.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/budget/AgentSpendingTable.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create complex (>8 line) JSX inside .map() -- extract to a shared component

Applied to files:

  • web/src/pages/budget/AgentSpendingTable.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not build card-with-header layouts from scratch -- use <SectionCard>

Applied to files:

  • web/src/pages/budget/AgentSpendingTable.tsx
🔇 Additional comments (10)
web/src/pages/budget/AgentSpendingTable.tsx (5)

1-16: LGTM!

Clean imports and well-defined types. The SortKey union type covers all sortable columns, and AgentSpendingTableProps properly uses readonly for the rows array to prevent accidental mutation.


18-41: LGTM!

The COLUMNS configuration cleanly separates column metadata, and compareRows is a well-structured pure function with exhaustive case handling. The sort direction logic correctly negates for descending order.


43-69: LGTM!

The extracted ColumnHeader component addresses the map-complexity guideline. Accessibility is well-handled with aria-sort, focus-visible ring, and aria-hidden on decorative icons.


71-94: LGTM!

The SpendingRow component properly formats each cell type: currency formatting for costs, percentage with one decimal for budget share, and plain number for task count. The semantic text color classes provide appropriate visual hierarchy.


96-141: LGTM!

The main component properly addresses all previous review feedback: non-nullable sortKey, smart default sort direction (ascending for text, descending for numeric), and clean composition using the extracted subcomponents. The memoization dependencies are correct.

web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx (5)

7-16: LGTM!

The makeRows helper creates deterministic test data with predictable ordering properties: descending totalCost (first row is most expensive), ascending agentName (alphabetical), and ascending taskCount. This enables reliable sort-order assertions.


18-48: LGTM!

Good coverage of basic rendering scenarios. The row count test now includes an explicit count assertion (line 38) addressing the previous review feedback about verifying exact row counts.


50-88: LGTM!

The formatting tests properly verify currency formatting with explicit currency code, percentage rounding behavior, and plain number rendering for task counts. Using formatCurrency directly in assertions ensures the test stays in sync with the actual formatter.


90-140: LGTM!

The sorting tests comprehensively verify both the aria-sort attribute state and actual DOM order after interactions, addressing the previous review feedback. The test at lines 106-108 correctly expects ['Agent C', 'Agent B', 'Agent A'] for ascending cost order since makeRows creates rows with descending costs.


142-155: LGTM!

The EUR default currency test correctly verifies that when no currency prop is provided, the component uses formatCurrency's default. This aligns with the PR objective that "Default display currency is EUR".


Walkthrough

Adds a complete Budget feature: a new useBudgetData hook (with polling and WebSocket bindings and exported UseBudgetDataReturn), a fully implemented useBudgetStore Zustand store with fetch/update actions, and a new web/src/utils/budget.ts utilities module. Introduces UI components and pages (BudgetPage, BudgetForecastPage, BudgetGauge, SpendBurnChart, CostBreakdownChart, CategoryBreakdown, AgentSpendingTable, PeriodSelector, ThresholdAlerts, CfoActivityFeed, BudgetSkeleton), Storybook stories for these pieces, and comprehensive Vitest/React Testing Library test suites for hooks, store logic, utils, and UI.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 40.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: Budget Panel page (P&L dashboard, breakdown charts, forecast)' clearly and concisely summarizes the main feature: a complete budget dashboard with multiple chart types.
Description check ✅ Passed The PR description provides comprehensive context covering the budget page implementation, components, store design, utilities, tests, and test plan—directly related to the changeset.
Linked Issues check ✅ Passed The changeset implements all primary requirements from issue #781: budget gauge, burn chart, cost breakdown toggle (agent/dept/provider), threshold alerts, agent spending table, period selector, category breakdown, CFO events feed, data store, hooks, utilities, components, stories, and 163 passing tests.
Out of Scope Changes check ✅ Passed All changes directly support the Budget Panel feature: UI components, store actions, data utilities, hooks, tests, and stories are in scope; CLAUDE.md documentation update is a minor supporting change.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 27, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 19288b8.
Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.

Scanned Files

None

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a comprehensive Budget management feature, including a main dashboard, forecasting tools, and real-time updates via WebSockets. It introduces a new Zustand store for state management, a custom useBudgetData hook, and several UI components for cost tracking and visualization. The feedback focuses on improving type safety in test files by avoiding "as never" assertions and suggests adopting more idiomatic functional programming patterns for data transformations within React hooks.

})

it('returns overview from store', () => {
const overview = { total_cost_usd: 42 } as never
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The type assertion as never is used here to bypass TypeScript's type checking. While this works, it reduces type safety and can make the test harder to understand and maintain. A better approach would be to use a more explicit type assertion like as any, or ideally, create a mock object that fully satisfies the OverviewMetrics type.

Suggested change
const overview = { total_cost_usd: 42 } as never
const overview = { total_cost_usd: 42 } as any

vi.mocked(listActivities).mockResolvedValue({ data: [], total: 0, offset: 0, limit: 30 })
vi.mocked(listAgents).mockResolvedValue({
data: [
{ id: 'a1', name: 'Alpha', department: 'Engineering' } as never,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to another test file, as never is used here to bypass type checking for a mock agent object. This reduces type safety. It's better to use a more explicit type assertion like as any or, ideally, create a mock that satisfies the Agent type.

Suggested change
{ id: 'a1', name: 'Alpha', department: 'Engineering' } as never,
{ id: 'a1', name: 'Alpha', department: 'Engineering' } as any,

Comment on lines +27 to +34
const cumulativeValues = useMemo(() => {
if (!forecast) return []
let running = 0
return forecast.daily_projections.map((p) => {
running += p.projected_spend_usd
return running
})
}, [forecast])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This useMemo hook calculates cumulativeValues by using a map function with a side effect (mutating the running variable). While this works and is a common pattern, it's not ideal from a functional programming perspective, as map is expected to be a pure function.

A more idiomatic and "pure" approach would be to use reduce to build the array of cumulative values. This would make the code's intent clearer and avoid side effects within the map callback.

Suggested change
const cumulativeValues = useMemo(() => {
if (!forecast) return []
let running = 0
return forecast.daily_projections.map((p) => {
running += p.projected_spend_usd
return running
})
}, [forecast])
const cumulativeValues = useMemo(() => {
if (!forecast) return [];
return forecast.daily_projections.reduce<number[]>((acc, p) => {
const lastValue = acc.length > 0 ? acc[acc.length - 1]! : 0;
acc.push(lastValue + p.projected_spend_usd);
return acc;
}, []);
}, [forecast]);

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 34

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx`:
- Around line 87-113: The tests for AgentSpendingTable currently only assert
header aria-sort states but not that the table rows actually reorder; update the
three specs (the ones using AgentSpendingTable, makeRows,
totalCostHeader/totalCostBtn and agentBtn) to also assert the rendered agent
sequence after initial render and after each click by querying the table body
rows (e.g., via screen.getAllByRole('row') or selecting agent name cells) and
comparing the visible agent names order to the expected sorted order so the
assertions verify real sorting behavior.

In `@web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx`:
- Around line 24-29: The shared mock defaultProps.onDimensionChange (used in the
CostBreakdownChart tests) can accumulate calls across tests; update the test
suite to either recreate the mock per test or reset it before each test by
adding a beforeEach that sets defaultProps.onDimensionChange = vi.fn() (or calls
vi.clearAllMocks()/vi.resetAllMocks()) so tests that assert calls against
onDimensionChange (and other suites) start with a fresh mock; reference
defaultProps and onDimensionChange in the CostBreakdownChart describe block when
applying the fix.

In `@web/src/__tests__/stores/budget.test.ts`:
- Around line 280-293: Add a test that verifies updateFromWsEvent triggers the
async overview refresh: mock getOverviewMetrics (or the fetchOverview path used
by useBudgetStore), call useBudgetStore.getState().updateFromWsEvent(...) with
an event whose event_type is 'budget.record_added', then await the async work
(e.g., via vi.waitFor) and assert that getOverviewMetrics (or the store's
fetchOverview) was called; reference updateFromWsEvent, fetchOverview (or
getOverviewMetrics) and useBudgetStore to locate the code under test.
- Around line 100-107: The test uses an object cast with `as never` to satisfy
`listAgents`'s response which hides missing Agent fields; replace this by
creating a properly typed mock agent and returning it from
`vi.mocked(listAgents).mockResolvedValue(...)` (e.g., add a `const mockAgent:
Agent = { ... }` or a factory `createMockAgent(): Agent` that fills required
properties, or use `Partial<Agent>` and cast to `Agent`), then use that
`mockAgent` in the `data` array so the mock matches the real `Agent` type
instead of using `as never`.

In `@web/src/__tests__/utils/budget.property.test.ts`:
- Around line 20-30: The costRecordArb generator currently excludes zero-cost
records by setting cost_usd: fc.double({ min: 0.01, ... }); update the
CostRecord arbitrary (costRecordArb) to allow zero by changing cost_usd to
fc.double({ min: 0, max: 100, noNaN: true }) so tests exercise zero-cost edge
cases (leave callCategoryArb and other fields unchanged).

In `@web/src/__tests__/utils/budget.test.ts`:
- Around line 352-365: The tests for daysUntilBudgetReset are time-dependent;
make them deterministic by freezing the system clock during the test(s): use
Jest's fake timers (jest.useFakeTimers / jest.setSystemTime or equivalent) to
set "now" to a stable date (e.g., the 5th of a month) before calling
daysUntilBudgetReset and then restore real timers after the test; update both
test cases (the positive-number check and the "reset later this month" scenario
that references futureDay) to run against the fixed date so the assertion never
becomes a no-op and reliably exercises daysUntilBudgetReset.

In `@web/src/hooks/useBudgetData.ts`:
- Line 20: The hook is subscribing to both 'budget' and 'system' channels via
the BUDGET_CHANNELS constant which causes system.* events to be passed into
stores.updateFromWsEvent (in web/src/stores/budget.ts) and appear as
ActivityItem entries; either restrict the subscription to only the budget
channel by changing BUDGET_CHANNELS to ['budget'] or add a pre-filter in the WS
event handler inside useBudgetData (check event.type or event.channel) to only
call updateFromWsEvent for budget-relevant event types (e.g., those starting
with 'budget.' or a whitelist of event names).
- Around line 68-71: The polling object returned by usePolling is recreated each
render causing the useEffect in useBudgetData (which depends on polling) to
repeatedly re-run; fix this by stabilizing the return from usePolling: wrap the
returned object in useMemo (e.g., useMemo(() => ({ active, error, start, stop
}), [active, error, start, stop])) so polling identity is stable, or
alternatively change usePolling to return only start and stop and update the
useEffect dependency to [start, stop]; ensure you update the symbols usePolling,
start, stop, active, and error accordingly.

In `@web/src/pages/budget/AgentSpendingTable.stories.tsx`:
- Around line 19-30: The Storybook meta for AgentSpendingTable is missing the
a11y test parameter; update the meta object (the const meta for
AgentSpendingTable) to include a parameters property with a11y.test set to one
of the allowed values ("error", "todo", or "off") so Storybook 10 enforces WCAG
checks for this story; ensure the new parameters key sits alongside
title/component/tags/decorators in the meta definition.

In `@web/src/pages/budget/AgentSpendingTable.tsx`:
- Around line 74-93: The column header buttons in AgentSpendingTable render
sortable headers (using col, handleSort, sortKey, sortDir and icons
ArrowUp/ArrowDown) but lack a visible keyboard focus indicator; update the
button className to include focus-visible ring styles (matching PeriodSelector)
so keyboard users see a focus outline (e.g., add the appropriate focus-visible
ring and offset classes to the existing cn(...) call) while preserving existing
hover/cursor behavior and accessibility attributes.
- Around line 47-54: handleSort currently always resets sort direction to 'asc'
when switching columns; change it so switching to numeric columns (identify by
SortKey values 'totalCost', 'budgetPercent', 'costPerTask') resets to 'desc'
while non-numeric columns reset to 'asc'. Update the logic inside handleSort to
check if the incoming key is one of those numeric keys and call
setSortDir('desc') for them (otherwise 'asc'), keeping the existing toggle
behavior when clicking the same column; reference handleSort, SortKey,
setSortDir and the specific keys totalCost, budgetPercent, costPerTask.

In `@web/src/pages/budget/BudgetGauge.stories.tsx`:
- Around line 4-15: The Storybook meta object for BudgetGauge (the constant
named meta) needs a parameters entry enforcing a11y checks; add parameters: {
a11y: { test: 'error' } } to the meta object next to
title/component/tags/decorators so Storybook 10 will run WCAG assertions for the
BudgetGauge stories (the object defined as meta and typed with Meta<typeof
BudgetGauge>).

In `@web/src/pages/budget/BudgetPage.stories.tsx`:
- Around line 92-104: The story meta for BudgetPage is missing Storybook 10
accessibility enforcement; update the exported meta object (const meta) for
BudgetPage to include a parameters key with a11y.test set (e.g., parameters: {
a11y: { test: 'error' } }) so the story enforces WCAG gating; modify the meta
that wraps BudgetPage (the object containing title/component/decorators) to add
this parameters configuration.
- Around line 65-89: The stories preload the store via setStoreState but the
real useBudgetData hook still runs live side effects (fetchBudgetData, polling
via fetchOverview, WebSocket connections) which overwrite fixtures and break
offline stories; modify the stories to prevent those effects by either mocking
useBudgetData in the story decorators (replace the real hook with a test stub
that returns the preloaded state and no-op functions) or add a story-only
flag/env (e.g., STORYBOOK_DISABLE_BUDGET_EFFECTS) that the hook checks to skip
fetchBudgetData, polling, and WS setup; target the useBudgetData implementation
and the story decorators that call setStoreState so stories use the mocked/no-op
behavior instead of live network effects.

In `@web/src/pages/budget/CategoryBreakdown.stories.tsx`:
- Around line 33-44: The story meta object (meta) for CategoryBreakdown is
missing Storybook a11y enforcement; update the meta to include a parameters key
with a11y.test set to the required value (e.g., "error" or "todo") so WCAG
checks are enforced for this story module, ensuring the parameters property is
added alongside title/component/tags/decorators in the meta declaration.

In `@web/src/pages/budget/CategoryBreakdown.tsx`:
- Around line 60-74: The JSX inside the CATEGORIES.map in CategoryBreakdown.tsx
is over the complexity limit—extract the repeated legend row into a small
presentational component (e.g., CategoryLegendRow or LegendRow) that accepts
props { cat, bucket, currency } (where bucket comes from ratio[cat.key]) and
returns the four spans wrapped in the same div with the className and key moved
to the mapped element; then replace the current inline map body (the
CATEGORIES.map callback) with a single self-closing component invocation passing
cat, bucket and currency and keep the formatCurrency and
bucket.percent.toFixed(1) usage inside the new component.
- Around line 24-35: The empty-state check in CategoryBreakdown uses isAllZero
which only inspects ratio.productive.cost, ratio.coordination.cost,
ratio.system.cost, and ratio.uncategorized.cost; change isAllZero to treat a
bucket with zero cost but nonzero count as non-empty (i.e., consider each
bucket's count from computeCategoryBreakdown as well as cost). Update isAllZero
to return true only when all buckets have cost === 0 AND count === 0
(referencing ratio.productive.count, ratio.coordination.count,
ratio.system.count, ratio.uncategorized.count), so CategoryBreakdown's empty
detection reflects presence of zero-cost records.

In `@web/src/pages/budget/CfoActivityFeed.stories.tsx`:
- Around line 54-67: The story meta for CfoActivityFeed is missing the Storybook
accessibility parameter; update the meta object (the constant named meta for
CfoActivityFeed) to include parameters: { a11y: { test: 'error' } } so that
accessibility checks run as errors for this story. Ensure the parameters entry
is added alongside title/component/tags/decorators in the existing meta object.

In `@web/src/pages/budget/CostBreakdownChart.stories.tsx`:
- Around line 56-67: The Storybook meta for CostBreakdownChart currently lacks
accessibility and action logging configuration; update the exported meta object
(symbol: meta) for the CostBreakdownChart story to include a parameters field
with a11y.test set to "error" (or "todo"/"off" per guidelines) and add
actions/configuration for interaction logging (e.g., parameters.actions) so
accessibility checks run and user interactions are logged during development;
modify the meta object near the Meta<typeof CostBreakdownChart> declaration to
include these parameters while keeping existing title, component, tags, and
decorators.
- Around line 72-97: The stories ByAgent, ByDepartment, and ByProvider declare
args.breakdown but their custom render ignores those args and uses
DIMENSION_DATA, which makes Controls misleading; either remove the static
breakdown entries from args for these stories, or change the render to accept
story args and pass args.breakdown into InteractiveBreakdown (i.e., have render
receive args and call <InteractiveBreakdown initialDimension="..."
breakdown={args.breakdown}/>), ensuring InteractiveBreakdown uses the passed
breakdown prop instead of always reading DIMENSION_DATA.

In `@web/src/pages/budget/CostBreakdownChart.tsx`:
- Around line 71-93: The dimension toggle buttons rendered from
DIMENSION_OPTIONS (inside the map that uses dimension, deptDisabled, and
onDimensionChange) need a visible keyboard focus style; update the button
className to include a focus-visible utility (e.g., focus-visible:outline or
focus-visible:ring with appropriate color/offset) so when a button receives
keyboard focus it shows a clear visible indicator even when not active or
disabled, while retaining the existing active/disabled styles and not changing
the onClick/aria-checked/role behavior.
- Around line 25-38: DonutTooltipContent currently formats cost with
formatCurrency(slice.cost) and ignores the currency prop, causing inconsistency
with the legend; update DonutTooltipContent to accept a currency prop (e.g.,
function DonutTooltipContent({ active, payload, currency }) and type it
accordingly) and call formatCurrency(slice.cost, currency), then update every
place that renders DonutTooltipContent to pass the currency value (the same
currency used by the legend) so tooltip and legend use the same currency.
- Around line 47-59: The computed arrays legendSlices and chartData are
recalculated on every render (e.g., tooltip hovers); wrap both computations in
React.useMemo to memoize them. Replace the inline legendSlices and chartData
logic with useMemo blocks that compute the same values (use the existing
MAX_LEGEND_SLICES, breakdown, and any other inputs used in the computation) and
supply a dependency array including breakdown (and any props or constants used).
Ensure the rest of the component (where chartData is consumed) uses the memoized
variables.

In `@web/src/pages/budget/PeriodSelector.stories.tsx`:
- Around line 20-39: Replace the no-op onChange handlers in the Hourly, Daily,
and Weekly Story objects with Storybook action(...) calls so interactions are
logged (update the args.onChange for Hourly, Daily, Weekly to use
action('onChange') from 'storybook/actions'); additionally add
parameters.a11y.test: "error" to each story (or to the default export) to
enforce WCAG checks; ensure you import action from 'storybook/actions' and
reference the story names Hourly, Daily, Weekly when applying these changes.

In `@web/src/pages/budget/PeriodSelector.tsx`:
- Around line 29-34: The radio button styles in PeriodSelector use the className
constructed via cn(...) but lack a keyboard-visible focus state; update the
className passed to the element (the same expression where value ===
period.value is checked) to include focus-visible utilities (e.g. remove default
outline and add a visible ring like focus:outline-none and focus-visible:ring-2
focus-visible:ring-accent with an appropriate focus-visible:ring-offset) so
keyboard users see a clear focus indicator when tabbing to the radio buttons.

In `@web/src/pages/budget/SpendBurnChart.tsx`:
- Around line 46-48: getTodayLabel() currently uses the browser local Date which
can misalign the "Today" reference against trendData timestamps in UTC or
another timezone; change getTodayLabel (and any similar logic around the chart
reference line at lines ~202-213) to compute "today" from the trendData timezone
by deriving a date from the trendData timestamps (e.g., take the most recent
trendData timestamp or the timestamps' UTC date, normalize to that
timezone/start-of-day, then format with toLocaleDateString('en-US', ...) for the
label) so the reference line matches the chart data points (update usages of
getTodayLabel and the code that decides whether to render the "Today" line
accordingly).
- Around line 75-92: The tooltip is formatting values without the selected
currency; update ChartTooltipContent to accept a currency prop (e.g., add
currency: string to its props) and use it when calling
formatCurrency(entry.value, currency) so the tooltip matches the rest of the
chart; then pass the current currency from the parent where Tooltip is rendered
(the component that renders <Tooltip content={...}>), ensuring the Tooltip usage
supplies currency into ChartTooltipContent via closure or render prop so
formatCurrency receives the correct currency everywhere.

In `@web/src/pages/budget/ThresholdAlerts.stories.tsx`:
- Around line 33-44: The story meta for ThresholdAlerts is missing Storybook
a11y enforcement; update the meta object (the const named meta) to include
parameters.a11y.test (e.g., add parameters: { a11y: { test: true } }) so these
stories participate in the WCAG gate, keeping the existing
title/component/tags/decorators intact.

In `@web/src/pages/budget/ThresholdAlerts.tsx`:
- Around line 17-26: The alert copy and displayed percent should reflect the raw
budget usage and correct zone semantics: stop rounding
overview.budget_used_percent before deciding/displaying (use the raw value from
overview.budget_used_percent in the message) and update the amber branch text in
ThresholdAlerts (the zone check using zone === 'amber') to indicate that the
warning threshold has been crossed (e.g., "warning threshold reached" rather
than "approaching"); update any corresponding expectations in
ThresholdAlerts.test.tsx to match the new unrounded value and revised amber
copy.

In `@web/src/pages/BudgetForecastPage.tsx`:
- Around line 17-23: The page currently destructures only overview,
budgetConfig, forecast, trends, and loading from useBudgetData() in
BudgetForecastPage and treats fetch failures as an empty state; update the
destructuring to also include error, wsConnected, and wsSetupError from
useBudgetData(), then modify the render logic (the forecast empty-state branch
and the area handling lines ~76-149) to surface these failure conditions (e.g.,
show an error/banner or the same stale-data UI used by BudgetPage) instead of
showing blank metric cards and “No forecast data”; ensure you reference and use
the error, wsConnected, and wsSetupError variables to decide when to display an
explicit error/stale-connection UI.
- Around line 118-137: The JSX inside the forecast.daily_projections.map is too
large—extract it into a small presentational component (e.g., ProjectionRow)
that takes props: point, cumulative, currency and budgetPct (or compute
budgetPct inside the component using cumulative and budgetConfig.total_monthly
passed as prop). Create ProjectionRow (or similar) and move the div with spans
into that component, reusing formatCurrency via prop or import; then replace the
map body in BudgetForecastPage with a single <ProjectionRow key={point.day}
point={point} cumulative={cumulativeValues[idx]} currency={currency}
budgetConfig={budgetConfig} /> to keep the .map callback minimal.

In `@web/src/stores/budget.ts`:
- Around line 164-167: setAggregationPeriod currently triggers
get().fetchTrends() without awaiting, leading to race conditions when users
switch periods quickly; update the store to cancel or ignore stale requests by
adding an in-flight tracking mechanism (e.g., store a current AbortController or
incremental requestId on the budget store) and have fetchTrends accept/observe
that token/id so it aborts/ignores previous responses, then call fetchTrends
with the new token from setAggregationPeriod (or await a debounced fetch) to
ensure only the latest period's results are applied. Ensure you reference and
update the symbols setAggregationPeriod and fetchTrends and add a store field
like currentFetchController or currentFetchId to manage cancellation/staleness.

In `@web/src/utils/budget.ts`:
- Around line 237-248: computeExhaustionDate currently hardcodes the 'en-US'
locale which breaks i18n; change the function signature (computeExhaustionDate)
to accept an optional locale parameter (e.g., locale?: string) or derive locale
from the environment (e.g., navigator.language) and pass that locale into
toLocaleDateString instead of the fixed 'en-US', keeping the existing formatting
options; ensure callers are updated or the parameter has a safe default so
existing behavior remains unchanged.
- Around line 284-297: The daysUntilBudgetReset function fails when resetDay
exceeds the target month's length; clamp resetDay to the month's actual last day
before computing differences: compute currentMonthLastDay = new Date(year, month
+ 1, 0).getDate() and use Math.min(resetDay, currentMonthLastDay) for the
same-month branch, and compute nextMonthLastDay = new Date(year, month + 2,
0).getDate() and use Math.min(resetDay, nextMonthLastDay) when constructing the
next reset date (referencing function daysUntilBudgetReset, variables resetDay,
now, year, month, and nextMonth).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ec17338f-d398-48ca-a2c2-bcc4d1b0b736

📥 Commits

Reviewing files that changed from the base of the PR and between 6cdcd8a and 3729e44.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (37)
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/__tests__/pages/budget/BudgetGauge.test.tsx
  • web/src/__tests__/pages/budget/BudgetSkeleton.test.tsx
  • web/src/__tests__/pages/budget/CategoryBreakdown.test.tsx
  • web/src/__tests__/pages/budget/CfoActivityFeed.test.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/__tests__/pages/budget/PeriodSelector.test.tsx
  • web/src/__tests__/pages/budget/SpendBurnChart.test.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/stores/budget.test.ts
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/__tests__/utils/budget.test.ts
  • web/src/hooks/useBudgetData.ts
  • web/src/pages/BudgetForecastPage.tsx
  • web/src/pages/BudgetPage.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/BudgetGauge.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/BudgetSkeleton.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/CfoActivityFeed.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/PeriodSelector.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/pages/budget/SpendBurnChart.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/stores/budget.ts
  • web/src/utils/budget.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Dashboard Test
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (go)
🧰 Additional context used
📓 Path-based instructions (4)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/__tests__/pages/budget/BudgetSkeleton.test.tsx
  • web/src/__tests__/pages/budget/BudgetGauge.test.tsx
  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/budget/SpendBurnChart.test.tsx
  • web/src/__tests__/pages/budget/CategoryBreakdown.test.tsx
  • web/src/pages/BudgetForecastPage.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/__tests__/pages/budget/PeriodSelector.test.tsx
  • web/src/pages/budget/CfoActivityFeed.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/PeriodSelector.tsx
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/BudgetGauge.tsx
  • web/src/pages/BudgetPage.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/__tests__/pages/budget/CfoActivityFeed.test.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/BudgetSkeleton.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/utils/budget.test.ts
  • web/src/pages/budget/SpendBurnChart.tsx
  • web/src/stores/budget.ts
  • web/src/__tests__/stores/budget.test.ts
  • web/src/hooks/useBudgetData.ts
  • web/src/utils/budget.ts
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/__tests__/pages/budget/BudgetSkeleton.test.tsx
  • web/src/__tests__/pages/budget/BudgetGauge.test.tsx
  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/budget/SpendBurnChart.test.tsx
  • web/src/__tests__/pages/budget/CategoryBreakdown.test.tsx
  • web/src/pages/BudgetForecastPage.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/__tests__/pages/budget/PeriodSelector.test.tsx
  • web/src/pages/budget/CfoActivityFeed.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/PeriodSelector.tsx
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/BudgetGauge.tsx
  • web/src/pages/BudgetPage.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/__tests__/pages/budget/CfoActivityFeed.test.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/BudgetSkeleton.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/utils/budget.test.ts
  • web/src/pages/budget/SpendBurnChart.tsx
  • web/src/stores/budget.ts
  • web/src/__tests__/stores/budget.test.ts
  • web/src/hooks/useBudgetData.ts
  • web/src/utils/budget.ts
web/src/**/__tests__/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Files:

  • web/src/__tests__/pages/budget/BudgetSkeleton.test.tsx
  • web/src/__tests__/pages/budget/BudgetGauge.test.tsx
  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/budget/SpendBurnChart.test.tsx
  • web/src/__tests__/pages/budget/CategoryBreakdown.test.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/__tests__/pages/budget/PeriodSelector.test.tsx
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/__tests__/pages/budget/CfoActivityFeed.test.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/utils/budget.test.ts
  • web/src/__tests__/stores/budget.test.ts
web/src/**/*.stories.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.stories.{tsx,ts}: Use 'storybook/test' (not '@storybook/test'), 'storybook/actions' (not '@storybook/addon-actions') in Storybook 10
Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)
Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Files:

  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to web/src/__tests__/**/*.{ts,js} : Dashboard testing: Vitest unit tests organized by feature under `web/src/__tests__/`. Use fast-check for property-based testing (`fc.assert` + `fc.property`).

Applied to files:

  • web/src/__tests__/pages/budget/BudgetSkeleton.test.tsx
  • web/src/__tests__/pages/budget/BudgetGauge.test.tsx
  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/budget/SpendBurnChart.test.tsx
  • web/src/__tests__/pages/budget/CategoryBreakdown.test.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/__tests__/pages/budget/PeriodSelector.test.tsx
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/__tests__/pages/budget/CfoActivityFeed.test.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/utils/budget.test.ts
  • web/src/__tests__/stores/budget.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/__tests__/**/*.{tsx,ts} : Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Applied to files:

  • web/src/__tests__/pages/budget/BudgetSkeleton.test.tsx
  • web/src/__tests__/pages/budget/BudgetGauge.test.tsx
  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/budget/SpendBurnChart.test.tsx
  • web/src/__tests__/pages/budget/CategoryBreakdown.test.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/__tests__/pages/budget/PeriodSelector.test.tsx
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/__tests__/pages/budget/CfoActivityFeed.test.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/utils/budget.test.ts
  • web/src/__tests__/stores/budget.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/__tests__/pages/budget/BudgetSkeleton.test.tsx
  • web/src/__tests__/pages/budget/SpendBurnChart.test.tsx
  • web/src/__tests__/pages/budget/CategoryBreakdown.test.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/CfoActivityFeed.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/BudgetGauge.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/__tests__/pages/budget/CfoActivityFeed.test.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/BudgetSkeleton.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/pages/budget/SpendBurnChart.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create metric displays with 'text-metric font-bold' -- use <MetricCard>

Applied to files:

  • web/src/__tests__/pages/budget/BudgetGauge.test.tsx
  • web/src/__tests__/pages/budget/SpendBurnChart.test.tsx
  • web/src/pages/BudgetForecastPage.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/pages/budget/BudgetGauge.tsx
  • web/src/pages/BudgetPage.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/pages/budget/BudgetSkeleton.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/pages/budget/SpendBurnChart.tsx
  • web/src/utils/budget.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10

Applied to files:

  • web/src/__tests__/pages/budget/BudgetGauge.test.tsx
  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/budget/SpendBurnChart.test.tsx
  • web/src/__tests__/pages/budget/CategoryBreakdown.test.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/__tests__/pages/budget/CfoActivityFeed.test.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/stores/budget.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Applied to files:

  • web/src/__tests__/pages/budget/BudgetGauge.test.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • web/src/pages/BudgetForecastPage.tsx
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/pages/BudgetPage.tsx
  • web/src/__tests__/utils/budget.test.ts
  • web/src/stores/budget.ts
  • web/src/utils/budget.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)

Applied to files:

  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/.storybook/**/*.{ts,tsx} : Use 'defineMain' from 'storybook/react-vite/node' and 'definePreview' from 'storybook/react-vite' in Storybook 10; include explicit 'framework' field

Applied to files:

  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not build card-with-header layouts from scratch -- use <SectionCard>

Applied to files:

  • web/src/pages/budget/CfoActivityFeed.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/pages/budget/BudgetSkeleton.tsx
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/pages/BudgetPage.tsx
  • web/src/__tests__/utils/budget.test.ts
  • web/src/stores/budget.ts
  • web/src/utils/budget.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not recreate status dots inline -- use <StatusBadge>

Applied to files:

  • web/src/pages/budget/BudgetGauge.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).

Applied to files:

  • web/src/utils/budget.ts
🔇 Additional comments (19)
web/src/utils/budget.ts (1)

1-78: Well-structured utility module with comprehensive type exports.

The module provides clean separation of concerns with:

  • Properly typed constants with companion _VALUES arrays for runtime iteration
  • Readonly interface fields preventing accidental mutation
  • CSS custom properties for donut colors (compliant with design system guidelines)
  • Clear JSDoc documentation for threshold zone ordering
web/src/pages/budget/PeriodSelector.tsx (1)

15-41: Good accessibility implementation with proper ARIA attributes.

The component correctly implements:

  • role="radiogroup" with aria-label on the container
  • role="radio" with aria-checked on each button
  • Proper type="button" to prevent form submission
web/src/__tests__/utils/budget.property.test.ts (1)

1-126: Solid property-based test coverage for budget utilities.

The test suite effectively validates invariants:

  • Non-negative metrics for agent spending
  • Percentage sums to ~100 for breakdowns
  • Threshold zone always returns valid value

The alertsArb construction ensuring proper threshold ordering is particularly well-designed.

web/src/pages/budget/AgentSpendingTable.tsx (1)

61-124: Well-implemented sortable table with proper accessibility.

Good implementation choices:

  • Uses existing UI primitives (SectionCard, EmptyState, StaggerGroup)
  • Proper aria-sort attributes on column headers
  • useMemo for sorted data prevents unnecessary re-computations
  • Correct use of [...rows].sort() to avoid mutating props
web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx (1)

1-130: Comprehensive test coverage for CostBreakdownChart.

The test suite covers all key behaviors:

  • Empty state rendering
  • Chart presence with data
  • Dimension radio group accessibility
  • Click handling and disabled state
  • Legend rendering including "Other" collapse
  • Currency formatting

Good use of data-testid for querying chart elements that lack semantic selectors.

web/src/pages/budget/BudgetSkeleton.tsx (1)

1-42: Looks good — clean, accessible skeleton composition.

Good reuse of shared UI skeleton primitives and responsive layout structure.

web/src/__tests__/hooks/useBudgetData.test.ts (1)

1-125: Solid hook test coverage and lifecycle assertions.

This suite exercises the critical integration points (store wiring, websocket bindings, polling lifecycle) well.

web/src/__tests__/pages/budget/CfoActivityFeed.test.tsx (1)

1-78: Great component test suite depth.

Nice mix of behavior checks and property-based validation for the max-visible constraint.

web/src/pages/budget/CfoActivityFeed.tsx (1)

1-38: Well-structured feed component with correct shared-card usage.

The empty-state/live-feed branching is clean and accessible.

web/src/__tests__/pages/budget/PeriodSelector.test.tsx (1)

1-72: Comprehensive selector behavior/a11y tests.

The suite validates both semantics and interaction paths thoroughly.

web/src/__tests__/pages/budget/CategoryBreakdown.test.tsx (1)

1-92: Good breadth on visual-state and formatting assertions.

The tests cover key rendering branches and data formatting behavior well.

web/src/__tests__/pages/budget/SpendBurnChart.test.tsx (1)

58-114: Good coverage around nullable forecast branches.

Covering both forecast={null} and days_until_exhausted: null should catch the most likely regressions in the stat-pill rendering logic.

web/src/__tests__/pages/BudgetPage.test.tsx (1)

46-72: Good use of a typed hook fixture.

Keeping the mock aligned with UseBudgetDataReturn makes this suite fail fast when the hook contract changes.

web/src/pages/BudgetPage.tsx (1)

48-79: Nice separation between data shaping and rendering.

Pushing the budget calculations into utils/budget.ts and memoizing the derived slices keeps this page component readable and the business logic easy to test.

web/src/pages/budget/SpendBurnChart.stories.tsx (1)

1-97: LGTM!

The Storybook file provides good coverage of the component's states: with/without forecast, near-budget threshold scenario, and empty state. Mock data is well-structured and the story variants align with the component's prop interface.

web/src/hooks/useBudgetData.ts (1)

41-56: Good use of individual selectors.

Using separate selectors for each store field is the correct pattern to minimize unnecessary re-renders when unrelated state changes.

web/src/pages/budget/SpendBurnChart.tsx (1)

134-249: LGTM!

The chart implementation correctly uses design tokens (var(--so-*)) throughout, properly handles the conditional forecast overlay, and includes helpful reference lines for budget thresholds. The gradient definitions and area styling follow good recharts patterns.

web/src/stores/budget.ts (1)

70-133: LGTM on the fetch strategy.

Good use of Promise.allSettled for resilience - critical dependencies (overview, budgetConfig) fail the operation while non-critical ones (forecast, costRecords, trends, activities, agent metadata) degrade gracefully. This provides a good user experience when some endpoints are temporarily unavailable.

web/src/__tests__/stores/budget.test.ts (1)

131-198: LGTM!

The fetchBudgetData test suite provides good coverage of the critical vs non-critical fetch distinction, properly testing that overview and budgetConfig failures set an error while forecast, costRecords, and listAgents failures degrade gracefully without surfacing errors.

Comment on lines +164 to +167
setAggregationPeriod: (period) => {
set({ aggregationPeriod: period })
get().fetchTrends()
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential race condition in setAggregationPeriod.

setAggregationPeriod calls fetchTrends() without awaiting it. If the user rapidly switches periods, multiple concurrent fetchTrends calls could resolve out of order, causing the displayed trends to not match the selected period.

Consider debouncing or tracking an in-flight request to cancel stale fetches.

Example fix using AbortController pattern
 interface BudgetState {
   // ...existing fields...
+  _trendsAbort: AbortController | null
   // ...
 }

 // In store:
+_trendsAbort: null,

 fetchTrends: async () => {
+  // Cancel any in-flight request
+  get()._trendsAbort?.abort()
+  const controller = new AbortController()
+  set({ _trendsAbort: controller })
+
   const { aggregationPeriod } = get()
   const apiPeriod = PERIOD_TO_API[aggregationPeriod]
   try {
-    const result = await getTrends(apiPeriod, 'spend')
+    const result = await getTrends(apiPeriod, 'spend', { signal: controller.signal })
+    if (controller.signal.aborted) return
     // ...rest of logic
   } catch (err) {
+    if (err instanceof DOMException && err.name === 'AbortError') return
     console.warn('Failed to fetch trends:', err)
   }
 },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/stores/budget.ts` around lines 164 - 167, setAggregationPeriod
currently triggers get().fetchTrends() without awaiting, leading to race
conditions when users switch periods quickly; update the store to cancel or
ignore stale requests by adding an in-flight tracking mechanism (e.g., store a
current AbortController or incremental requestId on the budget store) and have
fetchTrends accept/observe that token/id so it aborts/ignores previous
responses, then call fetchTrends with the new token from setAggregationPeriod
(or await a debounced fetch) to ensure only the latest period's results are
applied. Ensure you reference and update the symbols setAggregationPeriod and
fetchTrends and add a store field like currentFetchController or currentFetchId
to manage cancellation/staleness.

Comment on lines +237 to +248
export function computeExhaustionDate(
daysUntilExhausted: number | null,
): string | null {
if (daysUntilExhausted === null) return null
const date = new Date()
date.setDate(date.getDate() + daysUntilExhausted)
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Hardcoded locale may cause i18n issues.

computeExhaustionDate uses a hardcoded 'en-US' locale. If the application supports internationalization, consider accepting a locale parameter or using the user's browser locale.

♻️ Optional: Accept locale parameter
 export function computeExhaustionDate(
   daysUntilExhausted: number | null,
+  locale?: string,
 ): string | null {
   if (daysUntilExhausted === null) return null
   const date = new Date()
   date.setDate(date.getDate() + daysUntilExhausted)
-  return date.toLocaleDateString('en-US', {
+  return date.toLocaleDateString(locale ?? 'en-US', {
     month: 'short',
     day: 'numeric',
     year: 'numeric',
   })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/utils/budget.ts` around lines 237 - 248, computeExhaustionDate
currently hardcodes the 'en-US' locale which breaks i18n; change the function
signature (computeExhaustionDate) to accept an optional locale parameter (e.g.,
locale?: string) or derive locale from the environment (e.g.,
navigator.language) and pass that locale into toLocaleDateString instead of the
fixed 'en-US', keeping the existing formatting options; ensure callers are
updated or the parameter has a safe default so existing behavior remains
unchanged.

… Gemini

Bugs: fix tooltip currency mismatch in CostBreakdownChart and SpendBurnChart,
fix system channel events polluting budget activities, fix inconsistent
currency source in SpendBurnChart StatPill, fix threshold zone defaulting
to 'normal' when data missing.

Error handling: add logging for rejected Promise.allSettled results, replace
empty catch in fetchOverview with console.warn, use sanitizeForLog consistently,
clear stale trends on fetchTrends failure, add .catch() to unhandled promise,
add event context to WS error log, surface polling error in useBudgetData,
add NaN guards for confidence display.

Type safety: derive BudgetMetricCardData from Omit<MetricCardProps>, type
PERIOD_TO_API with satisfies Record, add satisfies to _VALUES arrays, clamp
resetDay to month length in daysUntilBudgetReset with input validation.

Tests: add BudgetForecastPage test suite, add tests for fetchOverview trigger
on WS events, setAggregationPeriod -> fetchTrends, loading:true during fetch,
silent error handling, and trends clearing. Make daysUntilBudgetReset tests
deterministic with fake timers. Expand computeBudgetMetricCards assertions.
Replace as-never casts with typed mocks. Add DOM order sort assertions. Use
userEvent instead of fireEvent. Allow zero-cost in property tests.

Accessibility: add focus-visible to PeriodSelector, CostBreakdownChart, and
AgentSpendingTable interactive elements. Add parameters.a11y.test to all 9
story files. Use storybook/actions in PeriodSelector stories.

Code quality: extract CategoryLegendRow and ProjectionRow components from
.map() bodies. Remove unused dailySummary/periodSummary state. Memoize
legendSlices. Fix BudgetPage.stories live effects. Fix misleading comments
and JSDoc. Update CLAUDE.md stores description.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (5)
web/src/pages/budget/CostBreakdownChart.tsx (1)

110-111: 🧹 Nitpick | 🔵 Trivial

Consider memoizing chart data array.

data={[...breakdown]} creates a new array reference on every render. While the previous review suggested memoizing this alongside legendSlices, it wasn't addressed. For consistency with the memoization approach:

⚡ Memoize chart data
   const legendSlices = useMemo(() => {
     // ... existing logic
   }, [breakdown])

+  const chartData = useMemo(() => [...breakdown], [breakdown])
+
   return (
     // ...
               <Pie
-                data={[...breakdown]}
+                data={chartData}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/budget/CostBreakdownChart.tsx` around lines 110 - 111, The Pie
chart is recreating the data array each render via data={[...breakdown]},
causing unnecessary re-renders; change this to use a memoized value (e.g.,
memoize the chartData alongside legendSlices) and pass that memoized variable
into the Pie component (reference Pie, breakdown, and legendSlices) so the Pie
receives a stable array reference unless breakdown actually changes.
web/src/pages/budget/AgentSpendingTable.tsx (1)

77-82: ⚠️ Potential issue | 🟡 Minor

Focus indicator lacks visible ring for keyboard navigation.

The focus-visible:outline-none removes the default outline, but focus-visible:text-foreground alone may not provide sufficient visual indication for keyboard users. Consider adding a visible ring:

♿ Add visible focus ring
 className={cn(
-  'flex items-center gap-1 text-[11px] font-semibold uppercase tracking-wider text-text-muted transition-colors focus-visible:outline-none focus-visible:text-foreground',
+  'flex items-center gap-1 text-[11px] font-semibold uppercase tracking-wider text-text-muted transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:text-foreground',
   col.sortable && 'cursor-pointer hover:text-foreground',
   col.width,
   col.key !== 'agentName' && 'justify-end',
 )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/budget/AgentSpendingTable.tsx` around lines 77 - 82, The header
cell's focus styling in AgentSpendingTable removes the default outline but
doesn't add a visible ring for keyboard users; update the className passed into
the cn(...) expression (the same one starting with 'flex items-center...') to
include accessible focus ring classes such as 'focus-visible:ring-2
focus-visible:ring-offset-2 focus-visible:ring-primary/60' (or your design
system's ring color) in addition to keeping focus-visible:outline-none so
keyboard focus shows a clear ring around sortable header cells.
web/src/pages/budget/BudgetPage.stories.tsx (1)

65-95: ⚠️ Potential issue | 🟠 Major

These stories still run the live budget hook side effects.

Overriding store actions on Lines 88-92 avoids API mutations, but BudgetPage still mounts the real useBudgetData(). Storybook will therefore start polling and try to open WebSocket connections for every story, which makes the fixtures nondeterministic and can surface stale/disconnected banners without a backend. Mock the hook for stories, or add a story-only switch that disables fetch/polling/WS setup.

Also applies to: 115-170

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/budget/BudgetPage.stories.tsx` around lines 65 - 95, The
stories still mount the real useBudgetData hook causing polling and WebSocket
side effects; update the stories to prevent live hook behavior by mocking
useBudgetData (or adding a story-only flag that disables polling/WS) and ensure
setStoreState stays as-is; specifically, replace or stub the real useBudgetData
used by BudgetPage so it returns deterministic no-op implementations for
fetchBudgetData, fetchOverview, fetchTrends, updateFromWsEvent and any
polling/WS setup, or add a prop/flag the hook checks to skip network/polling
logic when rendering stories.
web/src/pages/BudgetForecastPage.tsx (1)

170-175: ⚠️ Potential issue | 🟠 Major

Don't show the generic empty-state copy for failed or stale loads.

This branch still says projections will appear “once enough spending data is available” even when error or wsSetupError is already surfaced above. That still makes a backend failure/stale state look like a legitimate empty forecast. Gate this fallback behind a successful load and add a regression test for the error+no-forecast case.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/BudgetForecastPage.tsx` around lines 170 - 175, The EmptyState
fallback currently displays regardless of backend errors; update the render
logic in BudgetForecastPage so the EmptyState (component EmptyState) is only
shown when the load was successful and there is genuinely no forecast data —
i.e. guard the branch with checks for no error and no wsSetupError (and any
successful-load flag or forecast presence used in this file) instead of showing
it unconditionally; also add a regression test covering the scenario where error
or wsSetupError is true and no forecast exists to assert that the error UI is
shown and the generic empty-state copy is not rendered.
web/src/pages/budget/ThresholdAlerts.tsx (1)

17-25: ⚠️ Potential issue | 🟡 Minor

Use the raw usage percent in the alert text.

Math.round on Line 17 can show an amber state like 89.6% as 90%, which contradicts the selected zone and threshold message. Format the raw value instead of rounding across boundaries, and cover a decimal case in ThresholdAlerts.test.tsx.

🧮 Keep the displayed percentage stable across thresholds
-  const usedPct = Math.round(overview.budget_used_percent)
+  const usedPct = Math.floor(overview.budget_used_percent * 10) / 10
+  const usedPctLabel = Number.isInteger(usedPct) ? String(usedPct) : usedPct.toFixed(1)
 
   let message: string
   if (zone === 'amber') {
-    message = `Budget usage at ${usedPct}% -- warning threshold (${budgetConfig.alerts.warn_at}%) reached`
+    message = `Budget usage at ${usedPctLabel}% -- warning threshold (${budgetConfig.alerts.warn_at}%) reached`
   } else if (zone === 'red') {
-    message = `Budget usage at ${usedPct}% -- critical threshold (${budgetConfig.alerts.critical_at}%) reached`
+    message = `Budget usage at ${usedPctLabel}% -- critical threshold (${budgetConfig.alerts.critical_at}%) reached`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/budget/ThresholdAlerts.tsx` around lines 17 - 25, The alert
text uses a rounded value (usedPct via Math.round(overview.budget_used_percent))
which can make the displayed percentage cross threshold boundaries; change
ThresholdAlerts.tsx to use the raw usage percent (e.g., format
overview.budget_used_percent to a stable decimal string) when building the three
message branches (zone === 'amber', 'red', else) instead of Math.round, and
update ThresholdAlerts.test.tsx to include a case with a decimal percent (e.g.,
89.6) to assert the message matches the raw/ formatted value so the displayed
percent never contradicts the computed zone.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/__tests__/hooks/useBudgetData.test.ts`:
- Around line 107-112: The test name says it should set up both "budget and
system" channels but the assertion only checks for 'budget'; update the test in
useBudgetData.test (the it block that calls renderHook(() => useBudgetData()))
to assert that wsCall (from vi.mocked(useWebSocket).mock.calls[0]![0]) includes
both channel bindings — inspect wsCall.bindings.map(b => b.channel) (variable
channels) and either expect it toEqual(['budget','system']) if order matters or
use assertions like toContain('budget') and toContain('system'); alternatively,
if you only want to assert 'budget', rename the it to reflect that change.

In `@web/src/pages/budget/BudgetGauge.stories.tsx`:
- Around line 21-51: Add an additional Story to BudgetGauge.stories.tsx that
explicitly sets the currency prop (e.g., currency: 'USD') to demonstrate non-EUR
formatting; create a named export like "WithCurrency" (or similar) next to
Healthy/Warning/Critical/Exhausted and supply args including usedPercent,
budgetRemaining, daysUntilExhausted, and currency to validate the optional
currency behavior of the BudgetGauge component.

In `@web/src/pages/budget/PeriodSelector.tsx`:
- Around line 9-13: Replace the hardcoded PERIODS array with a derived array
built from the canonical AGGREGATION_PERIOD_VALUES (imported from
"@/utils/budget"): map over AGGREGATION_PERIOD_VALUES to produce objects of
shape { value: AggregationPeriod; label: string } (use the period value for
value and generate a human label, e.g., capitalize the period string or use a
small label-mapping helper if needed) so new periods added to
AGGREGATION_PERIOD_VALUES automatically appear in PERIODS; ensure the import of
AGGREGATION_PERIOD_VALUES and the AggregationPeriod type are present and the
resulting array type matches the original.

In `@web/src/pages/BudgetForecastPage.tsx`:
- Around line 17-37: The ProjectionRow component currently renders a four-column
row using div/span which is not accessible; replace its markup with semantic
table elements (wrap the list of projections in a <table> with <thead> and
<tbody>, change ProjectionRow to output a <tr> with <td> cells) and add a header
row using <th scope="col"> for each column to preserve header-to-cell
associations; do the same conversion for the other instance referenced (lines
149-167) so both projection lists use a <table>/<thead>/<tbody>/<tr>/<th>/<td>
structure and ensure any existing className/styling is preserved on the new
table, tr, th, and td elements.

In `@web/src/utils/budget.ts`:
- Around line 152-176: computeCostBreakdown currently assigns colors while
iterating the Map `groups`, which yields colors tied to Map iteration order and
can vary; instead, build `slices` without colors, sort them (the function
already returns sorted by cost), then iterate the sorted array and assign
`color` from `DONUT_COLORS` using the index (e.g., color = DONUT_COLORS[i %
DONUT_COLORS.length]) so the same ranked slice always gets the same color;
update references in the function (`groups`, `slices`, `DONUT_COLORS`,
`colorIdx`) accordingly and return the colored, sorted `slices`.

---

Duplicate comments:
In `@web/src/pages/budget/AgentSpendingTable.tsx`:
- Around line 77-82: The header cell's focus styling in AgentSpendingTable
removes the default outline but doesn't add a visible ring for keyboard users;
update the className passed into the cn(...) expression (the same one starting
with 'flex items-center...') to include accessible focus ring classes such as
'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary/60'
(or your design system's ring color) in addition to keeping
focus-visible:outline-none so keyboard focus shows a clear ring around sortable
header cells.

In `@web/src/pages/budget/BudgetPage.stories.tsx`:
- Around line 65-95: The stories still mount the real useBudgetData hook causing
polling and WebSocket side effects; update the stories to prevent live hook
behavior by mocking useBudgetData (or adding a story-only flag that disables
polling/WS) and ensure setStoreState stays as-is; specifically, replace or stub
the real useBudgetData used by BudgetPage so it returns deterministic no-op
implementations for fetchBudgetData, fetchOverview, fetchTrends,
updateFromWsEvent and any polling/WS setup, or add a prop/flag the hook checks
to skip network/polling logic when rendering stories.

In `@web/src/pages/budget/CostBreakdownChart.tsx`:
- Around line 110-111: The Pie chart is recreating the data array each render
via data={[...breakdown]}, causing unnecessary re-renders; change this to use a
memoized value (e.g., memoize the chartData alongside legendSlices) and pass
that memoized variable into the Pie component (reference Pie, breakdown, and
legendSlices) so the Pie receives a stable array reference unless breakdown
actually changes.

In `@web/src/pages/budget/ThresholdAlerts.tsx`:
- Around line 17-25: The alert text uses a rounded value (usedPct via
Math.round(overview.budget_used_percent)) which can make the displayed
percentage cross threshold boundaries; change ThresholdAlerts.tsx to use the raw
usage percent (e.g., format overview.budget_used_percent to a stable decimal
string) when building the three message branches (zone === 'amber', 'red', else)
instead of Math.round, and update ThresholdAlerts.test.tsx to include a case
with a decimal percent (e.g., 89.6) to assert the message matches the raw/
formatted value so the displayed percent never contradicts the computed zone.

In `@web/src/pages/BudgetForecastPage.tsx`:
- Around line 170-175: The EmptyState fallback currently displays regardless of
backend errors; update the render logic in BudgetForecastPage so the EmptyState
(component EmptyState) is only shown when the load was successful and there is
genuinely no forecast data — i.e. guard the branch with checks for no error and
no wsSetupError (and any successful-load flag or forecast presence used in this
file) instead of showing it unconditionally; also add a regression test covering
the scenario where error or wsSetupError is true and no forecast exists to
assert that the error UI is shown and the generic empty-state copy is not
rendered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0f3f964c-d31f-4f85-bc45-ef64f84d17bb

📥 Commits

Reviewing files that changed from the base of the PR and between 3729e44 and 1f306c8.

📒 Files selected for processing (29)
  • CLAUDE.md
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/stores/budget.test.ts
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/__tests__/utils/budget.test.ts
  • web/src/hooks/useBudgetData.ts
  • web/src/pages/BudgetForecastPage.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/PeriodSelector.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/pages/budget/SpendBurnChart.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/stores/budget.ts
  • web/src/utils/budget.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Dashboard Test
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/pages/BudgetForecastPage.tsx
  • web/src/pages/budget/PeriodSelector.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/hooks/useBudgetData.ts
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
  • web/src/pages/budget/SpendBurnChart.tsx
  • web/src/__tests__/stores/budget.test.ts
  • web/src/stores/budget.ts
  • web/src/utils/budget.ts
  • web/src/__tests__/utils/budget.test.ts
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/pages/BudgetForecastPage.tsx
  • web/src/pages/budget/PeriodSelector.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/hooks/useBudgetData.ts
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
  • web/src/pages/budget/SpendBurnChart.tsx
  • web/src/__tests__/stores/budget.test.ts
  • web/src/stores/budget.ts
  • web/src/utils/budget.ts
  • web/src/__tests__/utils/budget.test.ts
web/src/**/__tests__/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Files:

  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
  • web/src/__tests__/stores/budget.test.ts
  • web/src/__tests__/utils/budget.test.ts
web/src/**/*.stories.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.stories.{tsx,ts}: Use 'storybook/test' (not '@storybook/test'), 'storybook/actions' (not '@storybook/addon-actions') in Storybook 10
Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)
Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Files:

  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
🧠 Learnings (22)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • CLAUDE.md
  • web/src/pages/budget/PeriodSelector.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
  • web/src/pages/budget/SpendBurnChart.tsx
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to web/src/__tests__/**/*.{ts,js} : Dashboard testing: Vitest unit tests organized by feature under `web/src/__tests__/`. Use fast-check for property-based testing (`fc.assert` + `fc.property`).

Applied to files:

  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
  • web/src/__tests__/stores/budget.test.ts
  • web/src/__tests__/utils/budget.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/__tests__/**/*.{tsx,ts} : Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Applied to files:

  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
  • web/src/__tests__/stores/budget.test.ts
  • web/src/__tests__/utils/budget.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10

Applied to files:

  • web/src/__tests__/pages/BudgetPage.test.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
  • web/src/__tests__/stores/budget.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create metric displays with 'text-metric font-bold' -- use <MetricCard>

Applied to files:

  • web/src/pages/BudgetForecastPage.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
  • web/src/pages/budget/SpendBurnChart.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create complex (>8 line) JSX inside .map() -- extract to a shared component

Applied to files:

  • web/src/pages/BudgetForecastPage.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/pages/budget/CategoryBreakdown.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Applied to files:

  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)

Applied to files:

  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/.storybook/**/*.{ts,tsx} : Use 'defineMain' from 'storybook/react-vite/node' and 'definePreview' from 'storybook/react-vite' in Storybook 10; include explicit 'framework' field

Applied to files:

  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/PeriodSelector.stories.tsx
  • web/src/pages/budget/ThresholdAlerts.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.stories.tsx
  • web/src/pages/budget/CategoryBreakdown.stories.tsx
  • web/src/pages/budget/BudgetPage.stories.tsx
  • web/src/pages/budget/CfoActivityFeed.stories.tsx
  • web/src/pages/budget/SpendBurnChart.stories.tsx
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/stores/budget.ts
  • web/src/utils/budget.ts
  • web/src/__tests__/utils/budget.test.ts
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • web/src/__tests__/utils/budget.property.test.ts
  • web/src/stores/budget.ts
  • web/src/utils/budget.ts
  • web/src/__tests__/utils/budget.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not recreate status dots inline -- use <StatusBadge>

Applied to files:

  • web/src/pages/budget/ThresholdAlerts.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files

Applied to files:

  • web/src/pages/budget/ThresholdAlerts.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not use rgba() with hardcoded values -- use design token variables

Applied to files:

  • web/src/pages/budget/ThresholdAlerts.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not build card-with-header layouts from scratch -- use <SectionCard>

Applied to files:

  • web/src/pages/budget/CostBreakdownChart.tsx
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).

Applied to files:

  • web/src/utils/budget.ts
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • web/src/__tests__/utils/budget.test.ts
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • web/src/__tests__/utils/budget.test.ts
🪛 GitHub Check: Dashboard Lint
web/src/hooks/useBudgetData.ts

[warning] 66-66:
React Hook useEffect has a missing dependency: 'polling'. Either include it or remove the dependency array

🔇 Additional comments (41)
CLAUDE.md (1)

131-131: Store inventory update is clear and consistent.

The expanded web/src/stores/ description reads correctly and matches the broader budget/tasks/settings/providers additions in this PR scope.

web/src/pages/budget/BudgetGauge.stories.tsx (3)

1-2: LGTM!

The imports are correct. Type imports from @storybook/react follow the standard Storybook pattern.


4-16: LGTM! Past review concern already addressed.

The meta object is well-configured. Line 8 already includes parameters: { a11y: { test: 'error' } }, which addresses the past review comment requesting a11y enforcement. The decorator provides appropriate sizing, and the satisfies keyword ensures type safety.

Note: The past review comment requesting the a11y parameter is now outdated—the parameter is already present.


18-19: LGTM!

Standard Storybook export pattern and type alias.

web/src/utils/budget.ts (3)

232-243: Hardcoded locale may cause i18n issues.

computeExhaustionDate uses a hardcoded 'en-US' locale. If the application supports internationalization, consider accepting a locale parameter or using the user's browser locale.


283-302: LGTM — edge case for resetDay exceeding month length is now handled.

The function correctly clamps resetDay to the actual last day of both the current and next month using new Date(year, month + 1, 0).getDate() and new Date(year, month + 2, 0).getDate(). This addresses the previous concern about months with fewer days (e.g., resetDay=31 in February).


318-361: LGTM — metric card computation is well-structured.

The function handles nullable inputs gracefully, uses appropriate guards (Math.max(0, ...) for remaining percent, null checks for forecast), and provides sensible fallback text. Currency resolution via overview.currency ?? budgetConfig?.currency correctly prioritizes the runtime currency.

web/src/pages/budget/PeriodSelector.tsx (1)

29-34: LGTM — focus-visible styling now included.

The radio buttons correctly include focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1, ensuring keyboard users see a clear focus indicator.

web/src/pages/budget/PeriodSelector.stories.tsx (1)

1-41: LGTM — Storybook configuration follows best practices.

The file correctly uses action from 'storybook/actions' (per Storybook 10 conventions) and includes parameters: { a11y: { test: 'error' } } for WCAG compliance enforcement. All three period variants are covered with proper action logging.

web/src/__tests__/utils/budget.property.test.ts (2)

20-30: LGTM — zero-cost records are now covered.

The cost_usd arbitrary now uses min: 0, allowing tests to exercise zero-cost edge cases. This addresses the previous review feedback.


32-45: LGTM — alert thresholds are correctly generated with valid ordering.

The alertsArb properly sorts three random values and accumulates them to ensure warn_at < critical_at < hard_stop_at, producing valid threshold configurations for all property tests.

web/src/pages/budget/CategoryBreakdown.tsx (2)

24-29: LGTM — empty state detection now considers record counts.

The isAllZero function correctly checks the sum of all category count values, ensuring that datasets with zero-cost but categorized records are not incorrectly treated as empty.


31-49: LGTM — legend row extracted into reusable component.

CategoryLegendRow keeps the .map() body concise and under the complexity limit. Props are well-typed and the component is appropriately scoped to this module.

web/src/pages/budget/CategoryBreakdown.stories.tsx (1)

33-48: LGTM — story meta includes a11y enforcement.

The parameters: { a11y: { test: 'error' } } configuration ensures WCAG compliance checks run for this component. The four story variants provide good coverage of different ratio distributions.

web/src/pages/budget/CfoActivityFeed.stories.tsx (1)

54-68: LGTM — a11y configuration added.

The parameters: { a11y: { test: 'error' } } is now present, ensuring accessibility checks are enforced. The MemoryRouter decorator appropriately provides routing context for any internal links.

web/src/pages/budget/CostBreakdownChart.tsx (3)

26-40: LGTM — tooltip now respects the currency prop.

DonutTooltipContent accepts currency and passes it to formatCurrency, ensuring consistency with the legend formatting.


49-62: LGTM — legendSlices is now memoized.

The useMemo wrapper prevents recalculation on every render (including tooltip hover events), with breakdown as the appropriate dependency.


85-90: LGTM — focus-visible styling added to dimension toggle buttons.

The buttons now include focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1, ensuring keyboard accessibility.

web/src/pages/budget/SpendBurnChart.tsx (3)

46-48: Timezone mismatch for "Today" reference line remains unaddressed.

getTodayLabel() uses the browser's local date, but trendData timestamps may be in UTC. If they differ, the "Today" vertical line won't match any X-axis label.

Consider deriving "today" from the trend data's timezone context, or at minimum, document this as expected behavior.


75-93: LGTM — Tooltip now correctly uses currency prop.

The ChartTooltipContent component properly accepts and passes currency to formatCurrency, ensuring consistent currency formatting between the tooltip and the rest of the chart.


50-73: Clean forecast bridging implementation.

The buildChartData function correctly handles the transition between actual and projected data by copying the last actual value into the bridged projected point, ensuring visual continuity in the chart.

web/src/pages/budget/CostBreakdownChart.stories.tsx (1)

1-100: LGTM — Storybook setup follows guidelines.

The file correctly configures parameters.a11y.test: 'error' for WCAG compliance, uses design token colors (var(--so-*)), and provides good coverage of dimension states. The InteractiveBreakdown wrapper enables interactive dimension toggling within stories.

web/src/pages/budget/AgentSpendingTable.tsx (1)

43-59: Well-structured sorting implementation with proper memoization.

The sorting logic correctly uses useCallback for the handler and useMemo for the computed sorted array, avoiding unnecessary re-renders. The compareRows function handles all column types appropriately with locale-aware string comparison for agent names.

web/src/pages/budget/AgentSpendingTable.stories.tsx (1)

1-52: LGTM — Clean Storybook setup with proper accessibility configuration.

The stories file correctly configures parameters.a11y.test: 'error', provides good test data fixtures, and covers both populated and empty states. The satisfies Meta<typeof AgentSpendingTable> pattern ensures type safety.

web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx (1)

93-123: LGTM — Sorting tests now verify actual DOM order.

The tests correctly verify both aria-sort attributes and the actual rendered row sequence using getAllByText with regex patterns. This ensures the sorting implementation actually reorders rows, not just updates header state.

web/src/__tests__/pages/budget/CostBreakdownChart.test.tsx (1)

26-35: LGTM — Mock cleanup properly configured.

The beforeEach with vi.clearAllMocks() ensures the shared defaultProps.onDimensionChange mock doesn't accumulate calls across tests, addressing the isolation concern.

web/src/pages/budget/SpendBurnChart.stories.tsx (1)

1-98: LGTM — Comprehensive story coverage for SpendBurnChart.

The stories file provides excellent coverage of component states: with forecast overlay, without forecast, near-budget scenario with scaled values, and empty state. The NearBudget story effectively demonstrates threshold behavior. The a11y.test: 'error' parameter ensures accessibility checks run.

web/src/__tests__/utils/budget.test.ts (2)

352-376: LGTM — Deterministic time-dependent tests using fake timers.

The daysUntilBudgetReset tests now properly use vi.useFakeTimers() and vi.setSystemTime() to freeze the clock, making the assertions deterministic. This addresses the previous concern about time-dependent test flakiness.


51-121: Thorough coverage of computeAgentSpending edge cases.

The test suite covers all important scenarios: empty input, grouping by agent, unique task counting, budget percent calculation, zero-total safety, name mapping with fallback, and sort order verification. Well-structured with clear test names.

web/src/stores/budget.ts (5)

182-187: Race condition concern previously flagged.

The potential for out-of-order trend responses when rapidly switching periods was identified in a previous review. The current implementation doesn't use AbortController or request tracking, so this remains a known limitation.


72-149: Well-structured Promise.allSettled with appropriate error hierarchy.

The two-tier error handling correctly treats overview+config as critical (blocks rendering) while allowing forecast/records/trends/activities to fail gracefully. The sequential agent metadata fetch after the main parallel batch ensures critical data loads first without blocking on display-name resolution.

Logging non-critical failures via console.warn with sanitizeForLog provides debuggability without exposing sensitive data.


160-180: Correct weekly aggregation and error-clearing behavior.

The conditional aggregateWeekly application for weekly period is correct. Clearing trends to null on failure (line 177) prevents stale data from a previous period from misleading users—this is the right UX decision.


195-205: Correct event-to-activity conversion with conditional overview refresh.

The implementation properly:

  1. Converts WebSocket events to activity items via wsEventToActivityItem
  2. Triggers fetchOverview() only for budget.record_added events (keeping metrics fresh when new costs arrive)
  3. Logs conversion failures with context (event_type, channel) for debugging

The fire-and-forget fetchOverview() is appropriate here since real-time updates don't need to block the activity feed.


189-193: Clean activity prepend with proper cap.

The functional update pattern set((state) => ...) correctly creates a new array with the new item prepended and capped at MAX_BUDGET_ACTIVITIES. This maintains immutability as expected by React/Zustand.

web/src/hooks/useBudgetData.ts (3)

63-66: Dependency array correctly targets stable function references.

The ESLint warning about missing polling dependency is a false positive. The start and stop functions from usePolling are memoized with useCallback (see usePolling.ts lines 45-68), so depending on polling.start and polling.stop directly is correct and more precise than depending on the entire polling object. This avoids unnecessary effect re-runs while maintaining correctness.

If the lint rule is enforced in CI, you can suppress it with an inline comment:

  useEffect(() => {
    polling.start()
    return () => polling.stop()
+   // eslint-disable-next-line react-hooks/exhaustive-deps -- start/stop are stable refs from usePolling
  }, [polling.start, polling.stop])

38-56: Well-structured hook with granular Zustand selectors.

The individual field selectors (lines 40-51) correctly optimize re-renders by subscribing to specific state slices rather than the entire store. The mount-time fetch pattern using getState() (line 55) avoids stale closure issues.


68-81: WebSocket bindings correctly scoped to budget channel.

The channel subscription is properly limited to ['budget'], addressing the previous concern about system events polluting the activity feed. The memoization with empty deps is correct since BUDGET_CHANNELS is a module-level constant.

web/src/__tests__/stores/budget.test.ts (4)

100-142: Agent mock now provides complete type-safe data.

The mock now includes all required Agent fields with realistic values, properly addressing the previous concern about using as never type assertions. This ensures the test will catch type mismatches if the Agent interface changes.


164-238: Comprehensive coverage of Promise.allSettled failure modes.

Tests correctly verify the two-tier error handling strategy:

  • Critical failures (overview/config) set error state
  • Non-critical failures (forecast/records/agents) degrade gracefully with error remaining null

The pattern of setupSuccessfulFetches() followed by selective mock overrides is clean and maintainable.


280-297: Weekly aggregation test correctly verifies same-week merging.

The test uses March 23-24, 2026 (Monday-Tuesday of the same week) and asserts they merge into a single data point with summed value (3+7=10). This validates the aggregateWeekly utility integration.


356-368: Test correctly verifies async fetchOverview trigger.

The vi.waitFor pattern appropriately handles the fire-and-forget nature of fetchOverview() called from updateFromWsEvent. This test coverage was previously flagged as missing and is now properly implemented.

Comment on lines +107 to +112
it('sets up WebSocket with budget and system channels', () => {
renderHook(() => useBudgetData())
const wsCall = vi.mocked(useWebSocket).mock.calls[0]![0]
const channels = wsCall.bindings.map((b) => b.channel)
expect(channels).toEqual(['budget'])
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep this channel assertion aligned with the spec.

The case says “budget and system channels”, but Line 111 only checks budget. As written, it won't catch a missing system binding—or the test name is misleading.

🧪 Tighten the assertion
   const wsCall = vi.mocked(useWebSocket).mock.calls[0]![0]
   const channels = wsCall.bindings.map((b) => b.channel)
-  expect(channels).toEqual(['budget'])
+  expect(channels).toEqual(expect.arrayContaining(['budget', 'system']))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('sets up WebSocket with budget and system channels', () => {
renderHook(() => useBudgetData())
const wsCall = vi.mocked(useWebSocket).mock.calls[0]![0]
const channels = wsCall.bindings.map((b) => b.channel)
expect(channels).toEqual(['budget'])
})
it('sets up WebSocket with budget and system channels', () => {
renderHook(() => useBudgetData())
const wsCall = vi.mocked(useWebSocket).mock.calls[0]![0]
const channels = wsCall.bindings.map((b) => b.channel)
expect(channels).toEqual(expect.arrayContaining(['budget', 'system']))
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/hooks/useBudgetData.test.ts` around lines 107 - 112, The
test name says it should set up both "budget and system" channels but the
assertion only checks for 'budget'; update the test in useBudgetData.test (the
it block that calls renderHook(() => useBudgetData())) to assert that wsCall
(from vi.mocked(useWebSocket).mock.calls[0]![0]) includes both channel bindings
— inspect wsCall.bindings.map(b => b.channel) (variable channels) and either
expect it toEqual(['budget','system']) if order matters or use assertions like
toContain('budget') and toContain('system'); alternatively, if you only want to
assert 'budget', rename the it to reflect that change.

- Fix stale test name ("budget and system" -> "budget channel")
- Add WithCurrency story to BudgetGauge for USD formatting
- Derive PeriodSelector PERIODS from AGGREGATION_PERIOD_VALUES
- Convert BudgetForecastPage projections to semantic table (thead/tbody/th/td)
- Guard EmptyState: only show when no error (error banner takes precedence)
- Assign breakdown colors after sorting for deterministic color ranking
- Add focus ring (ring-2 ring-accent) to AgentSpendingTable sort headers
- Memoize Pie chart data array in CostBreakdownChart
- Use raw percentage with toFixed(1) in ThresholdAlerts instead of Math.round
- Add decimal-percent and semantic-table regression tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
web/src/pages/budget/AgentSpendingTable.tsx (1)

47-54: 🧹 Nitpick | 🔵 Trivial

Consider defaulting to descending sort for numeric columns.

When switching to a new numeric column (totalCost, budgetPercent, taskCount, costPerTask), users typically expect to see the highest values first. Currently, clicking a new column always resets to ascending order.

This is a minor UX improvement and entirely optional.

♻️ Optional: Default to descending for numeric columns
 const handleSort = useCallback((key: SortKey) => {
   if (sortKey === key) {
     setSortDir((d) => d === 'asc' ? 'desc' : 'asc')
   } else {
     setSortKey(key)
-    setSortDir('asc')
+    setSortDir(key === 'agentName' ? 'asc' : 'desc')
   }
 }, [sortKey])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/budget/AgentSpendingTable.tsx` around lines 47 - 54, The
handleSort callback currently resets to 'asc' whenever a new column is selected;
change it so that when the new SortKey is one of the numeric columns (totalCost,
budgetPercent, taskCount, costPerTask) it defaults setSortDir('desc') so highest
values show first, otherwise keep setSortDir('asc'). Update the logic inside
handleSort (which references sortKey, setSortKey, setSortDir and SortKey) to
detect those numeric keys and choose the initial direction accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@web/src/pages/budget/AgentSpendingTable.tsx`:
- Around line 47-54: The handleSort callback currently resets to 'asc' whenever
a new column is selected; change it so that when the new SortKey is one of the
numeric columns (totalCost, budgetPercent, taskCount, costPerTask) it defaults
setSortDir('desc') so highest values show first, otherwise keep
setSortDir('asc'). Update the logic inside handleSort (which references sortKey,
setSortKey, setSortDir and SortKey) to detect those numeric keys and choose the
initial direction accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 46cac507-88d0-44b2-be6b-92c197bf8404

📥 Commits

Reviewing files that changed from the base of the PR and between 1f306c8 and d11ae2a.

📒 Files selected for processing (10)
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/pages/BudgetForecastPage.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/pages/budget/PeriodSelector.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/utils/budget.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Dashboard Test
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/pages/BudgetForecastPage.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/pages/budget/PeriodSelector.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
  • web/src/utils/budget.ts
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/pages/BudgetForecastPage.tsx
  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/pages/budget/PeriodSelector.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
  • web/src/utils/budget.ts
web/src/**/__tests__/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Files:

  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
web/src/**/*.stories.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.stories.{tsx,ts}: Use 'storybook/test' (not '@storybook/test'), 'storybook/actions' (not '@storybook/addon-actions') in Storybook 10
Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)
Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Files:

  • web/src/pages/budget/BudgetGauge.stories.tsx
🧠 Learnings (16)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create metric displays with 'text-metric font-bold' -- use <MetricCard>

Applied to files:

  • web/src/pages/BudgetForecastPage.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create complex (>8 line) JSX inside .map() -- extract to a shared component

Applied to files:

  • web/src/pages/BudgetForecastPage.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/__tests__/**/*.{tsx,ts} : Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Applied to files:

  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to web/src/__tests__/**/*.{ts,js} : Dashboard testing: Vitest unit tests organized by feature under `web/src/__tests__/`. Use fast-check for property-based testing (`fc.assert` + `fc.property`).

Applied to files:

  • web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx
  • web/src/__tests__/hooks/useBudgetData.test.ts
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/budget/PeriodSelector.tsx
  • web/src/pages/budget/ThresholdAlerts.tsx
  • web/src/pages/budget/CostBreakdownChart.tsx
  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not use rgba() with hardcoded values -- use design token variables

Applied to files:

  • web/src/pages/budget/ThresholdAlerts.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not build card-with-header layouts from scratch -- use <SectionCard>

Applied to files:

  • web/src/pages/budget/CostBreakdownChart.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10

Applied to files:

  • web/src/pages/budget/BudgetGauge.stories.tsx
  • web/src/__tests__/pages/BudgetForecastPage.test.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance

Applied to files:

  • web/src/pages/budget/BudgetGauge.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)

Applied to files:

  • web/src/pages/budget/BudgetGauge.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/.storybook/**/*.{ts,tsx} : Use 'defineMain' from 'storybook/react-vite/node' and 'definePreview' from 'storybook/react-vite' in Storybook 10; include explicit 'framework' field

Applied to files:

  • web/src/pages/budget/BudgetGauge.stories.tsx
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • web/src/pages/budget/AgentSpendingTable.tsx
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)

Applied to files:

  • web/src/utils/budget.ts
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.

Applied to files:

  • web/src/utils/budget.ts
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).

Applied to files:

  • web/src/utils/budget.ts
🔇 Additional comments (21)
web/src/pages/budget/BudgetGauge.stories.tsx (2)

4-60: LGTM! Comprehensive coverage of gauge states and currency formatting.

The stories address all previous review findings (a11y enforcement and WithCurrency variant). The usedPercent values correctly map to the getHealthColor thresholds (Healthy → success, Warning → warning, Critical/Exhausted → danger, WithCurrency → accent). The daysUntilExhausted: null vs numeric distinction properly exercises the conditional exhaustion date rendering.


1-1: ⚠️ Potential issue | 🟠 Major

Update import to use the correct Storybook 10 react-vite package.

For Storybook 10 with the react-vite framework, Meta and StoryObj should be imported from @storybook/react-vite, not @storybook/react:

Import fix
-import type { Meta, StoryObj } from '@storybook/react'
+import type { Meta, StoryObj } from '@storybook/react-vite'
⛔ Skipped due to learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/.storybook/**/*.{ts,tsx} : Use 'defineMain' from 'storybook/react-vite/node' and 'definePreview' from 'storybook/react-vite' in Storybook 10; include explicit 'framework' field
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create metric displays with 'text-metric font-bold' -- use <MetricCard>
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not recreate status dots inline -- use <StatusBadge>
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : CSS side-effect imports need type declarations; Vite's '/// <reference types="vite/client" />' covers this in TS 6
web/src/__tests__/hooks/useBudgetData.test.ts (1)

1-135: LGTM!

The test suite is well-structured and provides good coverage of the useBudgetData hook lifecycle:

  • Mount behavior (fetchBudgetData called)
  • Store state propagation (loading, error, overview, aggregationPeriod)
  • WebSocket binding to the budget channel (correctly matches BUDGET_CHANNELS = ['budget'] from the hook)
  • Polling lifecycle (30s interval, start on mount, stop on unmount)

The mocking strategy using vi.hoisted for polling controls and resetting the Zustand store in beforeEach is clean and follows testing best practices.

web/src/pages/BudgetForecastPage.tsx (1)

1-182: LGTM!

The forecast page implementation is solid:

  • Properly destructures error handling fields (error, wsConnected, wsSetupError) from useBudgetData
  • Uses semantic table markup (<table>, <thead>, <tbody>, <th scope="col">, <td>) for accessibility
  • Extracts ProjectionRow to keep the .map() callback minimal
  • Uses design system components (MetricCard, SectionCard, EmptyState, ErrorBoundary)
  • Correctly guards empty state behind !error to avoid showing "No forecast data" when there's actually a fetch failure
  • Handles edge cases well: Number.isFinite() for confidence, != null for days_until_exhausted
web/src/pages/budget/PeriodSelector.tsx (1)

1-40: LGTM!

Clean implementation that addresses previous feedback:

  • Derives PERIODS from the canonical AGGREGATION_PERIOD_VALUES to avoid drift
  • Includes focus-visible ring for keyboard accessibility
  • Proper accessibility attributes (role="radiogroup", aria-label, role="radio", aria-checked)
  • Uses design tokens throughout
web/src/pages/budget/ThresholdAlerts.tsx (1)

1-49: LGTM!

Well-implemented threshold alert component:

  • Correctly returns null for normal zone or missing data
  • Uses one-decimal fixed formatting that preserves precision without rounding across zone boundaries
  • Alert messages accurately reflect threshold states (amber = "warning threshold reached", not "approaching")
  • Proper accessibility with role="alert" and aria-hidden="true" on the icon
  • Uses semantic design tokens for warning/danger states
  • animate-pulse reserved for critical severity
web/src/__tests__/pages/budget/ThresholdAlerts.test.tsx (1)

1-136: LGTM!

Comprehensive test coverage for ThresholdAlerts:

  • Covers all null/empty rendering cases (normal zone, null budgetConfig, null overview)
  • Validates zone-specific messaging for amber, red, and critical
  • Verifies animate-pulse is exclusive to critical zone
  • Confirms role="alert" for accessibility
  • Includes decimal percentage regression test (89.6% without rounding)
  • Uses parameterized tests for DRY coverage of all alert zones
web/src/pages/budget/CostBreakdownChart.tsx (1)

1-147: LGTM!

The cost breakdown chart implementation addresses all previous feedback:

  • DonutTooltipContent now accepts and uses the currency prop consistently with the legend
  • legendSlices and chartData are properly memoized to avoid recalculations on re-render
  • Dimension toggle buttons include focus-visible ring for keyboard accessibility
  • Uses design system components (SectionCard, EmptyState) and semantic tokens
  • The "Other" slice aggregation for legend truncation (>6 slices) is well-implemented
web/src/pages/budget/AgentSpendingTable.tsx (1)

61-122: LGTM on the overall component structure!

Well-implemented sortable table with:

  • Proper accessibility (aria-sort on active column headers)
  • Focus-visible ring on column header buttons for keyboard navigation
  • Memoized sorting to avoid unnecessary recalculations
  • Consistent decimal formatting for budget percentages
  • Uses design system components (SectionCard, EmptyState, StaggerGroup)
web/src/__tests__/pages/BudgetForecastPage.test.tsx (1)

1-212: LGTM!

Thorough test coverage for BudgetForecastPage:

  • Tests loading skeleton visibility logic (shown only when loading && !overview)
  • Validates all four metric cards render with correct labels
  • Covers edge cases: confidence NaN/undefined--, days_until_exhausted nullN/A
  • Verifies error banner takes precedence over empty state
  • Confirms semantic table structure (4 column headers)
  • Tests WebSocket connectivity messaging
  • Clean mock setup with mutable hookReturn reset in beforeEach
web/src/utils/budget.ts (11)

236-247: Hardcoded locale 'en-US' noted.

This was previously flagged as a nitpick. For i18n support, consider accepting an optional locale parameter or using the browser's default locale.


16-60: LGTM!

Type definitions are well-structured with readonly properties. The as const satisfies readonly string[] pattern for the _VALUES arrays enables type-safe iteration while preserving the literal types. The BudgetMetricCardData type correctly derives from MetricCardProps via Omit.


64-74: LGTM!

DONUT_COLORS correctly uses CSS custom properties (var(--so-*)) per coding guidelines. CFO_EVENT_TYPES as a Set provides O(1) membership checks for the activity filter.


84-116: LGTM!

Clean implementation: uses Map for O(n) grouping, Set for unique task counting, handles edge cases (empty input, zero budget, zero tasks), and returns sorted results as documented.


152-179: LGTM!

Good implementation: colors are assigned after sorting by cost descending, ensuring deterministic color assignment where the highest-cost slice always receives the first palette color.


189-216: LGTM!

Correctly handles null call_category and unrecognized values by falling back to the uncategorized bucket. The defensive buckets[cat] ?? buckets.uncategorized pattern gracefully handles any unexpected category values.


221-229: LGTM!

Threshold checks are correctly ordered from highest severity (hard_stop_at) to lowest, ensuring the most critical zone is returned first.


256-279: LGTM!

Correct ISO-week aggregation: UTC-based Monday calculation avoids timezone-dependent bucket shifts. The day === 0 ? 6 : day - 1 shift logic correctly maps Sunday to offset 6 and Monday to offset 0.


287-306: LGTM!

The resetDay clamping fix correctly handles months with fewer days (e.g., February with resetDay=31). Input validation at line 288 provides a safe fallback for invalid input.


311-315: LGTM!

Simple filter leveraging the Set for O(1) membership checks.


322-365: LGTM!

Well-structured metric card generation with proper null handling. The fallback chain for days_until_exhausted gracefully degrades to showing reset days when no exhaustion is projected. The progress bar is conditionally included only when totalMonthly > 0.

Numeric columns (totalCost, budgetPercent, taskCount, costPerTask) now
default to descending when first clicked so highest values show first.
Text column (agentName) keeps ascending default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx`:
- Around line 30-36: The test "renders correct number of data rows" currently
only checks that each agentName appears but doesn't assert the exact count;
update the spec that uses makeRows(4) and AgentSpendingTable to include an
explicit assertion that the number of rendered data rows equals rows.length
(e.g., by using screen.getAllByRole('row') and adjusting for any header row, or
by querying the component's data-row test id/class if present) so the test fails
if extra rows are rendered.

In `@web/src/pages/budget/AgentSpendingTable.tsx`:
- Around line 44-59: Change sortKey from nullable to non-nullable and remove the
dead null-guard: update the useState declaration from useState<SortKey |
null>('totalCost') to useState<SortKey>('totalCost'), remove the if (!sortKey)
return rows check inside the sorted useMemo, and ensure handleSort logic (which
calls setSortKey) still works with the non-nullable sortKey; this simplifies
types and removes unreachable branches around sortKey, setSortKey, handleSort,
and sorted.
- Around line 72-92: The header and row mapping blocks (COLUMNS.map and
sorted.map) are too large inline; extract them into shared render components or
functions to reduce map complexity—create a ColumnHeader component (accepting
props: col, sortKey, sortDir, handleSort, ArrowUp, ArrowDown) to render the
button block currently inside COLUMNS.map, and create a RowCell or
AgentSpendingRow component (accepting the row item and COLUMNS/col definitions)
to render the JSX used inside sorted.map; replace the inline map bodies with
calls to these components/functions to preserve behavior (sorting props and
aria-sort logic, className logic, width/alignment, icons) while keeping
handleSort and sort state usage intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 53fb743d-a9ab-4a28-90e3-eef7f0c541e2

📥 Commits

Reviewing files that changed from the base of the PR and between d11ae2a and 9659eb8.

📒 Files selected for processing (2)
  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6

Files:

  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
web/src/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding

Files:

  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
  • web/src/pages/budget/AgentSpendingTable.tsx
web/src/**/__tests__/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Files:

  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget tracking includes pre-flight/in-flight checks, auto-downgrade, billing periods, cost tiers, quota/subscription. CFO includes anomaly detection, efficiency analysis, downgrade recommendations.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to src/synthorg/budget/**/*.py : Budget package (budget/): cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Budget: Cost tracking, budget enforcement (pre-flight/in-flight checks, auto-downgrade), billing periods, cost tiers, quota/subscription tracking, CFO cost optimization (anomaly detection, efficiency analysis, downgrade recommendations, approval decisions), spending reports, budget errors (BudgetExhaustedError, DailyLimitExceededError, QuotaExhaustedError).
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/__tests__/**/*.{tsx,ts} : Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest

Applied to files:

  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to web/src/__tests__/**/*.{ts,js} : Dashboard testing: Vitest unit tests organized by feature under `web/src/__tests__/`. Use fast-check for property-based testing (`fc.assert` + `fc.property`).

Applied to files:

  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10

Applied to files:

  • web/src/__tests__/pages/budget/AgentSpendingTable.test.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/pages/budget/AgentSpendingTable.tsx
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • web/src/pages/budget/AgentSpendingTable.tsx

- Remove dead `SortKey | null` type and null guard (sortKey is always set)
- Extract ColumnHeader component from COLUMNS.map() body (19 lines -> 1)
- Extract SpendingRow component from sorted.map() body (18 lines -> 1)
- Add exact row count assertion in test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Aureliolo
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Aureliolo Aureliolo merged commit b63b0f1 into main Mar 27, 2026
29 checks passed
@Aureliolo Aureliolo deleted the feat/budget-panel-page branch March 27, 2026 22:44
Aureliolo added a commit that referenced this pull request Mar 30, 2026
🤖 I have created a release *beep* *boop*
---
#MAJOR CHANGES; We got a somewhat working webui :)

##
[0.5.0](v0.4.9...v0.5.0)
(2026-03-30)


### Features

* add analytics trends and budget forecast API endpoints
([#798](#798))
([16b61f5](16b61f5))
* add department policies to default templates
([#852](#852))
([7a41548](7a41548))
* add remaining activity event types (task_started, tool_used,
delegation, cost_incurred)
([#832](#832))
([4252fac](4252fac))
* agent performance, activity, and history API endpoints
([#811](#811))
([9b75c1d](9b75c1d))
* Agent Profiles and Detail pages (biography, career, performance)
([#874](#874))
([62d7880](62d7880))
* app shell, Storybook, and CI/CD pipeline
([#819](#819))
([d4dde90](d4dde90))
* Approvals page with risk grouping, urgency indicators, batch actions
([#889](#889))
([4e9673d](4e9673d))
* Budget Panel page (P&L dashboard, breakdown charts, forecast)
([#890](#890))
([b63b0f1](b63b0f1))
* build infrastructure layer (API client, auth, WebSocket)
([#815](#815))
([9f01d3e](9f01d3e))
* CLI global options infrastructure, UI modes, exit codes, env vars
([#891](#891))
([fef4fc5](fef4fc5))
* CodeMirror editor and theme preferences toggle
([#905](#905),
[#807](#807))
([#909](#909))
([41fbedc](41fbedc))
* Company page (department/agent management)
([#888](#888))
([cfb88b0](cfb88b0))
* comprehensive hint coverage across all CLI commands
([#900](#900))
([937974e](937974e))
* config system extensions, per-command flags for
init/start/stop/status/logs
([#895](#895))
([32f83fe](32f83fe))
* configurable currency system replacing hardcoded USD
([#854](#854))
([b372551](b372551))
* Dashboard page (metric cards, activity feed, budget burn)
([#861](#861))
([7d519d5](7d519d5))
* department health, provider status, and activity feed endpoints
([#818](#818))
([6d5f196](6d5f196))
* design tokens and core UI components
([#833](#833))
([ed887f2](ed887f2))
* extend approval, meeting, and budget API responses
([#834](#834))
([31472bf](31472bf))
* frontend polish -- real-time UX, accessibility, responsive,
performance ([#790](#790),
[#792](#792),
[#791](#791),
[#793](#793))
([#917](#917))
([f04a537](f04a537))
* implement human roles and access control levels
([#856](#856))
([d6d8a06](d6d8a06))
* implement semantic conflict detection in workspace merge
([#860](#860))
([d97283b](d97283b))
* interaction components and animation patterns
([#853](#853))
([82d4b01](82d4b01))
* Login page + first-run bootstrap + Company page
([#789](#789),
[#888](#888))
([#896](#896))
([8758e8d](8758e8d))
* Meetings page with timeline viz, token bars, contribution formatting
([#788](#788))
([#904](#904))
([b207f46](b207f46))
* Messages page with threading, channel badges, sender indicators
([#787](#787))
([#903](#903))
([28293ad](28293ad))
* Org Chart force-directed view and drag-drop reassignment
([#872](#872),
[#873](#873))
([#912](#912))
([a68a938](a68a938))
* Org Chart page (living nodes, status, CRUD, department health)
([#870](#870))
([0acbdae](0acbdae))
* per-command flags for remaining commands, auto-behavior wiring,
help/discoverability
([#897](#897))
([3f7afa2](3f7afa2))
* Providers page with backend rework -- health, CRUD, subscription auth
([#893](#893))
([9f8dd98](9f8dd98))
* scaffold React + Vite + TypeScript + Tailwind project
([#799](#799))
([bd151aa](bd151aa))
* Settings page with search, dependency indicators, grouped rendering
([#784](#784))
([#902](#902))
([a7b9870](a7b9870))
* Setup Wizard rebuild with template comparison, cost estimator, theme
customization ([#879](#879))
([ae8b50b](ae8b50b))
* setup wizard UX -- template filters, card metadata, provider form
reuse ([#910](#910))
([7f04676](7f04676))
* setup wizard UX overhaul -- mode choice, step reorder, provider fixes
([#907](#907))
([ee964c4](ee964c4))
* structured ModelRequirement in template agent configs
([#795](#795))
([7433548](7433548))
* Task Board page (rich Kanban, filtering, dependency viz)
([#871](#871))
([04a19b0](04a19b0))


### Bug Fixes

* align frontend types with backend and debounce WS refetches
([#916](#916))
([134c11b](134c11b))
* auto-cleanup targets newly pulled images instead of old ones
([#884](#884))
([50e6591](50e6591))
* correct wipe backup-skip flow and harden error handling
([#808](#808))
([c05860f](c05860f))
* improve provider setup in wizard, subscription auth, dashboard bugs
([#914](#914))
([87bf8e6](87bf8e6))
* improve update channel detection and add config get command
([#814](#814))
([6b137f0](6b137f0))
* resolve all ESLint warnings, add zero-warnings enforcement
([#899](#899))
([079b46a](079b46a))
* subscription auth uses api_key, base URL optional for cloud providers
([#915](#915))
([f0098dd](f0098dd))


### Refactoring

* semantic analyzer cleanup -- shared filtering, concurrency, extraction
([#908](#908))
([81372bf](81372bf))


### Documentation

* brand identity and UX design system from
[#765](#765) exploration
([#804](#804))
([389a9f4](389a9f4))
* page structure and information architecture for v0.5.0 dashboard
([#809](#809))
([f8d6d4a](f8d6d4a))
* write UX design guidelines with WCAG-verified color system
([#816](#816))
([4a4594e](4a4594e))


### Tests

* add unit tests for agent hooks and page components
([#875](#875))
([#901](#901))
([1d81546](1d81546))


### CI/CD

* bump actions/deploy-pages from 4.0.5 to 5.0.0 in the major group
([#831](#831))
([01c19de](01c19de))
* bump astral-sh/setup-uv from 7.6.0 to 8.0.0 in
/.github/actions/setup-python-uv in the all group
([#920](#920))
([5f6ba54](5f6ba54))
* bump codecov/codecov-action from 5.5.3 to 6.0.0 in the major group
([#868](#868))
([f22a181](f22a181))
* bump github/codeql-action from 4.34.1 to 4.35.0 in the all group
([#883](#883))
([87a4890](87a4890))
* bump sigstore/cosign-installer from 4.1.0 to 4.1.1 in the
minor-and-patch group
([#830](#830))
([7a69050](7a69050))
* bump the all group with 3 updates
([#923](#923))
([ff27c8e](ff27c8e))
* bump wrangler from 4.76.0 to 4.77.0 in /.github in the minor-and-patch
group ([#822](#822))
([07d43eb](07d43eb))
* bump wrangler from 4.77.0 to 4.78.0 in /.github in the all group
([#882](#882))
([f84118d](f84118d))


### Maintenance

* add design system enforcement hook and component inventory
([#846](#846))
([15abc43](15abc43))
* add dev-only auth bypass for frontend testing
([#885](#885))
([6cdcd8a](6cdcd8a))
* add pre-push rebase check hook
([#855](#855))
([b637a04](b637a04))
* backend hardening -- eviction/size-caps and model validation
([#911](#911))
([81253d9](81253d9))
* bump axios from 1.13.6 to 1.14.0 in /web in the all group across 1
directory ([#922](#922))
([b1b0232](b1b0232))
* bump brace-expansion from 5.0.4 to 5.0.5 in /web
([#862](#862))
([ba4a565](ba4a565))
* bump eslint-plugin-react-refresh from 0.4.26 to 0.5.2 in /web
([#801](#801))
([7574bb5](7574bb5))
* bump faker from 40.11.0 to 40.11.1 in the minor-and-patch group
([#803](#803))
([14d322e](14d322e))
* bump https://github.com/astral-sh/ruff-pre-commit from v0.15.7 to
0.15.8 ([#864](#864))
([f52901e](f52901e))
* bump nginxinc/nginx-unprivileged from `6582a34` to `f99cc61` in
/docker/web in the all group
([#919](#919))
([df85e4f](df85e4f))
* bump nginxinc/nginx-unprivileged from `ccbac1a` to `6582a34` in
/docker/web ([#800](#800))
([f4e9450](f4e9450))
* bump node from `44bcbf4` to `71be405` in /docker/sandbox
([#827](#827))
([91bec67](91bec67))
* bump node from `5209bca` to `cf38e1f` in /docker/web
([#863](#863))
([66d6043](66d6043))
* bump picomatch in /site
([#842](#842))
([5f20bcc](5f20bcc))
* bump recharts 2-&gt;3 and @types/node 22-&gt;25 in /web
([#802](#802))
([a908800](a908800))
* Bump requests from 2.32.5 to 2.33.0
([#843](#843))
([41daf69](41daf69))
* bump smol-toml from 1.6.0 to 1.6.1 in /site
([#826](#826))
([3e5dbe4](3e5dbe4))
* bump the all group with 3 updates
([#921](#921))
([7bace0b](7bace0b))
* bump the minor-and-patch group across 1 directory with 2 updates
([#829](#829))
([93e611f](93e611f))
* bump the minor-and-patch group across 1 directory with 3 updates
([#841](#841))
([7010c8e](7010c8e))
* bump the minor-and-patch group across 1 directory with 3 updates
([#869](#869))
([548cee5](548cee5))
* bump the minor-and-patch group in /site with 2 updates
([#865](#865))
([9558101](9558101))
* bump the minor-and-patch group with 2 updates
([#867](#867))
([4830706](4830706))
* consolidate Dependabot groups to 1 PR per ecosystem
([06d2556](06d2556))
* consolidate Dependabot groups to 1 PR per ecosystem
([#881](#881))
([06d2556](06d2556))
* improve worktree skill with full dep sync and status enhancements
([#906](#906))
([772c625](772c625))
* remove Vue remnants and document framework decision
([#851](#851))
([bf2adf6](bf2adf6))
* update web dependencies and fix brace-expansion CVE
([#880](#880))
([a7a0ed6](a7a0ed6))
* upgrade to Storybook 10 and TypeScript 6
([#845](#845))
([52d95f2](52d95f2))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Budget Panel page (P&L dashboard, breakdown charts, forecast)

1 participant