feat: Budget Panel page (P&L dashboard, breakdown charts, forecast)#890
feat: Budget Panel page (P&L dashboard, breakdown charts, forecast)#890
Conversation
#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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📜 Recent review details🧰 Additional context used📓 Path-based instructions (3)web/src/**/*.{tsx,ts}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
web/src/**/*📄 CodeRabbit inference engine (CLAUDE.md)
Files:
web/src/**/__tests__/**/*.{tsx,ts}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
🧠 Learnings (7)📓 Common learnings📚 Learning: 2026-03-27T17:07:16.441ZApplied to files:
📚 Learning: 2026-03-20T08:28:32.845ZApplied to files:
📚 Learning: 2026-03-15T21:20:09.993ZApplied to files:
📚 Learning: 2026-03-27T17:07:16.441ZApplied to files:
📚 Learning: 2026-03-27T17:07:16.441ZApplied to files:
📚 Learning: 2026-03-27T17:07:16.441ZApplied to files:
🔇 Additional comments (10)
WalkthroughAdds a complete Budget feature: a new 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure 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 FilesNone |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
| 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, |
There was a problem hiding this comment.
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.
| { id: 'a1', name: 'Alpha', department: 'Engineering' } as never, | |
| { id: 'a1', name: 'Alpha', department: 'Engineering' } as any, |
| const cumulativeValues = useMemo(() => { | ||
| if (!forecast) return [] | ||
| let running = 0 | ||
| return forecast.daily_projections.map((p) => { | ||
| running += p.projected_spend_usd | ||
| return running | ||
| }) | ||
| }, [forecast]) |
There was a problem hiding this comment.
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.
| 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]); |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (37)
web/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/pages/BudgetPage.test.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/__tests__/pages/budget/BudgetGauge.test.tsxweb/src/__tests__/pages/budget/BudgetSkeleton.test.tsxweb/src/__tests__/pages/budget/CategoryBreakdown.test.tsxweb/src/__tests__/pages/budget/CfoActivityFeed.test.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/__tests__/pages/budget/PeriodSelector.test.tsxweb/src/__tests__/pages/budget/SpendBurnChart.test.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/stores/budget.test.tsweb/src/__tests__/utils/budget.property.test.tsweb/src/__tests__/utils/budget.test.tsweb/src/hooks/useBudgetData.tsweb/src/pages/BudgetForecastPage.tsxweb/src/pages/BudgetPage.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/BudgetGauge.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/BudgetSkeleton.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/CategoryBreakdown.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/src/pages/budget/CfoActivityFeed.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/PeriodSelector.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/src/pages/budget/SpendBurnChart.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/src/stores/budget.tsweb/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.tsxweb/src/__tests__/pages/budget/BudgetGauge.test.tsxweb/src/__tests__/pages/BudgetPage.test.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/pages/budget/SpendBurnChart.test.tsxweb/src/__tests__/pages/budget/CategoryBreakdown.test.tsxweb/src/pages/BudgetForecastPage.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/__tests__/pages/budget/PeriodSelector.test.tsxweb/src/pages/budget/CfoActivityFeed.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/PeriodSelector.tsxweb/src/__tests__/utils/budget.property.test.tsweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/BudgetGauge.tsxweb/src/pages/BudgetPage.tsxweb/src/pages/budget/CategoryBreakdown.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/__tests__/pages/budget/CfoActivityFeed.test.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/src/pages/budget/BudgetSkeleton.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/utils/budget.test.tsweb/src/pages/budget/SpendBurnChart.tsxweb/src/stores/budget.tsweb/src/__tests__/stores/budget.test.tsweb/src/hooks/useBudgetData.tsweb/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.tsxweb/src/__tests__/pages/budget/BudgetGauge.test.tsxweb/src/__tests__/pages/BudgetPage.test.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/pages/budget/SpendBurnChart.test.tsxweb/src/__tests__/pages/budget/CategoryBreakdown.test.tsxweb/src/pages/BudgetForecastPage.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/__tests__/pages/budget/PeriodSelector.test.tsxweb/src/pages/budget/CfoActivityFeed.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/PeriodSelector.tsxweb/src/__tests__/utils/budget.property.test.tsweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/BudgetGauge.tsxweb/src/pages/BudgetPage.tsxweb/src/pages/budget/CategoryBreakdown.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/__tests__/pages/budget/CfoActivityFeed.test.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/src/pages/budget/BudgetSkeleton.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/utils/budget.test.tsweb/src/pages/budget/SpendBurnChart.tsxweb/src/stores/budget.tsweb/src/__tests__/stores/budget.test.tsweb/src/hooks/useBudgetData.tsweb/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.tsxweb/src/__tests__/pages/budget/BudgetGauge.test.tsxweb/src/__tests__/pages/BudgetPage.test.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/pages/budget/SpendBurnChart.test.tsxweb/src/__tests__/pages/budget/CategoryBreakdown.test.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/__tests__/pages/budget/PeriodSelector.test.tsxweb/src/__tests__/utils/budget.property.test.tsweb/src/__tests__/pages/budget/CfoActivityFeed.test.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/utils/budget.test.tsweb/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.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/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.tsxweb/src/__tests__/pages/budget/BudgetGauge.test.tsxweb/src/__tests__/pages/BudgetPage.test.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/pages/budget/SpendBurnChart.test.tsxweb/src/__tests__/pages/budget/CategoryBreakdown.test.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/__tests__/pages/budget/PeriodSelector.test.tsxweb/src/__tests__/utils/budget.property.test.tsweb/src/__tests__/pages/budget/CfoActivityFeed.test.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/utils/budget.test.tsweb/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.tsxweb/src/__tests__/pages/budget/BudgetGauge.test.tsxweb/src/__tests__/pages/BudgetPage.test.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/pages/budget/SpendBurnChart.test.tsxweb/src/__tests__/pages/budget/CategoryBreakdown.test.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/__tests__/pages/budget/PeriodSelector.test.tsxweb/src/__tests__/utils/budget.property.test.tsweb/src/__tests__/pages/budget/CfoActivityFeed.test.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/utils/budget.test.tsweb/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.tsxweb/src/__tests__/pages/budget/SpendBurnChart.test.tsxweb/src/__tests__/pages/budget/CategoryBreakdown.test.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/CfoActivityFeed.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/BudgetGauge.tsxweb/src/pages/budget/CategoryBreakdown.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/__tests__/pages/budget/CfoActivityFeed.test.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/src/pages/budget/BudgetSkeleton.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/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.tsxweb/src/__tests__/pages/budget/SpendBurnChart.test.tsxweb/src/pages/BudgetForecastPage.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/pages/budget/BudgetGauge.tsxweb/src/pages/BudgetPage.tsxweb/src/pages/budget/CategoryBreakdown.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/pages/budget/BudgetSkeleton.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/pages/budget/SpendBurnChart.tsxweb/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.tsxweb/src/__tests__/pages/BudgetPage.test.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/pages/budget/SpendBurnChart.test.tsxweb/src/__tests__/pages/budget/CategoryBreakdown.test.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/__tests__/pages/budget/CfoActivityFeed.test.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/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.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/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.tsxweb/src/__tests__/utils/budget.property.test.tsweb/src/pages/BudgetPage.tsxweb/src/__tests__/utils/budget.test.tsweb/src/stores/budget.tsweb/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.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/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.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/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.tsxweb/src/pages/budget/CategoryBreakdown.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/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.tsweb/src/pages/BudgetPage.tsxweb/src/__tests__/utils/budget.test.tsweb/src/stores/budget.tsweb/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.tsxweb/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.tsxweb/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
_VALUESarrays 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"witharia-labelon the containerrole="radio"witharia-checkedon each button- Proper
type="button"to prevent form submissionweb/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
alertsArbconstruction 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-sortattributes on column headersuseMemofor sorted data prevents unnecessary re-computations- Correct use of
[...rows].sort()to avoid mutating propsweb/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-testidfor 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}anddays_until_exhausted: nullshould 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
UseBudgetDataReturnmakes 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.tsand 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.allSettledfor 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
fetchBudgetDatatest suite provides good coverage of the critical vs non-critical fetch distinction, properly testing thatoverviewandbudgetConfigfailures set an error whileforecast,costRecords, andlistAgentsfailures degrade gracefully without surfacing errors.
| setAggregationPeriod: (period) => { | ||
| set({ aggregationPeriod: period }) | ||
| get().fetchTrends() | ||
| }, |
There was a problem hiding this comment.
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.
| 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', | ||
| }) | ||
| } |
There was a problem hiding this comment.
🧹 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>
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (5)
web/src/pages/budget/CostBreakdownChart.tsx (1)
110-111: 🧹 Nitpick | 🔵 TrivialConsider memoizing chart data array.
data={[...breakdown]}creates a new array reference on every render. While the previous review suggested memoizing this alongsidelegendSlices, 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 | 🟡 MinorFocus indicator lacks visible ring for keyboard navigation.
The
focus-visible:outline-noneremoves the default outline, butfocus-visible:text-foregroundalone 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 | 🟠 MajorThese stories still run the live budget hook side effects.
Overriding store actions on Lines 88-92 avoids API mutations, but
BudgetPagestill mounts the realuseBudgetData(). 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 | 🟠 MajorDon'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
errororwsSetupErroris 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 | 🟡 MinorUse the raw usage percent in the alert text.
Math.roundon Line 17 can show an amber state like89.6%as90%, which contradicts the selected zone and threshold message. Format the raw value instead of rounding across boundaries, and cover a decimal case inThresholdAlerts.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
📒 Files selected for processing (29)
CLAUDE.mdweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/pages/BudgetForecastPage.test.tsxweb/src/__tests__/pages/BudgetPage.test.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/stores/budget.test.tsweb/src/__tests__/utils/budget.property.test.tsweb/src/__tests__/utils/budget.test.tsweb/src/hooks/useBudgetData.tsweb/src/pages/BudgetForecastPage.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/CategoryBreakdown.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/PeriodSelector.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/src/pages/budget/SpendBurnChart.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/src/stores/budget.tsweb/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.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/pages/BudgetForecastPage.tsxweb/src/pages/budget/PeriodSelector.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/__tests__/utils/budget.property.test.tsweb/src/pages/budget/ThresholdAlerts.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/CategoryBreakdown.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/src/hooks/useBudgetData.tsweb/src/__tests__/pages/BudgetForecastPage.test.tsxweb/src/pages/budget/SpendBurnChart.tsxweb/src/__tests__/stores/budget.test.tsweb/src/stores/budget.tsweb/src/utils/budget.tsweb/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.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/pages/BudgetForecastPage.tsxweb/src/pages/budget/PeriodSelector.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/__tests__/utils/budget.property.test.tsweb/src/pages/budget/ThresholdAlerts.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/CategoryBreakdown.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/src/hooks/useBudgetData.tsweb/src/__tests__/pages/BudgetForecastPage.test.tsxweb/src/pages/budget/SpendBurnChart.tsxweb/src/__tests__/stores/budget.test.tsweb/src/stores/budget.tsweb/src/utils/budget.tsweb/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.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/utils/budget.property.test.tsweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/__tests__/pages/BudgetForecastPage.test.tsxweb/src/__tests__/stores/budget.test.tsweb/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.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/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.mdweb/src/pages/budget/PeriodSelector.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/CategoryBreakdown.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/src/__tests__/pages/BudgetForecastPage.test.tsxweb/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.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/utils/budget.property.test.tsweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/__tests__/pages/BudgetForecastPage.test.tsxweb/src/__tests__/stores/budget.test.tsweb/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.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/utils/budget.property.test.tsweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/__tests__/pages/BudgetForecastPage.test.tsxweb/src/__tests__/stores/budget.test.tsweb/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.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/src/__tests__/pages/BudgetForecastPage.test.tsxweb/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.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/pages/budget/CategoryBreakdown.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/pages/budget/SpendBurnChart.stories.tsxweb/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.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/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.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/__tests__/pages/budget/CostBreakdownChart.test.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/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.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/AgentSpendingTable.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/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.tsxweb/src/pages/budget/PeriodSelector.stories.tsxweb/src/pages/budget/ThresholdAlerts.stories.tsxweb/src/pages/budget/CostBreakdownChart.stories.tsxweb/src/pages/budget/CategoryBreakdown.stories.tsxweb/src/pages/budget/BudgetPage.stories.tsxweb/src/pages/budget/CfoActivityFeed.stories.tsxweb/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.tsweb/src/stores/budget.tsweb/src/utils/budget.tsweb/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.tsweb/src/stores/budget.tsweb/src/utils/budget.tsweb/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/reactfollow 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 thesatisfieskeyword 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.
computeExhaustionDateuses 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 forresetDayexceeding month length is now handled.The function correctly clamps
resetDayto the actual last day of both the current and next month usingnew Date(year, month + 1, 0).getDate()andnew Date(year, month + 2, 0).getDate(). This addresses the previous concern about months with fewer days (e.g.,resetDay=31in 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 viaoverview.currency ?? budgetConfig?.currencycorrectly 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
actionfrom'storybook/actions'(per Storybook 10 conventions) and includesparameters: { 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_usdarbitrary now usesmin: 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
alertsArbproperly sorts three random values and accumulates them to ensurewarn_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
isAllZerofunction correctly checks the sum of all categorycountvalues, ensuring that datasets with zero-cost but categorized records are not incorrectly treated as empty.
31-49: LGTM — legend row extracted into reusable component.
CategoryLegendRowkeeps 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. TheMemoryRouterdecorator appropriately provides routing context for any internal links.web/src/pages/budget/CostBreakdownChart.tsx (3)
26-40: LGTM — tooltip now respects thecurrencyprop.
DonutTooltipContentacceptscurrencyand passes it toformatCurrency, ensuring consistency with the legend formatting.
49-62: LGTM —legendSlicesis now memoized.The
useMemowrapper prevents recalculation on every render (including tooltip hover events), withbreakdownas 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, buttrendDatatimestamps 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
ChartTooltipContentcomponent properly accepts and passescurrencytoformatCurrency, ensuring consistent currency formatting between the tooltip and the rest of the chart.
50-73: Clean forecast bridging implementation.The
buildChartDatafunction 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. TheInteractiveBreakdownwrapper 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
useCallbackfor the handler anduseMemofor the computed sorted array, avoiding unnecessary re-renders. ThecompareRowsfunction 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. Thesatisfies 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-sortattributes and the actual rendered row sequence usinggetAllByTextwith 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
beforeEachwithvi.clearAllMocks()ensures the shareddefaultProps.onDimensionChangemock 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
NearBudgetstory effectively demonstrates threshold behavior. Thea11y.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
daysUntilBudgetResettests now properly usevi.useFakeTimers()andvi.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-structuredPromise.allSettledwith 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.warnwithsanitizeForLogprovides debuggability without exposing sensitive data.
160-180: Correct weekly aggregation and error-clearing behavior.The conditional
aggregateWeeklyapplication for weekly period is correct. Clearingtrendstonullon 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:
- Converts WebSocket events to activity items via
wsEventToActivityItem- Triggers
fetchOverview()only forbudget.record_addedevents (keeping metrics fresh when new costs arrive)- Logs conversion failures with context (
event_type,channel) for debuggingThe 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 atMAX_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
pollingdependency is a false positive. Thestartandstopfunctions fromusePollingare memoized withuseCallback(seeusePolling.tslines 45-68), so depending onpolling.startandpolling.stopdirectly is correct and more precise than depending on the entirepollingobject. 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 sinceBUDGET_CHANNELSis 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
Agentfields with realistic values, properly addressing the previous concern about usingas nevertype assertions. This ensures the test will catch type mismatches if theAgentinterface changes.
164-238: Comprehensive coverage ofPromise.allSettledfailure modes.Tests correctly verify the two-tier error handling strategy:
- Critical failures (overview/config) set
errorstate- Non-critical failures (forecast/records/agents) degrade gracefully with
errorremainingnullThe 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
aggregateWeeklyutility integration.
356-368: Test correctly verifies asyncfetchOverviewtrigger.The
vi.waitForpattern appropriately handles the fire-and-forget nature offetchOverview()called fromupdateFromWsEvent. This test coverage was previously flagged as missing and is now properly implemented.
| 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']) | ||
| }) |
There was a problem hiding this comment.
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.
| 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>
There was a problem hiding this comment.
♻️ Duplicate comments (1)
web/src/pages/budget/AgentSpendingTable.tsx (1)
47-54: 🧹 Nitpick | 🔵 TrivialConsider 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
📒 Files selected for processing (10)
web/src/__tests__/hooks/useBudgetData.test.tsweb/src/__tests__/pages/BudgetForecastPage.test.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/pages/BudgetForecastPage.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/pages/budget/PeriodSelector.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/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.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/pages/budget/PeriodSelector.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/__tests__/pages/BudgetForecastPage.test.tsxweb/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.tsxweb/src/__tests__/pages/budget/ThresholdAlerts.test.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/src/pages/budget/PeriodSelector.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/src/__tests__/pages/BudgetForecastPage.test.tsxweb/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.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/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.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/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.tsxweb/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.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/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.tsxweb/src/__tests__/hooks/useBudgetData.test.tsweb/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.tsxweb/src/pages/budget/ThresholdAlerts.tsxweb/src/pages/budget/CostBreakdownChart.tsxweb/src/pages/budget/BudgetGauge.stories.tsxweb/src/pages/budget/AgentSpendingTable.tsxweb/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.tsxweb/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
usedPercentvalues correctly map to thegetHealthColorthresholds (Healthy→ success,Warning→ warning,Critical/Exhausted→ danger,WithCurrency→ accent). ThedaysUntilExhausted: nullvs numeric distinction properly exercises the conditional exhaustion date rendering.
1-1:⚠️ Potential issue | 🟠 MajorUpdate import to use the correct Storybook 10 react-vite package.
For Storybook 10 with the react-vite framework,
MetaandStoryObjshould 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 10Learnt 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 complianceLearnt 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' fieldLearnt 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 exclusivelyLearnt 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 6web/src/__tests__/hooks/useBudgetData.test.ts (1)
1-135: LGTM!The test suite is well-structured and provides good coverage of the
useBudgetDatahook lifecycle:
- Mount behavior (fetchBudgetData called)
- Store state propagation (loading, error, overview, aggregationPeriod)
- WebSocket binding to the
budgetchannel (correctly matchesBUDGET_CHANNELS = ['budget']from the hook)- Polling lifecycle (30s interval, start on mount, stop on unmount)
The mocking strategy using
vi.hoistedfor polling controls and resetting the Zustand store inbeforeEachis 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) fromuseBudgetData- Uses semantic table markup (
<table>,<thead>,<tbody>,<th scope="col">,<td>) for accessibility- Extracts
ProjectionRowto keep the.map()callback minimal- Uses design system components (
MetricCard,SectionCard,EmptyState,ErrorBoundary)- Correctly guards empty state behind
!errorto avoid showing "No forecast data" when there's actually a fetch failure- Handles edge cases well:
Number.isFinite()for confidence,!= nullfor days_until_exhaustedweb/src/pages/budget/PeriodSelector.tsx (1)
1-40: LGTM!Clean implementation that addresses previous feedback:
- Derives
PERIODSfrom the canonicalAGGREGATION_PERIOD_VALUESto avoid drift- Includes
focus-visiblering 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
nullfor 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"andaria-hidden="true"on the icon- Uses semantic design tokens for warning/danger states
animate-pulsereserved for critical severityweb/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-pulseis 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:
DonutTooltipContentnow accepts and uses thecurrencyprop consistently with the legendlegendSlicesandchartDataare properly memoized to avoid recalculations on re-render- Dimension toggle buttons include
focus-visiblering 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-sorton 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_exhaustednull→N/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
hookReturnreset inbeforeEachweb/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_VALUESarrays enables type-safe iteration while preserving the literal types. TheBudgetMetricCardDatatype correctly derives fromMetricCardPropsviaOmit.
64-74: LGTM!
DONUT_COLORScorrectly uses CSS custom properties (var(--so-*)) per coding guidelines.CFO_EVENT_TYPESas aSetprovides O(1) membership checks for the activity filter.
84-116: LGTM!Clean implementation: uses
Mapfor O(n) grouping,Setfor 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_categoryand unrecognized values by falling back to the uncategorized bucket. The defensivebuckets[cat] ?? buckets.uncategorizedpattern 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 - 1shift logic correctly maps Sunday to offset 6 and Monday to offset 0.
287-306: LGTM!The
resetDayclamping 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
Setfor O(1) membership checks.
322-365: LGTM!Well-structured metric card generation with proper null handling. The fallback chain for
days_until_exhaustedgracefully degrades to showing reset days when no exhaustion is projected. The progress bar is conditionally included only whentotalMonthly > 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>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
web/src/__tests__/pages/budget/AgentSpendingTable.test.tsxweb/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.tsxweb/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.tsxweb/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>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
🤖 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->3 and @types/node 22->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>
Summary
/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) -- projected total, confidence, avg daily spend metric cards, shared SpendBurnChart, and daily projections table with cumulative totalsstores/budget.ts) --Promise.allSettledparallel fetch with graceful degradation, WebSocket event processing (budget + system channels), 30s polling, agent name/department mapping fromlistAgents()hooks/useBudgetData.ts) -- composition hook mirroringuseDashboardDatapattern with individual selectors, polling, and WebSocket bindingsutils/budget.ts) -- 10 pure computation functions + 6 exported types withreadonlyfields, companion_VALUESarrays, extractedCategoryBucketinterfacepages/budget/-- BudgetSkeleton, PeriodSelector, ThresholdAlerts, BudgetGauge, SpendBurnChart, CostBreakdownChart, CategoryBreakdown, AgentSpendingTable, CfoActivityFeedTest plan
npm --prefix web run type-check-- 0 errorsnpm --prefix web run lint-- 0 errors (12 pre-existing warnings in other files)npm --prefix web run test-- 1280 tests passing across 109 filesnpm --prefix web run dev-- verify budget page renders at/budgetnpm --prefix web run storybook-- verify all 9 budget story files renderReview 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