Skip to content

feat: Dashboard page (metric cards, activity feed, budget burn)#861

Merged
Aureliolo merged 9 commits intomainfrom
feat/dashboard-page
Mar 27, 2026
Merged

feat: Dashboard page (metric cards, activity feed, budget burn)#861
Aureliolo merged 9 commits intomainfrom
feat/dashboard-page

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Implement the flagship Dashboard page (feat: Dashboard page (metric cards, sparklines, activity feed, budget burn) #777) -- the first screen users see after login
  • 4 metric cards: Tasks, Active Agents, Spend (with sparkline + trend), Pending Approvals
  • Org Health section: department health bars with overall gauge and cost per dept
  • Activity feed: real-time WebSocket-driven timeline with colored action-type dots
  • Budget burn chart: Recharts area chart with forecast projection, "Today" reference line, remaining budget callout
  • StatusBar wired to live data: agent count, active agents, tasks, spend, budget %, pending approvals, system health
  • WebSocket disconnect warning banner when real-time connection is lost
  • Loading skeletons, error boundaries, and empty states per section
  • Responsive layout: 4-col -> 2-col at <1024px for metrics, 2-col -> 1-col for sections
  • Analytics Zustand store with parallel data fetching, graceful degradation, and WS event handling
  • useDashboardData hook: initial fetch + 30s polling + 5-channel WebSocket
  • Pure data transformation utilities with property-based tests (fast-check)
  • 11 test files, 585 total tests passing (15 new tests added during pre-PR review)
  • 4 Storybook story files (DashboardPage, ActivityFeed, OrgHealthSection, BudgetBurnChart)

Pre-PR Review

Reviewed by 6 agents (docs-consistency, frontend-reviewer, api-contract-drift, pr-test-analyzer, issue-resolution-verifier, type-design-analyzer). 20 findings addressed including immutability fixes, readonly arrays, missing test coverage, issue scope completions, and documentation updates.

Test plan

  • npm --prefix web run type-check -- passes
  • npm --prefix web run lint -- passes (0 errors)
  • npm --prefix web run test -- 585/585 passing
  • npm --prefix web run storybook -- verify Dashboard stories render correctly
  • Visual check: metric cards show correct labels and values
  • Visual check: org health bars animate and color-code by threshold
  • Visual check: budget burn chart renders with trend + forecast + today line
  • Visual check: activity feed shows agent actions with colored dots
  • Visual check: StatusBar shows live agent/task/spend/budget/approval counts
  • Responsive: resize to <1024px, verify 2-col metrics and 1-col sections

Closes #777

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 034fa9ab-e416-4f27-8643-b9ee67c2db3a

📥 Commits

Reviewing files that changed from the base of the PR and between b0d558b and 544d805.

📒 Files selected for processing (3)
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/utils/dashboard.ts

Walkthrough

Adds a full dashboard implementation and supporting infrastructure: new API endpoints (listActivities, getDepartmentHealth) and DTOs (ActivityItem, DepartmentHealth), a populated analytics Zustand store with fetch/push/update actions, a new hook useDashboardData (polling + WebSocket bindings), dashboard utilities (computeMetricCards, computeSpendTrend, computeOrgHealth, describeEvent, wsEventToActivityItem), UI components and pages (StatusBar updates, DashboardPage, DashboardSkeleton, ActivityFeed, ActivityFeedItem, OrgHealthSection, BudgetBurnChart), exported MetricCardProps, many tests, and Storybook stories.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 40.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Dashboard page (metric cards, activity feed, budget burn)' clearly summarizes the main change—implementing the Dashboard page with its key components.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing all Dashboard features, components, and test coverage implemented.
Linked Issues check ✅ Passed The PR fully addresses issue #777 requirements: metric cards, org health section, activity feed, budget burn chart, StatusBar integration, real-time WebSocket updates, loading states, and comprehensive test coverage are all implemented.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with the Dashboard page implementation scope from issue #777; no unrelated changes detected in the changeset.

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


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

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 22:47 — with GitHub Actions Inactive
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

Dependency Review

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

Snapshot Warnings

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

Scanned Files

None

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request implements the core Dashboard functionality, transitioning from a placeholder to a data-driven interface. Key additions include an analytics Zustand store, a useDashboardData hook for managing real-time updates via WebSockets and polling, and several specialized components such as ActivityFeed, BudgetBurnChart, and OrgHealthSection. The StatusBar now displays live metrics including agent counts, task status, and budget usage. Comprehensive testing has been introduced across the new modules. Feedback suggests improving type safety in test mocks by avoiding 'as never' and adhering to standard useEffect dependency practices instead of suppressing linter warnings.

useAnalyticsStore.setState({
overview: {
total_tasks: 42,
tasks_by_status: {} as never,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using as never can sometimes mask potential type issues, even in test mocks. While it might be acceptable here, consider providing a more accurate mock type or using as any if type strictness is not critical for this specific test case, to avoid potential future confusion or unexpected behavior if the tasks_by_status structure changes.


it('returns overview from store', () => {
const mockOverview = {
total_tasks: 10, tasks_by_status: {} as never, total_agents: 5,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using as never can sometimes mask potential type issues, even in test mocks. While it might be acceptable here, consider providing a more accurate mock type or using as any if type strictness is not critical for this specific test case, to avoid potential future confusion or unexpected behavior if the tasks_by_status structure changes.

Comment on lines +9 to +11
created: 2, assigned: 3, in_progress: 8, in_review: 2, completed: 5,
blocked: 1, failed: 1, interrupted: 1, cancelled: 1,
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using as never can sometimes mask potential type issues, even in test mocks. While it might be acceptable here, consider providing a more accurate mock type or using as any if type strictness is not critical for this specific test case, to avoid potential future confusion or unexpected behavior if the tasks_by_status structure changes.

Comment on lines +8 to +10
created: 2, assigned: 3, in_progress: 8, in_review: 2, completed: 5,
blocked: 1, failed: 1, interrupted: 1, cancelled: 1,
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using as never can sometimes mask potential type issues, even in test mocks. While it might be acceptable here, consider providing a more accurate mock type or using as any if type strictness is not critical for this specific test case, to avoid potential future confusion or unexpected behavior if the tasks_by_status structure changes.

})

it('returns fallback for unmapped event types using regex replace', () => {
// Force a type assertion to test the fallback path with a value not in EVENT_DESCRIPTIONS
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using as never can sometimes mask potential type issues, even in test mocks. While it might be acceptable here, consider providing a more accurate mock type or using as any if type strictness is not critical for this specific test case, to avoid potential future confusion or unexpected behavior if the describeEvent function's input type changes.

useEffect(() => {
healthPolling.start()
return () => healthPolling.stop()
// eslint-disable-next-line @eslint-react/exhaustive-deps
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

While the comment on line 51 explains the stability of healthPolling.start and healthPolling.stop, it's generally good practice to explicitly list all dependencies in the useEffect dependency array or ensure the linter is configured to understand stable refs. If healthPolling itself is stable, then the suppression is fine, but it's worth double-checking to prevent subtle bugs if the usePolling hook's behavior changes in the future.

useEffect(() => {
polling.start()
return () => polling.stop()
// eslint-disable-next-line @eslint-react/exhaustive-deps
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

While the comment on line 52 explains the stability of polling.start and polling.stop, it's generally good practice to explicitly list all dependencies in the useEffect dependency array or ensure the linter is configured to understand stable refs. If polling itself is stable, then the suppression is fine, but it's worth double-checking to prevent subtle bugs if the usePolling hook's behavior changes in the future.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 27

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

Inline comments:
In `@docs/design/page-structure.md`:
- Line 26: The API endpoint list currently omits /analytics/trends while the
"Dashboard sparklines" section still references /analytics/trends; reconcile by
choosing one canonical endpoint and updating both places to match: either add
`/analytics/trends` back into the API endpoints list (the list containing `GET
/analytics/overview`, `GET /analytics/forecast`, etc.) or change the "Dashboard
sparklines" reference to the endpoint you intend to use (e.g., `GET
/analytics/overview`), and ensure any descriptive text about sparklines and
their source points to that same canonical endpoint.

In `@web/src/__tests__/components/layout/StatusBar.test.tsx`:
- Around line 77-93: The test duplicates the same overview fixture in multiple
tests; extract a shared base mock object and reuse it to DRY up setup: define a
const (e.g., baseOverview) containing the common fields used in both tests and
then in each test call useAnalyticsStore.setState({ overview: { ...baseOverview,
/* per-test overrides if any */ } }); update the tests that call
useAnalyticsStore.setState and references to budget_used_percent assertions (in
StatusBar.test.tsx) to use the spread so only differences are specified.

In `@web/src/__tests__/hooks/useDashboardData.test.ts`:
- Around line 100-111: The test late-re-imports and re-mocks usePolling inside
the it block which can be fragile due to module caching; instead extract the
mock setup for usePolling into a shared setup (e.g., a beforeEach) or use
vi.mocked(usePolling).mockReturnValueOnce(...) at the module-level so the mocked
return is applied before useDashboardData is imported; ensure you create the
mockStart fn (mockStart) in that shared setup and have usePolling return {
active: false, error: null, start: mockStart, stop: vi.fn() } so renderHook(()
=> useDashboardData()) sees the mocked hook immediately and the
expect(mockStart).toHaveBeenCalled() is reliable.

In `@web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx`:
- Around line 40-45: Add a property-based test using fast-check to assert the
10-item cap for ActivityFeed: import fc and use
fc.assert(fc.property(fc.integer({min:0, max:1000}), (n) => {
renderWithRouter(<ActivityFeed activities={makeActivities(n)} />); /* assertions
*/ })); inside the property, for any n > 10 assert that an item past the cap
(e.g., 'agent-10' or the index representing the 11th item) is not in the
document and that the last allowed item (e.g., 'agent-9') is present; for n <=
10 assert all generated items are present. Reference ActivityFeed,
makeActivities, renderWithRouter, and screen when adding the test and ensure to
import fc from 'fast-check'.

In `@web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx`:
- Around line 41-44: The test ActivityFeedItem.test.tsx' "handles null task_id
without error" currently only checks that the rendered container is truthy;
update it to assert that no task link is rendered when task_id is null by
querying for the link element and expecting it to be absent (e.g., use
screen.queryByRole('link') or queryByText for the task label and expect
null/not.toBeInTheDocument). Target the ActivityFeedItem render call
(renderWithRouter(<ActivityFeedItem activity={makeActivity({ task_id: null })}
/>)) and replace or extend the assertion to explicitly verify the absence of the
task link.

In `@web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx`:
- Around line 51-56: The test uses a broad regex /avg/i to assert forecast info;
update the assertion in the BudgetBurnChart.test to check for the specific
average value rendered from SAMPLE_FORECAST (or the exact label + value string
produced by BudgetBurnChart) instead of the generic /avg/i. Locate the test that
renders <BudgetBurnChart trendData={SAMPLE_TREND} forecast={SAMPLE_FORECAST}
budgetTotal={500} /> and replace
expect(screen.getByText(/avg/i)).toBeInTheDocument() with an assertion that
matches the concrete text (e.g., the numeric average or full label like "Avg:
123" or use toHaveTextContent on the element that displays SAMPLE_FORECAST.avg)
so the test verifies the correct forecast value.

In `@web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx`:
- Around line 17-21: Update the test for DashboardSkeleton to also assert the
chart loading section is present: in the 'renders sections row with 2 skeleton
cards' test (testing DashboardSkeleton) call
screen.getByTestId('skeleton-chart') and add an expectation (e.g.,
expect(...).toBeInTheDocument() or expect(...).not.toBeNull()) so the chart
skeleton is verified alongside the sections row children length.

In `@web/src/__tests__/utils/dashboard.property.test.ts`:
- Around line 10-25: Replace the hard-coded WS_EVENT_TYPES and DEPT_NAMES arrays
with the canonical exported "as const" lists from the types module (import the
lists from "@/api/types" instead of declaring literals), ensuring the imported
values are cast/typed to WsEventType[] and DepartmentName[] (or use
Array.from/Typed assertion if needed) so the test always reflects the
source-of-truth; update references to the constants WS_EVENT_TYPES and
DEPT_NAMES in this test file to use the imported names.

In `@web/src/api/endpoints/company.ts`:
- Line 2: Replace the looser `string` type used for department keys with the
domain-specific `DepartmentName` type: update the import list to include
`DepartmentName` and change any properties declared as `name: string` (e.g.,
inside the `Department`, `DepartmentHealth`, and any endpoint response types
referenced in this file) to `name: DepartmentName`; ensure function signatures
and PaginatedResponse/ApiResponse generic usages that currently expect `string`
names are updated to use `DepartmentName` so the compiler enforces valid
department keys.

In `@web/src/components/layout/StatusBar.tsx`:
- Around line 46-50: The useEffect currently suppresses
`@eslint-react/exhaustive-deps` without mentioning why healthPolling is omitted;
update the effect to either include stable references (add healthPolling.start
and healthPolling.stop or just healthPolling) in the dependency array if they
are stable, or keep the empty array but replace the generic disable comment with
one that explicitly references healthPolling (e.g., “// eslint-disable-next-line
`@eslint-react/exhaustive-deps` -- healthPolling is stable and intentionally
excluded”) so the linter suppression documents the reason; locate the useEffect
in StatusBar.tsx that calls healthPolling.start and healthPolling.stop to make
this change.

In `@web/src/pages/dashboard/ActivityFeed.stories.tsx`:
- Around line 20-33: The story meta for ActivityFeed lacks the accessibility
test settings; update the meta object (the const named meta in
ActivityFeed.stories.tsx) to include a parameters key with an a11y.test
configuration that enables automated WCAG checks (for example set the test to
run against the desired WCAG level such as "wcag2aa" and any specific rules or
options your project requires). Add parameters: { a11y: { test: { /* configure
WCAG level and rule options, e.g. level: "wcag2aa", rules: {...} */ } } } to the
meta so Storybook runs the accessibility tests during stories.
- Around line 12-13: Remove the non-null assertions on agents[i %
agents.length]! and eventTypes[i % eventTypes.length]! and instead ensure the
source arrays are typed as non-empty (use "as const" or explicit tuple types) so
TypeScript infers their element types; update the code that sets agent_name and
action_type to use agents[i % agents.length] and eventTypes[i %
eventTypes.length] (no !) once the arrays are declared as const/as const to
guarantee safe access.

In `@web/src/pages/dashboard/ActivityFeedItem.tsx`:
- Line 32: The fallback color class used when ACTION_DOT_COLORS[actionType] is
undefined should be updated from 'bg-text-muted' to the valid design token
'bg-muted'; change the return expression that currently reads "return
ACTION_DOT_COLORS[actionType] ?? 'bg-text-muted'" in ActivityFeedItem.tsx to use
'bg-muted' so it matches the project's semantic color tokens and existing usage.

In `@web/src/pages/dashboard/BudgetBurnChart.stories.tsx`:
- Around line 28-39: The Storybook story meta object for BudgetBurnChart is
missing the accessibility parameter; update the exported meta (the const meta
object used with BudgetBurnChart) to include a parameters.a11y.test field set to
one of the allowed values ('error' | 'todo' | 'off') so the story runs WCAG
checks; add parameters: { a11y: { test: 'error' } } (or 'todo'/'off' as
appropriate) inside the meta object.
- Around line 68-74: The NearBudget story currently does inlined, nested
transformations for TREND_DATA and FORECAST which hurt readability; extract the
scaled arrays into named constants (e.g., scaledTrendData = TREND_DATA.map(p =>
({...p, value: p.value * 10})) and scaledDailyProjections =
FORECAST.daily_projections.map(p => ({...p, projected_spend_usd:
p.projected_spend_usd * 10})), build a scaledForecast = { ...FORECAST,
daily_projections: scaledDailyProjections }, and then set NearBudget.args to use
trendData: scaledTrendData, forecast: scaledForecast, budgetTotal: 100 to make
the intent clear and simplify the JSX.

In `@web/src/pages/dashboard/BudgetBurnChart.tsx`:
- Around line 57-64: The date-only ISO strings are parsed as UTC and shift days
in US time zones; update formatDayLabel to detect YYYY-MM-DD (e.g.
/^\d{4}-\d{2}-\d{2}$/), split into year/month/day and construct a local Date via
new Date(year, monthIndex, day) instead of new Date(dateStr), then fallback to
the existing invalid-date behavior; also make getTodayLabel use the exact same
toLocaleDateString formatting so the "Today" label aligns with the parsed local
calendar dates (keep function names formatDayLabel and getTodayLabel).

In `@web/src/pages/dashboard/DashboardPage.stories.tsx`:
- Around line 125-136: The EmptyOrg Story decorator currently calls
setStoreState but leaves the default mockOverview in place; update the decorator
used by the EmptyOrg Story (the function in DashboardPage.stories.tsx that calls
setStoreState) to also set overview: null so the story truly represents an empty
organization state — modify the setStoreState call alongside departmentHealths
and activities to include overview: null.
- Around line 81-96: The Storybook meta object for DashboardPage (the const
named "meta") is missing accessibility test parameters; update the meta object
to include parameters.a11y.test with the chosen value (e.g., 'error') so
Storybook 10 enforces WCAG checks for this story (keep the existing properties
like title, component, decorators and the "satisfies Meta<typeof DashboardPage>"
assertion intact).

In `@web/src/pages/dashboard/DashboardSkeleton.tsx`:
- Line 22: DashboardSkeleton passes data-testid to <SkeletonCard> but
SkeletonCard in web/src/components/ui/skeleton.tsx doesn't forward arbitrary DOM
props, so the attribute is dropped; update the SkeletonCard component to accept
and forward rest props (e.g., accept ...props or extend props type with
React.HTMLAttributes<HTMLDivElement>) and spread them onto the root DOM element
inside SkeletonCard so data-testid (and other DOM attributes) from
DashboardSkeleton are rendered in the DOM.

In `@web/src/pages/dashboard/OrgHealthSection.stories.tsx`:
- Around line 16-27: The story meta for OrgHealthSection is missing an explicit
accessibility policy; update the meta object (the constant named "meta" for
component OrgHealthSection) to include a parameters property with a11y.test set
to one of the allowed values ('error' | 'todo' | 'off'), e.g. add parameters: {
a11y: { test: 'error' } } to the meta so the story cannot bypass the WCAG gate.
- Line 1: Update the Storybook import and meta to use the Vite builder and
enable WCAG checks: change the import from '@storybook/react' to
'@storybook/react-vite' (keep Meta and StoryObj types) and add a11y testing to
the story meta/default export (the Meta object used for the OrgHealthSection
stories) by including parameters.a11y.test with the WCAG standard (e.g.,
'wcag2aa' or equivalent test config) so Storybook 10 runs WCAG compliance checks
for these stories.

In `@web/src/pages/dashboard/OrgHealthSection.tsx`:
- Line 42: Update the incorrect Tailwind utility class usage: replace the
invalid class name "text-text-muted" with the correct semantic token
"text-muted-foreground" wherever used (notably in the OrgHealthSection
component's span and in ActivityFeedItem). Locate the JSX elements using
className="... text-text-muted ..." and swap that token to
"text-muted-foreground" so the styling maps to the defined --so-text-muted theme
variable.

In `@web/src/stores/analytics.ts`:
- Around line 56-63: The code uses listDepartments() but only processes
deptResult.data (first page), causing incomplete departmentHealths; modify the
logic in the function using listDepartments to either pass a sufficiently large
limit for the dashboard or iterate paginated results until you've fetched all
departments (using deptResult.total and deptResult.offset/next-page semantics)
and then map each department through getDepartmentHealth (handling .catch(() =>
null)) before assigning departmentHealths. Ensure the final assignment to
departmentHealths collects healths from every fetched page so the health bars
and overall gauge reflect all departments.
- Around line 100-103: The updateFromWsEvent reducer only appends an activity
via wsEventToActivityItem and pushActivity but never updates live metrics;
modify updateFromWsEvent to also derive metric changes from the incoming event
(or from the returned item) and apply them to the store's overview, budgetConfig
and orgHealthPercent state so the dashboard updates immediately. Locate
updateFromWsEvent and after creating item = wsEventToActivityItem(event) call
the appropriate state-updaters (or directly merge into get().overview,
get().budgetConfig, get().orgHealthPercent using existing reducer helpers) to
patch counts/percentages immutably based on the event payload; keep pushActivity
but ensure metric updates run atomically with the activity append so UI reflects
live changes prior to the next poll.
- Around line 47-52: The Promise.all call makes getForecast() and
getBudgetConfig() hard failures and can abort the whole load; change the fetch
to treat forecast and budgetConfig as optional by using Promise.allSettled or
adding per-call .catch handlers (similar to listActivities) so failures return
null/defaults instead of throwing; update the assignment logic that currently
destructures [overview, forecast, budgetConfig, activitiesResult] from
Promise.all to map settled results (or use individual awaits with try/catch) and
ensure the store's forecast and budgetConfig fields are set to null/default on
error while still allowing overview and activities (from getOverviewMetrics and
listActivities) to succeed. Ensure you update references to getForecast and
getBudgetConfig in this block so transient errors degrade sections rather than
failing the entire dashboard load.

In `@web/src/utils/dashboard.ts`:
- Around line 100-125: The synthesized id in wsEventToActivityItem is not unique
enough; change the id composition to append a stable payload identifier when
available (e.g., taskId, payload.approval_id, payload.id, or another backend
event id) so duplicate keys are avoided; update the id creation in
wsEventToActivityItem to prefer task_id (taskId), then check for
payload.approval_id or payload.id (casting to string when present), and fall
back to the existing `${event.timestamp}-${event.event_type}-${agentName}` only
if no stable payload identifier exists.
- Around line 71-74: The "PENDING APPROVALS" card is currently using
overview.tasks_by_status.in_review which is a task workflow metric; either
change the data source to the real approvals count (e.g., use
overview.approvals_count or overview.approvals?.length or the store/API field
that represents current approval queue) and fallback to 0, or rename the card
label to reflect the metric used (e.g., "IN REVIEW" or "TASKS IN REVIEW");
update the component that builds the card (the object with label 'PENDING
APPROVALS' and value overview.tasks_by_status.in_review) to point to the correct
approvals field or to the new label so the UI matches the actual data.
🪄 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: d2420333-cb3f-42cb-a46a-7bec2b51c5b3

📥 Commits

Reviewing files that changed from the base of the PR and between 82d4b01 and 127be83.

📒 Files selected for processing (32)
  • CLAUDE.md
  • docs/design/page-structure.md
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/api/endpoints/activities.ts
  • web/src/api/endpoints/company.ts
  • web/src/api/endpoints/index.ts
  • web/src/api/types.ts
  • web/src/components/layout/StatusBar.tsx
  • web/src/components/ui/metric-card.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/stores/analytics.ts
  • web/src/utils/dashboard.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). (5)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript 6.0+ syntax; remove baseUrl (deprecated in TS 6), use moduleResolution: 'bundler' or 'nodenext', list required types in types array

Files:

  • web/src/api/endpoints/index.ts
  • web/src/api/endpoints/company.ts
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/api/types.ts
  • web/src/hooks/useDashboardData.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/utils/dashboard.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/api/endpoints/activities.ts
  • web/src/stores/analytics.ts
web/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Import cn from @/lib/utils for conditional class merging in TypeScript utility code

Files:

  • web/src/api/endpoints/index.ts
  • web/src/api/endpoints/company.ts
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/api/types.ts
  • web/src/hooks/useDashboardData.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/utils/dashboard.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/api/endpoints/activities.ts
  • web/src/stores/analytics.ts
**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript 6.0+ syntax; remove baseUrl (deprecated in TS 6), use moduleResolution: 'bundler' or 'nodenext', list required types in types array

Files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/components/ui/metric-card.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
web/src/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.tsx: Always reuse existing shared components from web/src/components/ui/ before creating new ones (StatusBadge, MetricCard, Sparkline, SectionCard, AgentCard, DeptHealthBar, ProgressGauge, StatPill, Avatar, Button, Toast, Skeleton variants, EmptyState, ErrorBoundary, ConfirmDialog, CommandPalette, InlineEdit, AnimatedPresence, StaggerGroup/Item)
Use Tailwind semantic classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)) for colors; never hardcode hex values in .tsx files
Use font-sans or font-mono for typography (maps to Geist tokens); never set fontFamily directly
Use density-aware spacing tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables for shadows and borders (var(--so-shadow-card-hover), border-border, border-bright); never hardcode shadow or border values
Use Vitest with fast-check for property-based testing (fc.assert + fc.property); set types: ['vitest/globals'] in tsconfig

Files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/components/ui/metric-card.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
web/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

web/**/*.tsx: Do NOT recreate status dots inline -- use <StatusBadge>; do NOT build card-with-header layouts from scratch -- use <SectionCard>; do NOT create metric displays with manual styles -- use <MetricCard>; do NOT render initials manually -- use <Avatar>
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

Files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/components/ui/metric-card.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

ALWAYS read the relevant design specification page in docs/design/ before implementing any feature or planning any issue; design spec is the starting point for architecture, data models, and behavior; alert user and explain if implementation deviates from spec; update the spec page when deviations are approved

Files:

  • docs/design/page-structure.md
web/src/components/ui/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

When creating new shared components, place in web/src/components/ui/ with kebab-case filename, create a matching .stories.tsx file with all states, export props as TypeScript interface, use design tokens exclusively

Files:

  • web/src/components/ui/metric-card.tsx
web/src/**/*.stories.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

Storybook 10 component stories must use @storybook/react-vite, import from storybook/test (not @storybook/test), storybook/actions (not @storybook/addon-actions), use parameters.a11y.test: 'error' | 'todo' | 'off' for WCAG compliance

Files:

  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
🧠 Learnings (13)
📚 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/api/endpoints/index.ts
  • CLAUDE.md
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.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/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • CLAUDE.md
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/utils/dashboard.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to web/src/**/*.tsx : Always reuse existing shared components from `web/src/components/ui/` before creating new ones (StatusBadge, MetricCard, Sparkline, SectionCard, AgentCard, DeptHealthBar, ProgressGauge, StatPill, Avatar, Button, Toast, Skeleton variants, EmptyState, ErrorBoundary, ConfirmDialog, CommandPalette, InlineEdit, AnimatedPresence, StaggerGroup/Item)

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • CLAUDE.md
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/utils/dashboard.ts
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to web/**/*.tsx : Do NOT recreate status dots inline -- use `<StatusBadge>`; do NOT build card-with-header layouts from scratch -- use `<SectionCard>`; do NOT create metric displays with manual styles -- use `<MetricCard>`; do NOT render initials manually -- use `<Avatar>`

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/components/ui/metric-card.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/utils/dashboard.ts
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to web/src/components/ui/*.tsx : When creating new shared components, place in `web/src/components/ui/` with kebab-case filename, create a matching `.stories.tsx` file with all states, export props as TypeScript interface, use design tokens exclusively

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • CLAUDE.md
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to web/src/**/*.stories.tsx : Storybook 10 component stories must use `storybook/react-vite`, import from `storybook/test` (not `storybook/test`), `storybook/actions` (not `storybook/addon-actions`), use `parameters.a11y.test: 'error' | 'todo' | 'off'` for WCAG compliance

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to web/src/**/*.tsx : Use Vitest with fast-check for property-based testing (`fc.assert` + `fc.property`); set `types: ['vitest/globals']` in tsconfig

Applied to files:

  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/__tests__/stores/analytics.test.ts
📚 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-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).

Applied to files:

  • CLAUDE.md
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/utils/dashboard.ts
  • web/src/__tests__/utils/dashboard.test.ts
📚 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 web/package.json : Web dashboard Node.js 20+; dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, ESLint, vue-tsc)

Applied to files:

  • CLAUDE.md
📚 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-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: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-26T21:34:31.847Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T21:34:31.847Z
Learning: Applies to web/**/*.tsx : 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

Applied to files:

  • web/src/pages/dashboard/BudgetBurnChart.tsx
🔇 Additional comments (25)
web/src/api/types.ts (1)

477-498: DTO additions are clean and consistent.

ActivityItem and DepartmentHealth are well-shaped for dashboard usage and follow existing nullability/type patterns in this module.

web/src/api/endpoints/index.ts (1)

1-1: Barrel export update looks good.

Adding activities here keeps endpoint imports consistent across the API surface.

CLAUDE.md (1)

125-125: Doc updates match the implementation scope.

The endpoint-domain and store-scope updates are consistent with the new dashboard additions.

Also applies to: 131-131

web/src/components/ui/metric-card.tsx (1)

4-4: Exporting MetricCardProps is the right move.

This improves reuse and keeps dashboard data contracts type-safe.

As per coding guidelines, shared UI components should “export props as a TypeScript interface.”

web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx (1)

1-46: LGTM — Test structure and coverage are solid.

The test suite correctly validates the ActivityFeed component's key behaviors: section title, empty state, item rendering, and the 10-item cap. The renderWithRouter helper and makeActivities factory are well-structured.

web/src/api/endpoints/activities.ts (1)

1-9: LGTM — Clean API endpoint implementation.

The endpoint follows the established pattern using apiClient and unwrapPaginated, with proper TypeScript typing for parameters and return value.

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

1-112: LGTM — Comprehensive hook test coverage.

The test suite thoroughly validates useDashboardData's responsibilities: initial data fetch, state exposure, WebSocket channel setup with all 5 channels, connection status, and polling initialization.

web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx (2)

38-42: Good use of ARIA role for gauge verification.

Using getAllByRole('meter') to verify the overall health gauge is a solid accessibility-aware testing pattern.


1-43: LGTM — Test coverage is appropriate for the component.

The suite covers the essential rendering behaviors: title, empty state, department health bars, and the overall gauge presence.

web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx (1)

1-73: LGTM — Comprehensive chart rendering tests.

The test suite covers the key rendering states: empty data, with/without forecast, days-left indicator, and remaining budget display. Using SVG presence checks for Recharts output is appropriate.

web/src/__tests__/components/layout/StatusBar.test.tsx (1)

1-112: LGTM — Thorough StatusBar test coverage.

The suite validates placeholder states, live data display, system status, budget percentage, and conditional pending approvals rendering effectively.

web/src/pages/dashboard/ActivityFeed.tsx (1)

1-36: LGTM — Excellent adherence to design system guidelines.

The component correctly reuses shared UI components (SectionCard, EmptyState, StaggerGroup, StaggerItem), uses semantic Tailwind classes (divide-border), and maintains immutability with readonly ActivityItem[]. Clean and well-structured implementation.

web/src/pages/dashboard/DashboardPage.stories.tsx (1)

1-136: Good story coverage for key dashboard states.

The stories effectively demonstrate WithData, Loading, Error, and EmptyOrg states. The setStoreState helper cleanly manages Zustand state for each scenario.

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

1-127: Well-structured test suite with good coverage of dashboard states.

The tests appropriately cover the key UI states: loading skeleton, metric cards, section headers, error banner, and WebSocket disconnect warning. The mock structure correctly mirrors UseDashboardDataReturn.

web/src/pages/DashboardPage.tsx (2)

1-81: Clean implementation following component reuse and design token guidelines.

The page correctly uses shared components (MetricCard, ErrorBoundary, StaggerGroup/Item), semantic Tailwind classes (text-foreground, border-danger/30, bg-danger/5), and spacing tokens (gap-grid-gap). The conditional rendering for loading, error, and WS disconnect states is well-structured.


30-31: The current implementation is correct. computeMetricCards requires both overview and budgetConfig because it computes the SPEND metric card's progress field using budget.total_monthly. Since fetchDashboardData() fetches both together atomically via Promise.all(), and subsequent polling only refreshes overview (not budgetConfig), the edge case of overview existing without budgetConfig is not possible. The guard clause prevents unnecessary calls when data is still loading or has failed.

			> Likely an incorrect or invalid review comment.
web/src/components/layout/StatusBar.tsx (2)

17-57: Store selectors and health polling setup are well-implemented.

Good use of Zustand selectors to minimize re-renders, proper cleanup in the polling effect, and appropriate fallback handling for optional values (?? 0, ?? '--').


126-133: The Dot component is appropriate for this use case and does not need replacement.

StatusBadge is specifically designed for displaying AgentRuntimeStatus (active, idle, error, offline) with hardcoded color mappings. The Dot component in StatusBar displays system health status with custom status-to-color mappings that don't align with agent runtime states. Since StatusBadge requires an AgentRuntimeStatus enum and StatusBar needs flexible color class handling, the custom Dot component is the correct choice.

			> Likely an incorrect or invalid review comment.
web/src/pages/dashboard/OrgHealthSection.tsx (1)

1-54: Good component structure following shared component reuse guidelines.

Correctly uses SectionCard, DeptHealthBar, ProgressGauge, EmptyState, and StaggerGroup/StaggerItem from the UI library. The conditional rendering for empty state and nullable overallHealth is well-handled.

web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx (1)

1-51: Good test coverage for ActivityFeedItem core behavior.

The tests appropriately verify agent name, description, timestamp rendering, and task link behavior. The makeActivity helper provides clean fixture generation.

web/src/pages/dashboard/ActivityFeedItem.tsx (3)

61-63: Verify text-text-secondary class name.

The class text-text-secondary has a redundant text- prefix. Standard semantic classes would be text-secondary or text-muted-foreground. Verify this is an intentional custom token.


46-55: Action dot overlay is a reasonable custom implementation.

While guidelines suggest using <StatusBadge>, this dot is specifically positioned as an Avatar overlay with ring styling and aria-label for accessibility. This specialized use case justifies the custom implementation.


1-82: Clean component with good accessibility considerations.

The component correctly uses the shared Avatar component, provides aria-label on the action dot, and handles optional task_id gracefully. The ACTION_DOT_COLORS mapping provides consistent color semantics.

web/src/pages/dashboard/ActivityFeed.stories.tsx (1)

1-48: Good story coverage with routing context.

The MemoryRouter decorator correctly provides routing context for Link components within ActivityFeedItem. The three stories cover default, empty, and overflow (full) states effectively.

web/src/pages/dashboard/BudgetBurnChart.stories.tsx (1)

1-1: No action needed. The import from @storybook/react in story files is correct and consistent with the rest of the codebase. The a11y configuration is already set globally in web/.storybook/preview.tsx with a11y: { test: 'error' }, so it applies to all stories without requiring individual story-level parameters.

			> Likely an incorrect or invalid review comment.

Comment on lines +56 to +63
const deptResult = await listDepartments()
const healthPromises = deptResult.data.map((dept) =>
getDepartmentHealth(dept.name).catch(() => null),
)
const healthResults = await Promise.all(healthPromises)
departmentHealths = healthResults.filter(
(h): h is DepartmentHealth => h !== null,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This only computes org health from the first departments page.

listDepartments() returns a paginated result, but this code never follows total/offset or requests a dashboard-sized limit. Once an org exceeds the API's default page size, the health bars and overall gauge will under-report departments.

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

In `@web/src/stores/analytics.ts` around lines 56 - 63, The code uses
listDepartments() but only processes deptResult.data (first page), causing
incomplete departmentHealths; modify the logic in the function using
listDepartments to either pass a sufficiently large limit for the dashboard or
iterate paginated results until you've fetched all departments (using
deptResult.total and deptResult.offset/next-page semantics) and then map each
department through getDepartmentHealth (handling .catch(() => null)) before
assigning departmentHealths. Ensure the final assignment to departmentHealths
collects healths from every fetched page so the health bars and overall gauge
reflect all departments.

Comment on lines +100 to +125
export function wsEventToActivityItem(event: WsEvent): ActivityItem {
const payload = event.payload
const agentName =
(typeof payload.agent_name === 'string' && payload.agent_name) ||
(typeof payload.assigned_to === 'string' && payload.assigned_to) ||
'System'
const taskId =
typeof payload.task_id === 'string' ? payload.task_id : null
const department =
typeof payload.department === 'string'
? (payload.department as ActivityItem['department'])
: null

const description =
typeof payload.description === 'string' && payload.description
? payload.description
: describeEvent(event.event_type)

return {
id: `${event.timestamp}-${event.event_type}-${agentName}`,
timestamp: event.timestamp,
agent_name: agentName,
action_type: event.event_type,
description,
task_id: taskId,
department,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The synthesized activity id is not unique enough for feed keys.

timestamp + event_type + agentName collides when the same agent emits the same event type twice in the same timestamp bucket. That's realistic with second-precision timestamps and will produce duplicate React keys / unstable feed rendering. Fold in a stable payload identifier (task_id, approval id, backend event id, etc.) before returning the item.

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

In `@web/src/utils/dashboard.ts` around lines 100 - 125, The synthesized id in
wsEventToActivityItem is not unique enough; change the id composition to append
a stable payload identifier when available (e.g., taskId, payload.approval_id,
payload.id, or another backend event id) so duplicate keys are avoided; update
the id creation in wsEventToActivityItem to prefer task_id (taskId), then check
for payload.approval_id or payload.id (casting to string when present), and fall
back to the existing `${event.timestamp}-${event.event_type}-${agentName}` only
if no stable payload identifier exists.

@Aureliolo Aureliolo force-pushed the feat/dashboard-page branch from 127be83 to a150086 Compare March 26, 2026 23:28
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 26, 2026 23:29 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

♻️ Duplicate comments (3)
web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx (1)

17-21: ⚠️ Potential issue | 🟡 Minor

Add chart skeleton assertion to complete loading-state coverage.

The test validates the sections row count but still misses skeleton-chart, which is part of the component contract.

✅ Minimal test addition
   it('renders sections row with 2 skeleton cards', () => {
     render(<DashboardSkeleton />)
     const sectionsRow = screen.getByTestId('skeleton-sections-row')
     expect(sectionsRow.children).toHaveLength(2)
+    expect(screen.getByTestId('skeleton-chart')).toBeInTheDocument()
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx` around lines 17
- 21, The test for DashboardSkeleton currently only asserts the sections row
children length; add an assertion that verifies the chart loading skeleton is
present by querying for the test id 'skeleton-chart' (e.g., using
screen.getByTestId('skeleton-chart') or getAllByTestId if multiple) inside the
same test for DashboardSkeleton so the loading-state contract includes the chart
skeleton.
web/src/api/endpoints/company.ts (1)

2-2: ⚠️ Potential issue | 🟡 Minor

Use DepartmentName for getDepartmentHealth parameter type.

name: string allows invalid department keys at compile time; narrowing to DepartmentName keeps this endpoint aligned with domain typing.

🔧 Suggested type-safety patch
-import type { ApiResponse, CompanyConfig, Department, DepartmentHealth, PaginatedResponse, PaginationParams } from '../types'
+import type {
+  ApiResponse,
+  CompanyConfig,
+  Department,
+  DepartmentHealth,
+  DepartmentName,
+  PaginatedResponse,
+  PaginationParams,
+} from '../types'

-export async function getDepartmentHealth(name: string): Promise<DepartmentHealth> {
+export async function getDepartmentHealth(name: DepartmentName): Promise<DepartmentHealth> {

Also applies to: 19-24

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

In `@web/src/api/endpoints/company.ts` at line 2, The endpoint parameter types use
a plain string for department keys; update getDepartmentHealth (and the other
functions at the same area) to use the domain-specific DepartmentName type: add
DepartmentName to the import list from '../types' and change parameter
declarations from name: string to name: DepartmentName in the
getDepartmentHealth function (and the other similar functions referenced in
lines ~19-24) so the API layer enforces valid department keys at compile time.
web/src/pages/dashboard/DashboardPage.stories.tsx (1)

81-93: ⚠️ Potential issue | 🟡 Minor

Add the required Storybook a11y parameter.

The story meta still omits parameters.a11y.test, so this file is not opting into the required WCAG checks.

♻️ Suggested change
 const meta = {
   title: 'Pages/Dashboard',
   component: DashboardPage,
+  parameters: {
+    a11y: { test: 'error' },
+  },
   decorators: [

As per coding guidelines: "Storybook 10: use parameters.a11y.test: 'error' | 'todo' | 'off' for a11y testing rules".

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

In `@web/src/pages/dashboard/DashboardPage.stories.tsx` around lines 81 - 93, The
story meta for DashboardPage is missing the Storybook accessibility parameter;
update the exported meta object (the const named "meta" for DashboardPage) to
include a parameters field with a11y.test set to one of the allowed values
('error' | 'todo' | 'off'), e.g. add parameters: { a11y: { test: 'error' } } so
Storybook will run the required WCAG checks for this story.
🤖 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/components/layout/StatusBar.tsx`:
- Around line 26-43: The component currently initializes healthStatus to 'ok'
and sets it to 'down' on any fetch error; change the behavior so unknown/
loading is represented and transient network errors don't override last-known
good state: update the useState<SystemStatus>('ok') to use an explicit 'unknown'
or 'loading' initial value (e.g., 'unknown'), adjust any UI that reads
healthStatus to handle this new state, and modify pollHealth (and its catch
block) to either leave healthStatus unchanged on fetch failure (preserving
last-known value) or set it to 'unknown' instead of 'down'; reference the
healthStatus state and setHealthStatus, plus the pollHealth and getHealth
functions when making these changes.

In `@web/src/hooks/useDashboardData.ts`:
- Around line 45-49: The poller only calls
useAnalyticsStore.getState().fetchOverview(), so departmentHealths and
orgHealthPercent never get refreshed; update pollFn to also refresh department
health data (e.g., call useAnalyticsStore.getState().fetchDepartmentHealths() or
a store method that updates departmentHealths/orgHealthPercent) and await both
calls (or implement a combined fetchOverviewAndHealth() in the store and call
that) so updateFromWsEvent() no longer leaves org health stale during polling.
- Around line 40-56: The initial mount triggers fetchDashboardData() and
polling.start() which immediately calls pollFn -> fetchOverview(), causing a
duplicate request; change the flow so polling.start() runs only after the
initial fetchDashboardData() resolves. Concretely, move the
polling.start()/polling.stop() logic into the initial useEffect (or chain it
from useAnalyticsStore.getState().fetchDashboardData().then(...)) so that after
fetchDashboardData() completes you call polling.start(); keep polling.stop() on
cleanup; alternatively, enable a delayed-first-tick mode on usePolling so pollFn
(fetchOverview) is not invoked immediately on start — reference useEffect
(initial data fetch), pollFn, polling, usePolling, fetchDashboardData and
fetchOverview to locate the affected code.

In `@web/src/pages/dashboard/ActivityFeedItem.tsx`:
- Around line 48-54: The inline status dot in ActivityFeedItem should be
replaced with the shared StatusBadge component: import StatusBadge into
ActivityFeedItem and remove the hand-rolled <span> that uses dotColor; render
<StatusBadge> instead (use the same color mapping by providing the equivalent
prop—e.g., variant/status derived from activity.action_type or by mapping
dotColor to the StatusBadge prop) and keep the existing aria-label (`Action:
${activity.action_type.replace(/[._]/g, ' ')}`) and size styling (use the
small/compact size API of StatusBadge). Ensure you remove the absolute/classname
styling from the span and rely on StatusBadge’s API/props for visuals and
accessibility so the shared mapping and semantics are used centrally.

In `@web/src/pages/dashboard/BudgetBurnChart.stories.tsx`:
- Line 1: The Storybook types are imported from the old package; replace the
import source so Meta and StoryObj come from the Vite-specific package: change
the import of Meta and StoryObj currently from '@storybook/react' to
'@storybook/react-vite' (update the import statement that declares Meta and
StoryObj in BudgetBurnChart.stories.tsx).

In `@web/src/pages/dashboard/DashboardPage.stories.tsx`:
- Around line 59-65: The mockActivities array in DashboardPage.stories.tsx uses
Date.now() to build timestamps which makes the story non-deterministic; replace
the dynamic Date.now() expressions in the mockActivities fixtures (the const
mockActivities: ActivityItem[] declaration) with fixed ISO 8601 timestamp
strings (e.g. "2023-01-01T12:00:00.000Z", "2023-01-01T11:59:00.000Z", etc.) so
the relative-time labels are stable across renders and visual tests.

In `@web/src/pages/dashboard/OrgHealthSection.tsx`:
- Around line 32-48: The JSX inside departments.map is too large—extract the
mapped fragment into a new small component (e.g., DepartmentRow or
DeptHealthRow) that accepts a single prop (dept) and returns the StaggerItem
wrapper containing DeptHealthBar and the conditional cost block (using
formatCurrency); then replace the inline map body with departments.map(d =>
<DepartmentRow key={d.name} dept={d} />). Ensure the new component references
DeptHealthBar, StaggerItem, and formatCurrency, is exported/defined in the same
module or imported where used, and preserves the original prop names
(display_name, health_percent, agent_count, task_count, cost_usd).

In `@web/src/pages/DashboardPage.tsx`:
- Around line 30-31: The current metricCards assignment hides all cards when
budgetConfig is null; change the logic so computeMetricCards is called when
overview exists (i.e., use overview ? computeMetricCards(overview, budgetConfig)
: []), and update the spend card rendering inside the resulting cards (or the
SpendCard component) to gracefully degrade when budgetConfig is null by omitting
budget/progress details and showing a fallback state; locate uses of
metricCards, computeMetricCards, overview, budgetConfig and the Spend/SpendCard
rendering to make these adjustments so TASKS/ACTIVE AGENTS/IN REVIEW still
render while only spend-specific UI depends on budgetConfig.

In `@web/src/stores/analytics.ts`:
- Around line 117-124: The catch block in updateFromWsEvent is unreachable
because wsEventToActivityItem (and its helper describeEvent) always return a
valid ActivityItem and do not throw; update the updateFromWsEvent implementation
in analytics.ts to either remove the try/catch and call const item =
wsEventToActivityItem(event); get().pushActivity(item); directly, or if you want
to keep defensive protection, leave the structure but replace the empty catch
with a short comment referencing wsEventToActivityItem/describeEvent explaining
that it currently cannot throw and the try/catch is retained intentionally for
future-proofing.

---

Duplicate comments:
In `@web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx`:
- Around line 17-21: The test for DashboardSkeleton currently only asserts the
sections row children length; add an assertion that verifies the chart loading
skeleton is present by querying for the test id 'skeleton-chart' (e.g., using
screen.getByTestId('skeleton-chart') or getAllByTestId if multiple) inside the
same test for DashboardSkeleton so the loading-state contract includes the chart
skeleton.

In `@web/src/api/endpoints/company.ts`:
- Line 2: The endpoint parameter types use a plain string for department keys;
update getDepartmentHealth (and the other functions at the same area) to use the
domain-specific DepartmentName type: add DepartmentName to the import list from
'../types' and change parameter declarations from name: string to name:
DepartmentName in the getDepartmentHealth function (and the other similar
functions referenced in lines ~19-24) so the API layer enforces valid department
keys at compile time.

In `@web/src/pages/dashboard/DashboardPage.stories.tsx`:
- Around line 81-93: The story meta for DashboardPage is missing the Storybook
accessibility parameter; update the exported meta object (the const named "meta"
for DashboardPage) to include a parameters field with a11y.test set to one of
the allowed values ('error' | 'todo' | 'off'), e.g. add parameters: { a11y: {
test: 'error' } } so Storybook will run the required WCAG checks for this story.
🪄 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: 0c9bb188-8634-46fb-a2c9-b041b2a93034

📥 Commits

Reviewing files that changed from the base of the PR and between 127be83 and a150086.

📒 Files selected for processing (32)
  • CLAUDE.md
  • docs/design/page-structure.md
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/api/endpoints/activities.ts
  • web/src/api/endpoints/company.ts
  • web/src/api/endpoints/index.ts
  • web/src/api/types.ts
  • web/src/components/layout/StatusBar.tsx
  • web/src/components/ui/metric-card.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/stores/analytics.ts
  • web/src/utils/dashboard.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: CI Pass
  • GitHub Check: Build Backend
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Dependency Review
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (6)
{src,tests,cli,web}/**/*.{py,ts,tsx,go}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples — use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small aliases. Vendor names allowed only in: (1) Operations design page, (2) .claude/ files, (3) third-party import paths

Files:

  • web/src/api/endpoints/company.ts
  • web/src/components/ui/metric-card.tsx
  • web/src/api/endpoints/activities.ts
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/api/types.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/api/endpoints/index.ts
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/utils/dashboard.ts
  • web/src/stores/analytics.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{ts,tsx}: Web Dashboard: ALWAYS reuse existing components from web/src/components/ui/ before creating new ones
Web Dashboard colors: use Tailwind semantic classes (text-foreground, bg-card, text-accent, text-success, bg-danger, etc.) or CSS variables (var(--so-accent)) — NEVER hardcode hex values in .tsx/.ts files
Web Dashboard typography: use font-sans or font-mono (maps to Geist tokens) — NEVER set fontFamily directly
Web Dashboard spacing: use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing — NEVER hardcode pixel values for layout spacing
Web Dashboard shadows/borders: use token variables (var(--so-shadow-card-hover), border-border, border-bright) — never hardcode
Web Dashboard: Do NOT recreate status dots inline — use <StatusBadge>
Web Dashboard: Do NOT build card-with-header layouts from scratch — use <SectionCard>
Web Dashboard: Do NOT create metric displays with text-metric font-bold — use <MetricCard>
Web Dashboard: Do NOT render initials circles manually — use <Avatar>
Web Dashboard: Do NOT create complex (>8 line) JSX inside .map() — extract to a shared component
Web Dashboard: Do NOT use rgba() with hardcoded values — use design token variables
Use React 19 with TypeScript for web dashboard — use async/await with server-side data fetching

Files:

  • web/src/api/endpoints/company.ts
  • web/src/components/ui/metric-card.tsx
  • web/src/api/endpoints/activities.ts
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/api/types.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/api/endpoints/index.ts
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/utils/dashboard.ts
  • web/src/stores/analytics.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
web/src/components/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

When creating new shared React components in web/src/components/ui/: (1) place in web/src/components/ui/ with kebab-case filename, (2) create .stories.tsx alongside with all states, (3) export props as TypeScript interface, (4) use design tokens exclusively — no hardcoded colors/fonts/spacing, (5) import cn from @/lib/utils for conditional class merging

Files:

  • web/src/components/ui/metric-card.tsx
web/src/**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/__tests__/**/*.{ts,tsx}: Web Dashboard: use Vitest for unit tests with --detect-async-leaks flag in CI
React property-based testing: use fast-check (fc.assert + fc.property)

Files:

  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Docs: Markdown in docs/ (built with Zensical, config: mkdocs.yml) — design spec: docs/design/ (11 pages), Architecture: docs/architecture/, Roadmap: docs/roadmap/, Security: docs/security.md, Licensing: docs/licensing.md, Reference: docs/reference/

Files:

  • docs/design/page-structure.md
web/**/*.stories.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

web/**/*.stories.tsx: Storybook 10: ESM-only — all CommonJS support removed
Storybook 10: import storybook/test (not @storybook/test), storybook/actions (not @storybook/addon-actions)
Storybook 10: use parameters.a11y.test: 'error' | 'todo' | 'off' for a11y testing rules (replaces old .element and .manual)
Enforce WCAG a11y compliance on all React Storybook stories via parameters.a11y.test: 'error' set globally in preview.tsx

Files:

  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
🧠 Learnings (31)
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Web Dashboard: Do NOT create metric displays with `text-metric font-bold` — use `<MetricCard>`

Applied to files:

  • web/src/components/ui/metric-card.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/utils/dashboard.ts
  • web/src/stores/analytics.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Web Dashboard: Do NOT build card-with-header layouts from scratch — use `<SectionCard>`

Applied to files:

  • web/src/components/ui/metric-card.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/utils/dashboard.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Web Dashboard spacing: use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing — NEVER hardcode pixel values for layout spacing

Applied to files:

  • web/src/components/ui/metric-card.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).

Applied to files:

  • CLAUDE.md
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/utils/dashboard.test.ts
📚 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-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/package.json : Node.js 22+, TypeScript 6.0+, dependencies in web/package.json (React 19, react-router, shadcn/ui, Radix UI, Tailwind CSS 4, Zustand, tanstack/react-query, xyflow/react, Recharts, Framer Motion, cmdk, Axios, Lucide React, Geist fonts, Storybook 10, Vitest, fast-check, ESLint, eslint-react/eslint-plugin, eslint-plugin-security)

Applied to files:

  • CLAUDE.md
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.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 web/package.json : Web dashboard Node.js 20+; dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, ESLint, vue-tsc)

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
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/api/endpoints/index.ts
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/components/ui/**/*.{ts,tsx} : When creating new shared React components in web/src/components/ui/: (1) place in web/src/components/ui/ with kebab-case filename, (2) create .stories.tsx alongside with all states, (3) export props as TypeScript interface, (4) use design tokens exclusively — no hardcoded colors/fonts/spacing, (5) import cn from `@/lib/utils` for conditional class merging

Applied to files:

  • CLAUDE.md
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.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:

  • CLAUDE.md
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/utils/dashboard.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use React 19 with TypeScript for web dashboard — use async/await with server-side data fetching

Applied to files:

  • CLAUDE.md
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/stores/analytics.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: Documentation source in `docs/` (Markdown, built with Zensical). Design spec in `docs/design/` (7 pages: index, agents, organization, communication, engine, memory, operations). Architecture in `docs/architecture/` (overview, tech-stack, decision log). Roadmap in `docs/roadmap/`. Security in `docs/security.md`. Licensing in `docs/licensing.md`. Reference in `docs/reference/`. REST API reference in `docs/rest-api.md`. Library reference in `docs/api/` (auto-generated from docstrings). Custom templates in `docs/overrides/`. Config in `mkdocs.yml`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/__tests__/**/*.{ts,tsx} : Web Dashboard: use Vitest for unit tests with `--detect-async-leaks` flag in CI

Applied to files:

  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/**/*.stories.tsx : Storybook 10: import `storybook/test` (not `storybook/test`), `storybook/actions` (not `storybook/addon-actions`)

Applied to files:

  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/__tests__/**/*.{ts,tsx} : React property-based testing: use fast-check (`fc.assert` + `fc.property`)

Applied to files:

  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Web Dashboard: Do NOT create complex (>8 line) JSX inside `.map()` — extract to a shared component

Applied to files:

  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/utils/dashboard.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Web Dashboard: Do NOT recreate status dots inline — use `<StatusBadge>`

Applied to files:

  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/utils/dashboard.ts
  • web/src/stores/analytics.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Web Dashboard: ALWAYS reuse existing components from `web/src/components/ui/` before creating new ones

Applied to files:

  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/utils/dashboard.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
📚 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 docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Web Dashboard: Do NOT render initials circles manually — use `<Avatar>`

Applied to files:

  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Web Dashboard shadows/borders: use token variables (var(--so-shadow-card-hover), border-border, border-bright) — never hardcode

Applied to files:

  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Web Dashboard: Do NOT use rgba() with hardcoded values — use design token variables

Applied to files:

  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Web Dashboard colors: use Tailwind semantic classes (text-foreground, bg-card, text-accent, text-success, bg-danger, etc.) or CSS variables (var(--so-accent)) — NEVER hardcode hex values in .tsx/.ts files

Applied to files:

  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/**/*.{ts,tsx} : Web Dashboard typography: use `font-sans` or `font-mono` (maps to Geist tokens) — NEVER set fontFamily directly

Applied to files:

  • web/src/pages/dashboard/OrgHealthSection.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/**/*.stories.tsx : Storybook 10: ESM-only — all CommonJS support removed

Applied to files:

  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/**/*.stories.tsx : Enforce WCAG a11y compliance on all React Storybook stories via `parameters.a11y.test: 'error'` set globally in preview.tsx

Applied to files:

  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/**/*.stories.tsx : Storybook 10: use `parameters.a11y.test: 'error' | 'todo' | 'off'` for a11y testing rules (replaces old `.element` and `.manual`)

Applied to files:

  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
📚 Learning: 2026-03-26T23:18:47.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: When review agents find valid issues (including pre-existing issues, suggestions, adjacent findings), fix them all — never skip or defer as 'out of scope'

Applied to files:

  • web/src/utils/dashboard.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/stores/analytics.ts
🔇 Additional comments (39)
web/src/api/types.ts (1)

477-498: Strong DTO additions for dashboard contracts.

ActivityItem and DepartmentHealth are well-scoped and consistent with existing enum/nullability patterns in this file.

web/src/api/endpoints/index.ts (1)

1-1: Good barrel export addition.

This keeps endpoint namespace imports consistent with the rest of the API layer.

CLAUDE.md (1)

125-131: Documentation updates are aligned with current web architecture.

The endpoint/domain count and store/page layout notes look consistent with the dashboard implementation direction.

web/src/components/ui/metric-card.tsx (1)

4-4: Good API-surface improvement.

Exporting MetricCardProps is a clean way to enable strongly typed downstream composition.

web/src/api/endpoints/activities.ts (1)

4-9: Endpoint wrapper is clean and consistent.

Typing + unwrapPaginated usage matches the existing endpoint conventions.

docs/design/page-structure.md (1)

26-26: Nice consistency fix in the dashboard data-source docs.

The endpoint list and design-principle reference now point to the same analytics source model.

Also applies to: 335-335

web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx (2)

40-45: Consider property-based testing for the 10-item cap.

This test is a good candidate for property-based testing with fast-check to verify the invariant holds across arbitrary input sizes. This was previously suggested and remains applicable.


1-46: Well-structured test suite with good coverage.

The test suite covers the key behaviors of the ActivityFeed component: section title, empty state, item rendering, and the 10-item cap. The makeActivities helper is clean and the renderWithRouter wrapper is a good pattern.

web/src/__tests__/hooks/useDashboardData.test.ts (3)

105-116: Late re-mocking of usePolling is fragile.

Re-importing and re-mocking usePolling inside the test after the module-level mock can lead to inconsistent behavior due to module caching. This was previously flagged and remains applicable.


1-53: Good test coverage for hook initialization and store integration.

The tests properly verify that fetchDashboardData is called on mount and that the hook correctly mirrors loading, overview, and error states from the store.


83-98: WebSocket channel binding test is well-structured.

Good verification that all 5 required channels (tasks, agents, budget, system, approvals) are bound.

web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx (2)

1-51: LGTM!

The test suite provides good coverage of the ActivityFeedItem component. The makeActivity helper with override pattern is clean, and the tests properly verify agent name, description, timestamp, and conditional task link rendering.


41-44: Good improvement on the null task_id assertion.

The test now properly asserts that no link is rendered when task_id is null using screen.queryByRole('link'), which is more specific than just checking container truthiness.

web/src/pages/dashboard/DashboardSkeleton.tsx (1)

1-27: LGTM!

The skeleton component correctly reuses SkeletonCard and SkeletonMetric from the UI library, uses design tokens for spacing (gap-grid-gap, space-y-6), and includes proper accessibility attributes (role="status", aria-live="polite"). The data-testid placement on wrapper divs ensures testability.

web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx (2)

1-74: LGTM!

The test suite provides comprehensive coverage of the BudgetBurnChart component across all states: empty data, with trend data, with/without forecast, days left indicator, and remaining budget display. The fixtures are well-structured.


51-57: Good specificity on forecast assertions.

The test now uses specific strings ('Avg/day' and '$6.20') rather than a broad regex pattern, which makes the assertions more precise and less prone to false positives.

web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx (1)

1-49: LGTM!

Well-structured test suite covering the OrgHealthSection component's key behaviors: section title, empty state, department health bars, overall health gauge (using accessible role="meter"), and cost formatting. The makeDepts helper is clean and generates realistic test data.

web/src/pages/dashboard/ActivityFeed.tsx (1)

1-36: LGTM!

The component correctly reuses design system components (SectionCard, EmptyState, StaggerGroup), uses readonly for the activities prop array, and keeps the JSX within .map() minimal (under 8 lines). The MAX_VISIBLE constant at module level is clear and the component logic is straightforward.

web/src/__tests__/components/layout/StatusBar.test.tsx (1)

1-137: LGTM!

Excellent test suite with good coverage of the StatusBar component states. The makeOverview helper with overrides pattern is a clean solution that DRYs up the repeated overview setup (addressing the previous review feedback). Tests cover all key scenarios: placeholder values, live data, system status, cost formatting, budget percentage, and pending approvals.

web/src/pages/dashboard/OrgHealthSection.stories.tsx (2)

1-1: Use @storybook/react-vite for Storybook 10 type imports.

Per coding guidelines, Storybook 10 stories should import types from @storybook/react-vite for proper Vite builder type safety.

Suggested fix
-import type { Meta, StoryObj } from '@storybook/react'
+import type { Meta, StoryObj } from '@storybook/react-vite'

16-27: Add parameters.a11y.test for WCAG compliance.

Per coding guidelines, Storybook stories must include parameters.a11y.test: 'error' | 'todo' | 'off' for accessibility testing.

Suggested fix
 const meta = {
   title: 'Dashboard/OrgHealthSection',
   component: OrgHealthSection,
   tags: ['autodocs'],
+  parameters: {
+    a11y: { test: 'error' },
+  },
   decorators: [
web/src/pages/dashboard/ActivityFeed.stories.tsx (2)

20-33: Add parameters.a11y.test for WCAG compliance.

Per coding guidelines, Storybook stories must include parameters.a11y.test: 'error' | 'todo' | 'off' for accessibility testing.

Suggested fix
 const meta = {
   title: 'Dashboard/ActivityFeed',
   component: ActivityFeed,
   tags: ['autodocs'],
+  parameters: {
+    a11y: { test: 'error' },
+  },
   decorators: [

1-1: 🧹 Nitpick | 🔵 Trivial

Use @storybook/react-vite for Storybook 10 type imports.

Per coding guidelines, Storybook 10 stories should import types from @storybook/react-vite for proper Vite builder type safety.

Suggested 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-26T23:18:47.548Z
Learning: Applies to web/**/*.stories.tsx : Storybook 10: import `storybook/test` (not `storybook/test`), `storybook/actions` (not `storybook/addon-actions`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/**/*.stories.tsx : Storybook 10: ESM-only — all CommonJS support removed
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/**/*.stories.tsx : Enforce WCAG a11y compliance on all React Storybook stories via `parameters.a11y.test: 'error'` set globally in preview.tsx
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/**/*.stories.tsx : Storybook 10: use `parameters.a11y.test: 'error' | 'todo' | 'off'` for a11y testing rules (replaces old `.element` and `.manual`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/src/components/ui/**/*.{ts,tsx} : When creating new shared React components in web/src/components/ui/: (1) place in web/src/components/ui/ with kebab-case filename, (2) create .stories.tsx alongside with all states, (3) export props as TypeScript interface, (4) use design tokens exclusively — no hardcoded colors/fonts/spacing, (5) import cn from `@/lib/utils` for conditional class merging
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T23:18:47.548Z
Learning: Applies to web/package.json : Node.js 22+, TypeScript 6.0+, dependencies in web/package.json (React 19, react-router, shadcn/ui, Radix UI, Tailwind CSS 4, Zustand, tanstack/react-query, xyflow/react, Recharts, Framer Motion, cmdk, Axios, Lucide React, Geist fonts, Storybook 10, Vitest, fast-check, ESLint, eslint-react/eslint-plugin, eslint-plugin-security)
web/src/pages/dashboard/BudgetBurnChart.stories.tsx (2)

28-39: Add parameters.a11y.test for WCAG compliance.

Per coding guidelines, Storybook stories must include parameters.a11y.test: 'error' | 'todo' | 'off' for accessibility testing.

Suggested fix
 const meta = {
   title: 'Dashboard/BudgetBurnChart',
   component: BudgetBurnChart,
   tags: ['autodocs'],
+  parameters: {
+    a11y: { test: 'error' },
+  },
   decorators: [

68-83: Good refactor: NearBudget transformations extracted to named constants.

The inline transformations were correctly extracted to NEAR_BUDGET_TREND and NEAR_BUDGET_FORECAST constants, improving readability as suggested in the previous review.

web/src/stores/analytics.ts (2)

117-124: WebSocket events only append activities; live metrics remain stale until next poll.

The updateFromWsEvent handler converts the event to an activity item and pushes it to the feed, but never patches overview, forecast, or orgHealthPercent. This means the StatusBar and metric cards stay stale until the 30s poll cycle, missing the live-update requirement from the PR objectives.

Consider deriving metric deltas from incoming events (e.g., incrementing task counts on task.created, updating spend on budget.record_added) or document that real-time metric updates are intentionally deferred to polling.


44-100: Good: Graceful degradation implemented with Promise.allSettled.

The fetchDashboardData function now correctly uses Promise.allSettled for the initial parallel fetches, treating only overview as critical while allowing forecast, budgetConfig, and activities to fail gracefully. Department health fetching also properly catches individual failures.

web/src/pages/dashboard/BudgetBurnChart.tsx (3)

57-72: Date parsing fix correctly implemented.

The parseChartDate helper now properly parses YYYY-MM-DD strings as local calendar dates, avoiding the UTC-shift issue that would display wrong dates for US users. The matching toLocaleDateString format in both formatDayLabel and getTodayLabel ensures the "Today" reference line aligns correctly with chart data.


74-91: Tooltip component is clean and uses design tokens correctly.

The ChartTooltipContent component properly uses CSS variables (border-border, bg-card, text-text-secondary, text-foreground) and follows the font-sans/font-mono typography guidelines.


93-213: Well-structured chart component with proper design system usage.

The component correctly:

  • Uses SectionCard for card layout (per guidelines)
  • Uses StatPill for metric displays
  • Uses EmptyState for no-data scenario
  • Applies CSS variable tokens for all colors (var(--so-accent), var(--so-danger), var(--so-warning), var(--so-border), var(--so-text-muted))
web/src/__tests__/utils/dashboard.test.ts (3)

1-9: Comprehensive test coverage for dashboard utilities.

Good coverage of the key dashboard data transformation functions with appropriate edge case handling for empty data, single points, zero values, and boundary conditions.


97-103: Test correctly validates the renamed "IN REVIEW" card.

The test validates the label change from "PENDING APPROVALS" to "IN REVIEW", which aligns with the fix that addresses the semantic mismatch between the label and the underlying tasks_by_status.in_review data source.


230-294: Good coverage of wsEventToActivityItem edge cases.

Tests properly cover agent name fallbacks (assigned_to, System), description fallback via describeEvent, and missing optional fields like task_id.

web/src/__tests__/stores/analytics.test.ts (3)

1-67: Well-structured store test with comprehensive mocking.

Good use of Vitest mocking for API endpoints and the resetStore() helper ensures clean state between tests.


109-181: Excellent coverage of graceful degradation scenarios.

Tests properly verify that:

  • Activities, departments, forecast, and budget failures don't block the dashboard
  • Only overview failure (critical dataset) sets the error state
  • Individual department health fetch failures are filtered out

This aligns well with the Promise.allSettled implementation in the store.


235-252: Good test for activity list cap enforcement.

The test correctly verifies that pushActivity maintains a maximum of 50 items, with the newest item at index 0 and the oldest items truncated from the end.

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

71-74: Good: Card label renamed to match data source.

The label was correctly changed from "PENDING APPROVALS" to "IN REVIEW" to accurately reflect that the value comes from tasks_by_status.in_review. This addresses the semantic mismatch flagged in the previous review.


100-121: Good: Activity ID uniqueness improved with counter.

The addition of wsActivityCounter ensures unique IDs even when the same agent emits the same event type within the same timestamp. The ID format ${timestamp}-${event_type}-${agentName}-${++wsActivityCounter} now guarantees uniqueness for React keys.


42-76: Clean implementation of metric card computation.

The function correctly:

  • Returns exactly 4 cards as specified
  • Clamps spend progress when cost exceeds budget
  • Conditionally includes sparkline data (requires ≥2 points)
  • Computes spend trend with proper edge case handling

Comment on lines +26 to +43
const [healthStatus, setHealthStatus] = useState<SystemStatus>('ok')

// Trigger overview fetch on mount if data isn't loaded yet
useEffect(() => {
const state = useAnalyticsStore.getState()
if (!state.overview && !state.loading) {
state.fetchOverview()
}
}, [])

// Poll system health
const pollHealth = useCallback(async () => {
try {
const health: HealthStatus = await getHealth()
setHealthStatus(health.status)
} catch {
setHealthStatus('down')
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Model an unknown health state instead of defaulting to ok/down.

Right now the bar shows a healthy system before the first poll succeeds, and any fetch failure is reported as “system down” even when the real backend state is simply unknown. Please add an explicit unknown/loading state, or keep the last-known value on request failures, so this indicator reflects actual health instead of transport errors.

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

In `@web/src/components/layout/StatusBar.tsx` around lines 26 - 43, The component
currently initializes healthStatus to 'ok' and sets it to 'down' on any fetch
error; change the behavior so unknown/ loading is represented and transient
network errors don't override last-known good state: update the
useState<SystemStatus>('ok') to use an explicit 'unknown' or 'loading' initial
value (e.g., 'unknown'), adjust any UI that reads healthStatus to handle this
new state, and modify pollHealth (and its catch block) to either leave
healthStatus unchanged on fetch failure (preserving last-known value) or set it
to 'unknown' instead of 'down'; reference the healthStatus state and
setHealthStatus, plus the pollHealth and getHealth functions when making these
changes.

Comment on lines +40 to +56
// Initial data fetch
useEffect(() => {
useAnalyticsStore.getState().fetchDashboardData()
}, [])

// Lightweight polling for overview refresh
const pollFn = useCallback(async () => {
await useAnalyticsStore.getState().fetchOverview()
}, [])
const polling = usePolling(pollFn, DASHBOARD_POLL_INTERVAL)

// polling.start/stop are stable refs from useCallback inside usePolling
useEffect(() => {
polling.start()
return () => polling.stop()
// eslint-disable-next-line @eslint-react/exhaustive-deps
}, [])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid the extra overview request on first mount.

fetchDashboardData() already loads overview, and polling.start() immediately invokes pollFn, so the first render sends a second fetchOverview() in parallel. That adds avoidable load and can race the initial store population. Start polling only after the initial dashboard fetch settles, or add a delayed-first-tick mode to usePolling.

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

In `@web/src/hooks/useDashboardData.ts` around lines 40 - 56, The initial mount
triggers fetchDashboardData() and polling.start() which immediately calls pollFn
-> fetchOverview(), causing a duplicate request; change the flow so
polling.start() runs only after the initial fetchDashboardData() resolves.
Concretely, move the polling.start()/polling.stop() logic into the initial
useEffect (or chain it from
useAnalyticsStore.getState().fetchDashboardData().then(...)) so that after
fetchDashboardData() completes you call polling.start(); keep polling.stop() on
cleanup; alternatively, enable a delayed-first-tick mode on usePolling so pollFn
(fetchOverview) is not invoked immediately on start — reference useEffect
(initial data fetch), pollFn, polling, usePolling, fetchDashboardData and
fetchOverview to locate the affected code.

Comment on lines +45 to +49
// Lightweight polling for overview refresh
const pollFn = useCallback(async () => {
await useAnalyticsStore.getState().fetchOverview()
}, [])
const polling = usePolling(pollFn, DASHBOARD_POLL_INTERVAL)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Refresh department health as part of the polling path.

The poller only calls fetchOverview(). From the store code, fetchOverview() updates just overview, and updateFromWsEvent() only appends activities, so departmentHealths and orgHealthPercent never change after the initial mount. The Org Health section will go stale while the rest of the dashboard keeps updating.

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

In `@web/src/hooks/useDashboardData.ts` around lines 45 - 49, The poller only
calls useAnalyticsStore.getState().fetchOverview(), so departmentHealths and
orgHealthPercent never get refreshed; update pollFn to also refresh department
health data (e.g., call useAnalyticsStore.getState().fetchDepartmentHealths() or
a store method that updates departmentHealths/orgHealthPercent) and await both
calls (or implement a combined fetchOverviewAndHealth() in the store and call
that) so updateFromWsEvent() no longer leaves org health stale during polling.

Comment on lines +48 to +54
<span
className={cn(
'absolute -bottom-0.5 -right-0.5 size-[6px] rounded-full ring-1 ring-card',
dotColor,
)}
aria-label={`Action: ${activity.action_type.replace(/[._]/g, ' ')}`}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use the shared <StatusBadge> instead of a hand-rolled action dot.

This span recreates the dashboard status-indicator pattern locally. Please swap it to <StatusBadge> so the color mapping and accessibility semantics stay centralized instead of being reimplemented here.

Based on learnings: "Web Dashboard: Do NOT recreate status dots inline — use <StatusBadge>".

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

In `@web/src/pages/dashboard/ActivityFeedItem.tsx` around lines 48 - 54, The
inline status dot in ActivityFeedItem should be replaced with the shared
StatusBadge component: import StatusBadge into ActivityFeedItem and remove the
hand-rolled <span> that uses dotColor; render <StatusBadge> instead (use the
same color mapping by providing the equivalent prop—e.g., variant/status derived
from activity.action_type or by mapping dotColor to the StatusBadge prop) and
keep the existing aria-label (`Action: ${activity.action_type.replace(/[._]/g, '
')}`) and size styling (use the small/compact size API of StatusBadge). Ensure
you remove the absolute/classname styling from the span and rely on
StatusBadge’s API/props for visuals and accessibility so the shared mapping and
semantics are used centrally.

@@ -0,0 +1,83 @@
import type { Meta, StoryObj } from '@storybook/react'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Use @storybook/react-vite for Storybook 10 type imports.

Per coding guidelines, Storybook 10 stories should import types from @storybook/react-vite for proper Vite builder type safety.

Suggested fix
-import type { Meta, StoryObj } from '@storybook/react'
+import type { Meta, StoryObj } from '@storybook/react-vite'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/dashboard/BudgetBurnChart.stories.tsx` at line 1, The Storybook
types are imported from the old package; replace the import source so Meta and
StoryObj come from the Vite-specific package: change the import of Meta and
StoryObj currently from '@storybook/react' to '@storybook/react-vite' (update
the import statement that declares Meta and StoryObj in
BudgetBurnChart.stories.tsx).

@Aureliolo Aureliolo force-pushed the feat/dashboard-page branch from a150086 to 7b6a97b Compare March 27, 2026 07:43
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 07:44 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

♻️ Duplicate comments (6)
web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx (1)

40-45: 🧹 Nitpick | 🔵 Trivial

Strengthen the cap invariant with a property-based test.

Line 40 currently checks one case (15). Please add a fast-check property to validate the 10-item cap across a range of counts so regressions in list slicing are caught more reliably.

♻️ Suggested update
+import * as fc from 'fast-check'
+
   it('caps displayed items at 10', () => {
-    renderWithRouter(<ActivityFeed activities={makeActivities(15)} />)
-    // Only 10 should render
-    expect(screen.queryByText('agent-10')).not.toBeInTheDocument()
-    expect(screen.getByText('agent-9')).toBeInTheDocument()
+    fc.assert(
+      fc.property(fc.integer({ min: 0, max: 100 }), (count) => {
+        const { unmount } = renderWithRouter(<ActivityFeed activities={makeActivities(count)} />)
+        const visible = Math.min(count, 10)
+        for (let i = 0; i < visible; i++) {
+          expect(screen.getByText(`agent-${i}`)).toBeInTheDocument()
+        }
+        if (count > 10) {
+          expect(screen.queryByText('agent-10')).not.toBeInTheDocument()
+        }
+        unmount()
+      }),
+    )
   })

Based on learnings: "Use fast-check (fc.assert + fc.property) for property-based testing in React test files".

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

In `@web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx` around lines 40 -
45, Replace the single-case test with a property-based test using fast-check:
write an fc.assert(fc.property(...)) that generates activity counts (e.g.,
fc.integer({min:0, max:100})) and for each count renders ActivityFeed with
makeActivities(count) and then asserts that at most ten items are in the DOM and
that items beyond index 9 (e.g., "agent-10", "agent-11", ...) are not present
while items up to index 9 (e.g., "agent-0" … "agent-9") are present when count
>= those indices; use ActivityFeed and makeActivities names to locate the test
and import fc from 'fast-check' and keep the test fast by capping the generated
range and using a reasonable numRuns if needed.
web/src/pages/dashboard/DashboardPage.stories.tsx (2)

81-96: 🛠️ Refactor suggestion | 🟠 Major

Add parameters.a11y.test for WCAG compliance.

Per coding guidelines, Storybook 10 stories should specify accessibility testing parameters. Add parameters.a11y.test: 'error' to enforce WCAG compliance checks.

♻️ Proposed fix
 const meta = {
   title: 'Pages/Dashboard',
   component: DashboardPage,
+  parameters: {
+    a11y: { test: 'error' },
+  },
   decorators: [
     (Story) => (
       <MemoryRouter>

As per coding guidelines: "Set a11y testing with parameters.a11y.test: 'error' | 'todo' | 'off' in Storybook stories".

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

In `@web/src/pages/dashboard/DashboardPage.stories.tsx` around lines 81 - 96, The
Storybook meta for DashboardPage is missing the accessibility test setting;
update the exported meta object (the meta constant for DashboardPage) to include
parameters.a11y.test set to 'error' so WCAG checks run (add a parameters
property on the meta object with a11y: { test: 'error' }). Ensure you modify the
meta constant near the DashboardPage story definition so Storybook 10 picks up
the a11y enforcement.

59-65: ⚠️ Potential issue | 🟡 Minor

Make story timestamps deterministic.

mockActivities uses Date.now() for timestamps, causing relative-time labels to drift on every render. This makes visual regression tests flaky. Use fixed ISO timestamps instead.

🐛 Proposed fix
 const mockActivities: ActivityItem[] = [
-  { id: '1', timestamp: new Date(Date.now() - 60_000).toISOString(), agent_name: 'agent-cto', action_type: 'task.created', description: 'Created auth module task', task_id: 'task-42', department: 'engineering' },
-  { id: '2', timestamp: new Date(Date.now() - 120_000).toISOString(), agent_name: 'agent-designer', action_type: 'task.status_changed', description: 'Completed wireframe review', task_id: 'task-38', department: 'design' },
-  { id: '3', timestamp: new Date(Date.now() - 300_000).toISOString(), agent_name: 'agent-devops', action_type: 'agent.status_changed', description: 'Changed status to idle', task_id: null, department: 'operations' },
-  { id: '4', timestamp: new Date(Date.now() - 600_000).toISOString(), agent_name: 'agent-qa', action_type: 'approval.submitted', description: 'Requested deployment approval', task_id: 'task-40', department: 'quality_assurance' },
-  { id: '5', timestamp: new Date(Date.now() - 900_000).toISOString(), agent_name: 'agent-eng-2', action_type: 'budget.record_added', description: 'Recorded $2.30 cost', task_id: 'task-35', department: 'engineering' },
+  { id: '1', timestamp: '2026-03-26T12:00:00.000Z', agent_name: 'agent-cto', action_type: 'task.created', description: 'Created auth module task', task_id: 'task-42', department: 'engineering' },
+  { id: '2', timestamp: '2026-03-26T11:59:00.000Z', agent_name: 'agent-designer', action_type: 'task.status_changed', description: 'Completed wireframe review', task_id: 'task-38', department: 'design' },
+  { id: '3', timestamp: '2026-03-26T11:55:00.000Z', agent_name: 'agent-devops', action_type: 'agent.status_changed', description: 'Changed status to idle', task_id: null, department: 'operations' },
+  { id: '4', timestamp: '2026-03-26T11:50:00.000Z', agent_name: 'agent-qa', action_type: 'approval.submitted', description: 'Requested deployment approval', task_id: 'task-40', department: 'quality_assurance' },
+  { id: '5', timestamp: '2026-03-26T11:45:00.000Z', agent_name: 'agent-eng-2', action_type: 'budget.record_added', description: 'Recorded $2.30 cost', task_id: 'task-35', department: 'engineering' },
 ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/dashboard/DashboardPage.stories.tsx` around lines 59 - 65, The
mockActivities array uses Date.now() to generate timestamps which makes story
output non-deterministic; update mockActivities (the ActivityItem[] entries) to
use fixed ISO timestamp strings (e.g., hard-coded new
Date('2025-01-01T12:00:00Z').toISOString() values or literal ISO strings) for
each id so the relative-time labels are stable across renders and tests; ensure
all five entries in mockActivities are replaced with deterministic ISO strings
rather than Date.now()-based calculations.
web/src/__tests__/utils/dashboard.property.test.ts (1)

10-25: 🧹 Nitpick | 🔵 Trivial

Don't hard-code the event/department lists; use the canonical exports from @/api/types.

WS_EVENT_TYPES and DEPT_NAMES duplicate the type definitions. When these unions grow, the test will silently skip new members. Import and reuse the source-of-truth arrays.

♻️ Suggested refactor

If WsEventType and DepartmentName are union types, you can derive arrays at runtime or export as const arrays from the types file:

// In `@/api/types.ts`, add:
export const WS_EVENT_TYPE_VALUES = [
  'task.created', 'task.updated', /* ... all values */
] as const satisfies readonly WsEventType[]

export const DEPARTMENT_NAME_VALUES = [
  'executive', 'product', /* ... all values */
] as const satisfies readonly DepartmentName[]

Then import these in the test file instead of duplicating the lists.

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

In `@web/src/__tests__/utils/dashboard.property.test.ts` around lines 10 - 25,
Replace the hard-coded WS_EVENT_TYPES and DEPT_NAMES arrays in the test with the
canonical arrays exported from the types module: import and use
WS_EVENT_TYPE_VALUES and DEPARTMENT_NAME_VALUES (or whatever runtime arrays you
export from `@/api/types`) instead of duplicating the unions; update references to
WS_EVENT_TYPES and DEPT_NAMES in this test file to use those imported constants
so the test stays in sync with the source-of-truth for WsEventType and
DepartmentName.
web/src/pages/dashboard/ActivityFeedItem.tsx (1)

46-55: 🛠️ Refactor suggestion | 🟠 Major

Use the shared <StatusBadge> component instead of a hand-rolled action dot.

The inline <span> recreates the status-indicator pattern that exists in the shared StatusBadge component. Per coding guidelines, status dots should use <StatusBadge> to centralize color mapping and accessibility semantics.

As per coding guidelines: "Do NOT recreate status dots inline; always use <StatusBadge> component".

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

In `@web/src/pages/dashboard/ActivityFeedItem.tsx` around lines 46 - 55, Replace
the hand-rolled status dot in ActivityFeedItem (the inline <span> next to
<Avatar>) with the shared StatusBadge component: remove the span that uses
dotColor and instead render <StatusBadge> passing the appropriate prop that maps
to the same semantics (derive the status/variant from activity.action_type the
same way dotColor was computed) and preserve the accessibility label
(aria-label={`Action: ${activity.action_type.replace(/[._]/g, ' ')}`}) and
sizing/positioning classes so the badge appears in the same location relative to
<Avatar>; this centralizes color mapping and accessibility per the StatusBadge
contract.
web/src/components/layout/StatusBar.tsx (1)

26-44: ⚠️ Potential issue | 🟠 Major

Model an unknown/loading health state instead of defaulting to 'ok'.

The component initializes healthStatus to 'ok' before the first poll completes, showing "all systems nominal" when the actual state is unknown. Network errors also set 'down' even when the backend may be healthy but unreachable.

Consider adding an 'unknown' state that displays "checking system health..." or similar, and preserve the last-known-good state on transient fetch failures.

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

In `@web/src/components/layout/StatusBar.tsx` around lines 26 - 44, Change the
SystemStatus union to include 'unknown' and initialize healthStatus in
StatusBar.tsx with useState<SystemStatus>('unknown') instead of 'ok'; update
pollHealth (the async function that calls getHealth and calls setHealthStatus)
to set the status only on a successful response (setHealthStatus(health.status))
and do not set 'down' inside the catch block so transient network errors
preserve the last-known-good state; optionally implement a separate
retry/backoff or consecutive-failure counter if you want to transition to 'down'
after repeated failures, but do not overwrite the previous status on a single
fetch error.
🤖 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__/components/layout/StatusBar.test.tsx`:
- Around line 130-136: The test in StatusBar.test.tsx is asserting a hardcoded
USD string which is locale-dependent; update the expectation to use the same
formatter the component uses by importing and calling formatCurrency with the
test value (e.g., formatCurrency(1234.56)) and assert that screen.getByText(...)
matches that formatted string; locate the test that sets useAnalyticsStore state
via makeOverview and the StatusBar component, add the formatCurrency import, and
replace the hardcoded '$1,234.56' assertion with one that uses formatCurrency so
the test is stable across locales.

In `@web/src/__tests__/hooks/useDashboardData.test.ts`:
- Around line 83-98: The test for useDashboardData's WebSocket bindings is too
loose; update the test in the spec that imports useWebSocket and calls
renderHook(() => useDashboardData()) so it asserts the bindings are exactly the
five expected channels rather than using expect.arrayContaining. Locate the test
block referencing useWebSocket and change the expectation to either compare the
bindings array for exact equality to
[{channel:'tasks'},{channel:'agents'},{channel:'budget'},{channel:'system'},{channel:'approvals'}]
or assert both that bindings.length === 5 and that every expected channel is
present; keep references to useDashboardData and useWebSocket to find the test.

In `@web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx`:
- Around line 51-57: The test in BudgetBurnChart.test.tsx asserts a
locale-dependent literal string ('$6.20') which fails in CI; update the test to
assert the formatted forecast currency using the same formatter used by the
component (or Intl.NumberFormat) instead of a hardcoded symbol—e.g., compute
expected = formatCurrency(forecastValue) or new
Intl.NumberFormat(...).format(...) from SAMPLE_FORECAST and use
expect(screen.getByText(expected)).toBeInTheDocument(); keep references to
BudgetBurnChart, SAMPLE_FORECAST and the getByText assertion.

In `@web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx`:
- Around line 44-47: The test in OrgHealthSection.test.tsx hardcodes "$24.50"
which fails under different locales; update the assertion to use the same
currency formatter used by the component instead of a literal string. Import or
reference the shared formatter/utility (e.g., formatCurrency or
currencyFormatter used by OrgHealthSection) and assert against
formatCurrency(24.5) (or currencyFormatter.format(24.5)) so the test follows the
UI's locale/currency configuration; replace the getByText expectation that
checks "$24.50" with one that uses the formatted value.

In `@web/src/__tests__/pages/DashboardPage.test.tsx`:
- Around line 32-39: The mockBudgetConfig object is missing the required
currency field from the BudgetConfig interface; update mockBudgetConfig to
include a currency: string property (for example "USD" or the app's default
currency) so it matches the BudgetConfig type used in tests and avoids TS/test
failures; locate the mockBudgetConfig constant in the test and add the currency
field with an appropriate string value.

In `@web/src/__tests__/utils/dashboard.property.test.ts`:
- Around line 36-56: arbOverview is missing the required currency field from
OverviewMetrics; update the fc.record in arbOverview to include a currency
property that generates valid currency strings (e.g., add currency:
fc.constantFrom('USD','EUR','GBP') or currency:
fc.string({minLength:3,maxLength:3}) ) so the generated arbitrary matches
OverviewMetrics and avoids type/test failures.

In `@web/src/__tests__/utils/dashboard.test.ts`:
- Around line 84-89: The SPEND card is formatted using formatCurrency without a
currency code, causing total_cost_usd to render with the default 'EUR'; update
the call in computeMetricCards (or the helper that formats the SPEND value) to
pass the 'USD' currency code when formatting total_cost_usd so the Spend card
value shows "$42.17" (locate the formatCurrency(...) call in
web/src/utils/dashboard.ts where total_cost_usd is formatted and add the 'USD'
argument).

In `@web/src/api/types.ts`:
- Around line 483-493: ActivityItem currently types action_type as the broad
WsEventType (used by wsEventToActivityItem), which may allow internal-only
events; create a narrower union type (e.g., ActivityActionType) listing only
user-facing values, change ActivityItem.action_type to ActivityActionType, and
update the wsEventToActivityItem transformer to map/validate WsEventType ->
ActivityActionType (or filter/omit internal events) so the REST /activities
responses can only emit the allowed subset.

In `@web/src/pages/dashboard/ActivityFeed.stories.tsx`:
- Line 1: Update the Storybook type import to use the Vite builder types:
replace the import of Meta and StoryObj from '@storybook/react' with imports
from '@storybook/react-vite' (i.e. change the import line that currently reads
"import type { Meta, StoryObj } from '@storybook/react'" to import those types
from '@storybook/react-vite' so Storybook 10 + Vite type safety is used).

In `@web/src/utils/dashboard.ts`:
- Around line 101-129: The current wsEventToActivityItem ID construction using
wsActivityCounter can collide across HMR/module reloads; update
wsEventToActivityItem to include a stronger unique component by using an
available stable identifier (prefer payload.id or task_id when present) and
falling back to crypto.randomUUID() (or similar) when none exist, e.g., build
the id from event.timestamp, event.event_type, agentName, and (payload.id ||
task_id || crypto.randomUUID()) while keeping or removing wsActivityCounter as a
lightweight tie-breaker; reference the wsActivityCounter variable and the
wsEventToActivityItem function to locate where to replace the id generation
logic.

---

Duplicate comments:
In `@web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx`:
- Around line 40-45: Replace the single-case test with a property-based test
using fast-check: write an fc.assert(fc.property(...)) that generates activity
counts (e.g., fc.integer({min:0, max:100})) and for each count renders
ActivityFeed with makeActivities(count) and then asserts that at most ten items
are in the DOM and that items beyond index 9 (e.g., "agent-10", "agent-11", ...)
are not present while items up to index 9 (e.g., "agent-0" … "agent-9") are
present when count >= those indices; use ActivityFeed and makeActivities names
to locate the test and import fc from 'fast-check' and keep the test fast by
capping the generated range and using a reasonable numRuns if needed.

In `@web/src/__tests__/utils/dashboard.property.test.ts`:
- Around line 10-25: Replace the hard-coded WS_EVENT_TYPES and DEPT_NAMES arrays
in the test with the canonical arrays exported from the types module: import and
use WS_EVENT_TYPE_VALUES and DEPARTMENT_NAME_VALUES (or whatever runtime arrays
you export from `@/api/types`) instead of duplicating the unions; update
references to WS_EVENT_TYPES and DEPT_NAMES in this test file to use those
imported constants so the test stays in sync with the source-of-truth for
WsEventType and DepartmentName.

In `@web/src/components/layout/StatusBar.tsx`:
- Around line 26-44: Change the SystemStatus union to include 'unknown' and
initialize healthStatus in StatusBar.tsx with useState<SystemStatus>('unknown')
instead of 'ok'; update pollHealth (the async function that calls getHealth and
calls setHealthStatus) to set the status only on a successful response
(setHealthStatus(health.status)) and do not set 'down' inside the catch block so
transient network errors preserve the last-known-good state; optionally
implement a separate retry/backoff or consecutive-failure counter if you want to
transition to 'down' after repeated failures, but do not overwrite the previous
status on a single fetch error.

In `@web/src/pages/dashboard/ActivityFeedItem.tsx`:
- Around line 46-55: Replace the hand-rolled status dot in ActivityFeedItem (the
inline <span> next to <Avatar>) with the shared StatusBadge component: remove
the span that uses dotColor and instead render <StatusBadge> passing the
appropriate prop that maps to the same semantics (derive the status/variant from
activity.action_type the same way dotColor was computed) and preserve the
accessibility label (aria-label={`Action:
${activity.action_type.replace(/[._]/g, ' ')}`}) and sizing/positioning classes
so the badge appears in the same location relative to <Avatar>; this centralizes
color mapping and accessibility per the StatusBadge contract.

In `@web/src/pages/dashboard/DashboardPage.stories.tsx`:
- Around line 81-96: The Storybook meta for DashboardPage is missing the
accessibility test setting; update the exported meta object (the meta constant
for DashboardPage) to include parameters.a11y.test set to 'error' so WCAG checks
run (add a parameters property on the meta object with a11y: { test: 'error' }).
Ensure you modify the meta constant near the DashboardPage story definition so
Storybook 10 picks up the a11y enforcement.
- Around line 59-65: The mockActivities array uses Date.now() to generate
timestamps which makes story output non-deterministic; update mockActivities
(the ActivityItem[] entries) to use fixed ISO timestamp strings (e.g.,
hard-coded new Date('2025-01-01T12:00:00Z').toISOString() values or literal ISO
strings) for each id so the relative-time labels are stable across renders and
tests; ensure all five entries in mockActivities are replaced with deterministic
ISO strings rather than Date.now()-based calculations.
🪄 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: 2caca2d4-6240-4c55-b156-52e4740f51d7

📥 Commits

Reviewing files that changed from the base of the PR and between a150086 and 7b6a97b.

📒 Files selected for processing (32)
  • CLAUDE.md
  • docs/design/page-structure.md
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/api/endpoints/activities.ts
  • web/src/api/endpoints/company.ts
  • web/src/api/endpoints/index.ts
  • web/src/api/types.ts
  • web/src/components/layout/StatusBar.tsx
  • web/src/components/ui/metric-card.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/stores/analytics.ts
  • web/src/utils/dashboard.ts

Comment on lines +51 to +57
it('renders forecast info when available', () => {
render(
<BudgetBurnChart trendData={SAMPLE_TREND} forecast={SAMPLE_FORECAST} budgetTotal={500} />,
)
expect(screen.getByText('Avg/day')).toBeInTheDocument()
expect(screen.getByText('$6.20')).toBeInTheDocument()
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use formatter-based assertion for forecast currency value.

Line 56 is locale-dependent and currently fails in CI because the rendered symbol is , not $.

💚 Suggested fix
 import { render, screen } from '@testing-library/react'
 import { BudgetBurnChart } from '@/pages/dashboard/BudgetBurnChart'
+import { formatCurrency } from '@/utils/format'
 import type { ForecastResponse, TrendDataPoint } from '@/api/types'
@@
   it('renders forecast info when available', () => {
@@
     expect(screen.getByText('Avg/day')).toBeInTheDocument()
-    expect(screen.getByText('$6.20')).toBeInTheDocument()
+    expect(screen.getByText(formatCurrency(6.2))).toBeInTheDocument()
   })
🧰 Tools
🪛 GitHub Actions: CI

[error] 56-56: TestingLibraryElementError: Unable to find element with text '$6.20' (received '€6.20' in rendered output).

🪛 GitHub Check: Dashboard Test

[failure] 56-56: src/tests/pages/dashboard/BudgetBurnChart.test.tsx > BudgetBurnChart > renders forecast info when available
TestingLibraryElementError: Unable to find an element with the text: $6.20. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Ignored nodes: comments, script, style

Budget Burn

Avg/day €6.20
❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx:56:19
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx` around lines 51 -
57, The test in BudgetBurnChart.test.tsx asserts a locale-dependent literal
string ('$6.20') which fails in CI; update the test to assert the formatted
forecast currency using the same formatter used by the component (or
Intl.NumberFormat) instead of a hardcoded symbol—e.g., compute expected =
formatCurrency(forecastValue) or new Intl.NumberFormat(...).format(...) from
SAMPLE_FORECAST and use expect(screen.getByText(expected)).toBeInTheDocument();
keep references to BudgetBurnChart, SAMPLE_FORECAST and the getByText assertion.

Comment on lines +483 to +493
// ── Activities ──────────────────────────────────────────────

export interface ActivityItem {
id: string
timestamp: string
agent_name: string
action_type: WsEventType
description: string
task_id: string | null
department: DepartmentName | null
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider whether action_type: WsEventType is intentionally broad for REST API responses.

ActivityItem.action_type reuses the full WsEventType union, which includes system-internal events like 'system.startup', 'coordination.completed', and 'meeting.failed'. This works for the wsEventToActivityItem transformer (see web/src/utils/dashboard.ts:123-127), but if the REST /activities endpoint returns a narrower set of user-facing action types, consider defining a dedicated ActivityActionType subset to prevent invalid values from slipping through.

If the REST API intentionally mirrors all WebSocket event types, this is fine as-is.

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

In `@web/src/api/types.ts` around lines 483 - 493, ActivityItem currently types
action_type as the broad WsEventType (used by wsEventToActivityItem), which may
allow internal-only events; create a narrower union type (e.g.,
ActivityActionType) listing only user-facing values, change
ActivityItem.action_type to ActivityActionType, and update the
wsEventToActivityItem transformer to map/validate WsEventType ->
ActivityActionType (or filter/omit internal events) so the REST /activities
responses can only emit the allowed subset.

@@ -0,0 +1,48 @@
import type { Meta, StoryObj } from '@storybook/react'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Use @storybook/react-vite for Storybook 10 type imports.

Per coding guidelines, import types from @storybook/react-vite for proper Vite builder type safety.

-import type { Meta, StoryObj } from '@storybook/react'
+import type { Meta, StoryObj } from '@storybook/react-vite'
📝 Committable suggestion

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

Suggested change
import type { Meta, StoryObj } from '@storybook/react'
import type { Meta, StoryObj } from '@storybook/react-vite'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/dashboard/ActivityFeed.stories.tsx` at line 1, Update the
Storybook type import to use the Vite builder types: replace the import of Meta
and StoryObj from '@storybook/react' with imports from '@storybook/react-vite'
(i.e. change the import line that currently reads "import type { Meta, StoryObj
} from '@storybook/react'" to import those types from '@storybook/react-vite' so
Storybook 10 + Vite type safety is used).

Comment on lines +101 to +129
let wsActivityCounter = 0

export function wsEventToActivityItem(event: WsEvent): ActivityItem {
const payload = event.payload ?? {}
const agentName =
(typeof payload.agent_name === 'string' && payload.agent_name) ||
(typeof payload.assigned_to === 'string' && payload.assigned_to) ||
'System'
const taskId =
typeof payload.task_id === 'string' ? payload.task_id : null
const department =
typeof payload.department === 'string'
? (payload.department as ActivityItem['department'])
: null

const description =
typeof payload.description === 'string' && payload.description
? payload.description
: describeEvent(event.event_type)

return {
id: `${event.timestamp}-${event.event_type}-${agentName}-${++wsActivityCounter}`,
timestamp: event.timestamp,
agent_name: agentName,
action_type: event.event_type,
description,
task_id: taskId,
department,
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Activity ID uniqueness improved but still has edge-case collision risk.

The added wsActivityCounter (line 122) helps, but the counter resets on module reload/HMR, which could produce duplicate IDs across page refreshes if timestamps and event details match. Consider incorporating crypto.randomUUID() or a stable payload identifier (task_id, payload.id) when available for stronger uniqueness guarantees.

Suggested enhancement
+const generateActivityId = (event: WsEvent, agentName: string, taskId: string | null): string => {
+  const base = `${event.timestamp}-${event.event_type}-${agentName}`
+  // Prefer stable backend identifiers when available
+  if (taskId) return `${base}-${taskId}`
+  if (typeof event.payload?.id === 'string') return `${base}-${event.payload.id}`
+  return `${base}-${crypto.randomUUID()}`
+}
+
 export function wsEventToActivityItem(event: WsEvent): ActivityItem {
   // ...
   return {
-    id: `${event.timestamp}-${event.event_type}-${agentName}-${++wsActivityCounter}`,
+    id: generateActivityId(event, agentName, taskId),
     // ...
   }
 }
📝 Committable suggestion

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

Suggested change
let wsActivityCounter = 0
export function wsEventToActivityItem(event: WsEvent): ActivityItem {
const payload = event.payload ?? {}
const agentName =
(typeof payload.agent_name === 'string' && payload.agent_name) ||
(typeof payload.assigned_to === 'string' && payload.assigned_to) ||
'System'
const taskId =
typeof payload.task_id === 'string' ? payload.task_id : null
const department =
typeof payload.department === 'string'
? (payload.department as ActivityItem['department'])
: null
const description =
typeof payload.description === 'string' && payload.description
? payload.description
: describeEvent(event.event_type)
return {
id: `${event.timestamp}-${event.event_type}-${agentName}-${++wsActivityCounter}`,
timestamp: event.timestamp,
agent_name: agentName,
action_type: event.event_type,
description,
task_id: taskId,
department,
}
const generateActivityId = (event: WsEvent, agentName: string, taskId: string | null): string => {
const base = `${event.timestamp}-${event.event_type}-${agentName}`
// Prefer stable backend identifiers when available
if (taskId) return `${base}-${taskId}`
if (typeof event.payload?.id === 'string') return `${base}-${event.payload.id}`
return `${base}-${crypto.randomUUID()}`
}
export function wsEventToActivityItem(event: WsEvent): ActivityItem {
const payload = event.payload ?? {}
const agentName =
(typeof payload.agent_name === 'string' && payload.agent_name) ||
(typeof payload.assigned_to === 'string' && payload.assigned_to) ||
'System'
const taskId =
typeof payload.task_id === 'string' ? payload.task_id : null
const department =
typeof payload.department === 'string'
? (payload.department as ActivityItem['department'])
: null
const description =
typeof payload.description === 'string' && payload.description
? payload.description
: describeEvent(event.event_type)
return {
id: generateActivityId(event, agentName, taskId),
timestamp: event.timestamp,
agent_name: agentName,
action_type: event.event_type,
description,
task_id: taskId,
department,
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/utils/dashboard.ts` around lines 101 - 129, The current
wsEventToActivityItem ID construction using wsActivityCounter can collide across
HMR/module reloads; update wsEventToActivityItem to include a stronger unique
component by using an available stable identifier (prefer payload.id or task_id
when present) and falling back to crypto.randomUUID() (or similar) when none
exist, e.g., build the id from event.timestamp, event.event_type, agentName, and
(payload.id || task_id || crypto.randomUUID()) while keeping or removing
wsActivityCounter as a lightweight tie-breaker; reference the wsActivityCounter
variable and the wsEventToActivityItem function to locate where to replace the
id generation logic.

@Aureliolo Aureliolo force-pushed the feat/dashboard-page branch from 7b6a97b to 126ff66 Compare March 27, 2026 08:15
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 08:17 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

♻️ Duplicate comments (6)
web/src/pages/dashboard/BudgetBurnChart.stories.tsx (1)

1-40: 🛠️ Refactor suggestion | 🟠 Major

Align this story with the repo’s Storybook setup.

This story still imports Meta/StoryObj from @storybook/react, and meta still omits parameters.a11y.test.

🛠️ Suggested metadata fix
-import type { Meta, StoryObj } from '@storybook/react'
+import type { Meta, StoryObj } from '@storybook/react-vite'
 import { BudgetBurnChart } from './BudgetBurnChart'
@@
 const meta = {
   title: 'Dashboard/BudgetBurnChart',
   component: BudgetBurnChart,
   tags: ['autodocs'],
+  parameters: {
+    a11y: { test: 'error' },
+  },
   decorators: [
#!/bin/bash
# Verify the Storybook package/builder in use and the repo's story-meta conventions.
rg -n -C2 '"@storybook/react-vite"|@storybook/react-vite|@storybook/react"' package.json web/package.json web/.storybook 2>/dev/null || true
rg -n -C2 'a11y:\s*\{\s*test:' web/src --glob '*.stories.tsx'

As per coding guidelines, "Set a11y testing with parameters.a11y.test: 'error' | 'todo' | 'off' (not .element and .manual) in Storybook stories".

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

In `@web/src/pages/dashboard/BudgetBurnChart.stories.tsx` around lines 1 - 40, The
story imports Meta/StoryObj from the wrong Storybook package and omits the
required accessibility test parameter: update the import to the repo's Storybook
package (e.g., replace imports of Meta and StoryObj from '@storybook/react' with
the project's chosen package such as '@storybook/react-vite') and add a
parameters.a11y.test entry to the exported meta object (the meta constant for
BudgetBurnChart) set to a permitted value (for example 'error') so the meta now
includes parameters: { a11y: { test: 'error' } } and the Story type imports
match the repo's Storybook builder.
web/src/components/layout/StatusBar.tsx (1)

10-16: ⚠️ Potential issue | 🟠 Major

Keep an explicit unknown health state.

This still starts as 'ok' and maps any polling error to 'down', so users see a healthy system before the first successful poll and transient transport failures masquerade as outages.

Also applies to: 27-44

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

In `@web/src/components/layout/StatusBar.tsx` around lines 10 - 16, Add an
explicit "unknown" health state: extend the SystemStatus union to include
'unknown' and add an entry for it in STATUS_CONFIG (with an appropriate neutral
color/label). Initialize the component's health state to 'unknown' instead of
'ok' and change the polling/error-handling logic that currently maps transport
or polling errors to 'down' so that transient/initial transport failures map to
'unknown' until at least one successful poll determines 'ok', 'degraded', or
'down'.
web/src/pages/dashboard/ActivityFeedItem.tsx (1)

46-55: 🧹 Nitpick | 🔵 Trivial

Consider using the shared <StatusBadge> component for the action dot.

This hand-rolled span recreates the status-indicator pattern. Using <StatusBadge> would centralize the color mapping and accessibility semantics. As per coding guidelines: "Do NOT recreate status dots inline; always use <StatusBadge> component."

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

In `@web/src/pages/dashboard/ActivityFeedItem.tsx` around lines 46 - 55, Replace
the hand-rolled status dot span in ActivityFeedItem (the span using dotColor and
aria-label with activity.action_type) with the shared StatusBadge component:
import StatusBadge, pass the mapped color/state (use the existing dotColor or
map activity.action_type -> status value consistent with the app's status
mapping), preserve the accessibility label (aria-label={`Action:
${activity.action_type.replace(/[._]/g, ' ')}`}) and styling (size/positioning)
by forwarding equivalent props/className to StatusBadge so the centralized color
mapping and semantics are used instead of the inline span.
web/src/__tests__/hooks/useDashboardData.test.ts (2)

84-99: 🧹 Nitpick | 🔵 Trivial

Make the "5 channel bindings" assertion exact.

The arrayContaining check doesn't enforce exactly five bindings—extra unintended channels would still pass. Extract and compare the channel list for exact equality.

♻️ Suggested tightening
   it('sets up WebSocket with 5 channel bindings', async () => {
     const { useWebSocket } = await import('@/hooks/useWebSocket')
     renderHook(() => useDashboardData())

-    expect(useWebSocket).toHaveBeenCalledWith(
-      expect.objectContaining({
-        bindings: expect.arrayContaining([
-          expect.objectContaining({ channel: 'tasks' }),
-          expect.objectContaining({ channel: 'agents' }),
-          expect.objectContaining({ channel: 'budget' }),
-          expect.objectContaining({ channel: 'system' }),
-          expect.objectContaining({ channel: 'approvals' }),
-        ]),
-      }),
-    )
+    const call = vi.mocked(useWebSocket).mock.calls[0]?.[0]
+    const channels = call?.bindings.map((b: { channel: string }) => b.channel)
+    expect(channels).toHaveLength(5)
+    expect(channels).toEqual(expect.arrayContaining(['tasks', 'agents', 'budget', 'system', 'approvals']))
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/hooks/useDashboardData.test.ts` around lines 84 - 99, The
test currently uses expect.arrayContaining which allows extra channels; update
the assertion in the test for useDashboardData so it asserts the bindings array
exactly matches the five channels: after rendering call useDashboardData(), get
the argument passed to the mocked useWebSocket (refer to useWebSocket mock),
extract the bindings array (the bindings property) and map to binding.channel,
then assert that the resulting list strictly equals
['tasks','agents','budget','system','approvals'] (and optionally assert length
=== 5) so no extra channels can slip through.

106-117: 🧹 Nitpick | 🔵 Trivial

Consider extracting the mock setup to avoid late re-mocking.

Re-importing and re-mocking usePolling inside the test after it was already mocked at module level can be fragile due to module caching.

♻️ Alternative: configure mock in beforeEach
+const mockPollingStart = vi.fn()
+
+beforeEach(() => {
+  vi.clearAllMocks()
+  resetStore()
+  vi.mocked(usePolling).mockReturnValue({
+    active: false, error: null, start: mockPollingStart, stop: vi.fn(),
+  })
+})

 it('starts polling on mount', async () => {
-  const { usePolling } = await import('@/hooks/usePolling')
-  const mockStart = vi.fn()
-  vi.mocked(usePolling).mockReturnValue({
-    active: false, error: null, start: mockStart, stop: vi.fn(),
-  })
-
   renderHook(() => useDashboardData())
   await waitFor(() => {
-    expect(mockStart).toHaveBeenCalled()
+    expect(mockPollingStart).toHaveBeenCalled()
   })
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/hooks/useDashboardData.test.ts` around lines 106 - 117, The
test re-imports and re-mocks usePolling inside the it block which is fragile;
instead define mockStart and the mocked return value for usePolling at a shared
scope and configure it in a beforeEach so the mock is applied before modules are
imported—move the vi.mocked(usePolling).mockReturnValue({ active: false, error:
null, start: mockStart, stop: vi.fn() }) into a beforeEach (or top-level test
setup), ensure mockStart is declared in the outer scope and reset between tests,
and remove the in-test dynamic import of usePolling in the it('starts polling on
mount') test while still asserting mockStart was called after renderHook of
useDashboardData.
web/src/__tests__/utils/dashboard.property.test.ts (1)

10-25: 🧹 Nitpick | 🔵 Trivial

Consider importing event/department lists from the source-of-truth.

WS_EVENT_TYPES and DEPT_NAMES are hardcoded here. When either union grows in @/api/types, this suite will silently drop coverage while still passing. Export the canonical as const lists from the types module and reuse them here.

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

In `@web/src/__tests__/utils/dashboard.property.test.ts` around lines 10 - 25,
Replace the hardcoded WS_EVENT_TYPES and DEPT_NAMES arrays with the canonical
exports from the types module (import the as-const lists you add or already
export from "@/api/types"); remove the local arrays (WS_EVENT_TYPES and
DEPT_NAMES) and instead import the source-of-truth constants (e.g., the exported
event list and department list), then use or cast them to WsEventType[] and
DepartmentName[] where needed so the test always reflects the canonical unions
in the types module.
🤖 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/dashboard/BudgetBurnChart.test.tsx`:
- Around line 37-50: The svg presence check is a false positive because other
page svgs (like header icons) satisfy container.querySelector('svg'); update the
chart component BudgetBurnChart to wrap the rendered chart body with a unique
data-testid (e.g., data-testid="budget-burn-chart") and then change the tests in
BudgetBurnChart.test.tsx to assert on that test id (use getByTestId or
container.querySelector('[data-testid="budget-burn-chart"]')) for both the
forecast and no-forecast cases so the assertions are chart-specific.

In `@web/src/components/layout/StatusBar.tsx`:
- Around line 19-25: The StatusBar currently forces unloaded analytics to show
zeros by using "?? 0" fallbacks for totalAgents, activeAgents, and totalTasks;
remove those zero fallbacks in the selectors (e.g., totalAgents, activeAgents,
totalTasks in StatusBar.tsx) so they return undefined when data is not loaded,
and update the component's render logic to show a loading/skeleton or a neutral
placeholder (e.g., "—" or a spinner) when those values are undefined instead of
displaying 0; ensure similar treatment for other fields like
totalCost/currency/budgetPercent if needed so the UI reflects loading/error
states rather than valid zeros.
- Around line 76-119: Several status indicators in StatusBar.tsx (the StatusItem
blocks using the Dot component and spans for counts/labels, e.g., the
agents/active/tasks/pending approvals and final statusCfg display) recreate the
local dot pattern instead of using the shared StatusBadge; replace each use of
<Dot ... /> plus adjacent spans with the shared <StatusBadge /> component (pass
the color/status label and any count text or children as props) and ensure the
conditional pendingApprovals rendering still uses StatusBadge; update references
where statusCfg is used so you render <StatusBadge color={statusCfg.color}
label={statusCfg.label} /> (or the component's API) to keep styling consistent
across the dashboard.

In `@web/src/pages/dashboard/ActivityFeed.stories.tsx`:
- Around line 6-18: The makeActivities function generates timestamps using
Date.now(), causing non-deterministic story snapshots; change makeActivities to
use a fixed base Date (e.g., const FIXED_BASE = new
Date('2024-01-01T12:00:00.000Z')) and compute each activity timestamp relative
to that base (e.g., subtract i * 120_000 ms) so the timestamp field (timestamp)
becomes a deterministic ISO string for every run; update any references in
ActivityFeed.stories.tsx that call makeActivities to rely on the deterministic
timestamps.

In `@web/src/pages/dashboard/ActivityFeed.tsx`:
- Around line 26-32: Wrap the feed container (the element rendering StaggerGroup
/ the feed list) in an accessible live region by adding role="log" and
aria-live="polite" to the container that renders the visible.map entries (e.g.,
the StaggerGroup element that contains StaggerItem and ActivityFeedItem). Ensure
the attributes are applied to the parent element that contains the dynamic
additions (not individual items) so screen readers are notified of new
ActivityFeedItem entries without changing existing markup or animation behavior.

In `@web/src/pages/dashboard/BudgetBurnChart.stories.tsx`:
- Around line 69-76: NEAR_BUDGET_TREND and NEAR_BUDGET_FORECAST are partially
scaled (only daily_projections), causing the chart and summary callouts to
disagree; update NEAR_BUDGET_FORECAST so projected_total_usd and
avg_daily_spend_usd are scaled the same way you scaled daily_projections (use
the same multiplier applied to TREND_DATA), and then recompute
days_until_exhausted from those scaled values (e.g., days_until_exhausted =
projected_total_usd / avg_daily_spend_usd) to keep NEAR_BUDGET_TREND,
NEAR_BUDGET_FORECAST, TREND_DATA and FORECAST internally consistent.

In `@web/src/pages/dashboard/BudgetBurnChart.tsx`:
- Around line 43-46: The code mutates the local points array by assigning to
points[points.length - 1]; replace this with an immutable update: create a new
array (e.g., using slice + concatenation or Array.prototype.map) that copies all
elements except the last and appends a cloned last element with projected set to
last.actual. Update the variable used by the component (the same identifier
points) to reference the new array so the rest of the component uses the
immutably-updated data.

In `@web/src/stores/analytics.ts`:
- Around line 68-83: The department fetch uses listDepartments({ limit: 100 })
which only gets the first page so departmentHealths (and the overall gauge) will
be incomplete for orgs with >100 departments; update the logic around
listDepartments and deptResult to implement pagination (or repeatedly call
listDepartments with an offset/page token until you've fetched all items using
deptResult.total or the returned next-page token), aggregating results before
mapping to getDepartmentHealth and assigning departmentHealths, or explicitly
document the 100-department limit if that ceiling is intentional; key symbols:
listDepartments, deptResult.total (or next-page token), getDepartmentHealth, and
departmentHealths.

In `@web/src/utils/dashboard.ts`:
- Around line 111-114: The current cast of payload.department to
ActivityItem['department'] can accept invalid strings; replace the blind
assertion by validating payload.department against the allowed department names
(or using a type guard) before assigning to the department variable. Locate the
department assignment (variable name department, expression payload.department,
and the ActivityItem['department'] type) and change it to check that
payload.department is a string and is one of the known department values (e.g.,
via an array/set of allowed names or a isValidDepartment(value) function) — only
then assign it to department, otherwise set department to null or a safe
default.

---

Duplicate comments:
In `@web/src/__tests__/hooks/useDashboardData.test.ts`:
- Around line 84-99: The test currently uses expect.arrayContaining which allows
extra channels; update the assertion in the test for useDashboardData so it
asserts the bindings array exactly matches the five channels: after rendering
call useDashboardData(), get the argument passed to the mocked useWebSocket
(refer to useWebSocket mock), extract the bindings array (the bindings property)
and map to binding.channel, then assert that the resulting list strictly equals
['tasks','agents','budget','system','approvals'] (and optionally assert length
=== 5) so no extra channels can slip through.
- Around line 106-117: The test re-imports and re-mocks usePolling inside the it
block which is fragile; instead define mockStart and the mocked return value for
usePolling at a shared scope and configure it in a beforeEach so the mock is
applied before modules are imported—move the
vi.mocked(usePolling).mockReturnValue({ active: false, error: null, start:
mockStart, stop: vi.fn() }) into a beforeEach (or top-level test setup), ensure
mockStart is declared in the outer scope and reset between tests, and remove the
in-test dynamic import of usePolling in the it('starts polling on mount') test
while still asserting mockStart was called after renderHook of useDashboardData.

In `@web/src/__tests__/utils/dashboard.property.test.ts`:
- Around line 10-25: Replace the hardcoded WS_EVENT_TYPES and DEPT_NAMES arrays
with the canonical exports from the types module (import the as-const lists you
add or already export from "@/api/types"); remove the local arrays
(WS_EVENT_TYPES and DEPT_NAMES) and instead import the source-of-truth constants
(e.g., the exported event list and department list), then use or cast them to
WsEventType[] and DepartmentName[] where needed so the test always reflects the
canonical unions in the types module.

In `@web/src/components/layout/StatusBar.tsx`:
- Around line 10-16: Add an explicit "unknown" health state: extend the
SystemStatus union to include 'unknown' and add an entry for it in STATUS_CONFIG
(with an appropriate neutral color/label). Initialize the component's health
state to 'unknown' instead of 'ok' and change the polling/error-handling logic
that currently maps transport or polling errors to 'down' so that
transient/initial transport failures map to 'unknown' until at least one
successful poll determines 'ok', 'degraded', or 'down'.

In `@web/src/pages/dashboard/ActivityFeedItem.tsx`:
- Around line 46-55: Replace the hand-rolled status dot span in ActivityFeedItem
(the span using dotColor and aria-label with activity.action_type) with the
shared StatusBadge component: import StatusBadge, pass the mapped color/state
(use the existing dotColor or map activity.action_type -> status value
consistent with the app's status mapping), preserve the accessibility label
(aria-label={`Action: ${activity.action_type.replace(/[._]/g, ' ')}`}) and
styling (size/positioning) by forwarding equivalent props/className to
StatusBadge so the centralized color mapping and semantics are used instead of
the inline span.

In `@web/src/pages/dashboard/BudgetBurnChart.stories.tsx`:
- Around line 1-40: The story imports Meta/StoryObj from the wrong Storybook
package and omits the required accessibility test parameter: update the import
to the repo's Storybook package (e.g., replace imports of Meta and StoryObj from
'@storybook/react' with the project's chosen package such as
'@storybook/react-vite') and add a parameters.a11y.test entry to the exported
meta object (the meta constant for BudgetBurnChart) set to a permitted value
(for example 'error') so the meta now includes parameters: { a11y: { test:
'error' } } and the Story type imports match the repo's Storybook builder.
🪄 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: ebab9c63-ebe0-4f2c-83ae-c16245a6af0d

📥 Commits

Reviewing files that changed from the base of the PR and between 7b6a97b and 126ff66.

📒 Files selected for processing (32)
  • CLAUDE.md
  • docs/design/page-structure.md
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/api/endpoints/activities.ts
  • web/src/api/endpoints/company.ts
  • web/src/api/endpoints/index.ts
  • web/src/api/types.ts
  • web/src/components/layout/StatusBar.tsx
  • web/src/components/ui/metric-card.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/stores/analytics.ts
  • web/src/utils/dashboard.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{ts,tsx}: Validate TypeScript/JavaScript imports with explicit type checking; avoid implicit any types
Always reuse existing components from web/src/components/ui/ before creating new components (StatusBadge, MetricCard, Sparkline, SectionCard, AgentCard, DeptHealthBar, ProgressGauge, StatPill, Avatar, Button, Toast, Skeleton variants, EmptyState, ErrorBoundary, ConfirmDialog, CommandPalette, InlineEdit, AnimatedPresence, StaggerGroup/StaggerItem)
Use Tailwind semantic classes (text-foreground, bg-card, text-accent, text-success, bg-danger) and CSS variables (var(--so-accent)) for colors; never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono for typography (maps to Geist tokens); never set fontFamily directly in .tsx/.ts files
Use density-aware spacing tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing for layout; never hardcode pixel values for layout spacing in .tsx/.ts files
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows and borders instead of hardcoded values in .tsx/.ts files
Import cn from @/lib/utils for conditional class merging in React components
Do NOT recreate status dots inline; always use <StatusBadge> component
Do NOT build card-with-header layouts from scratch; always use <SectionCard> component
Do NOT create metric displays with inline text-metric font-bold classes; always use <MetricCard> component
Do NOT render initials circles manually; always use <Avatar> component
Do NOT create complex (>8 line) JSX inside .map() calls; extract to a shared component
Do NOT use rgba() with hardcoded values; always use design token variables in .tsx/.ts files
Use React 19 hooks and async component patterns in web dashboard files; React 19 supports Server Components (where applicable)
ESLint must pass @eslint-react/eslint-plugin checks in web dashboard files
Use Lucide React icons in web das...

Files:

  • web/src/api/endpoints/index.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/api/endpoints/activities.ts
  • web/src/api/endpoints/company.ts
  • web/src/components/ui/metric-card.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/api/types.ts
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/stores/analytics.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/utils/dashboard.ts
web/**/*.{ts,tsx,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Tailwind CSS 4 for styling web dashboard (configured in web/tailwind.config.ts)

Files:

  • web/src/api/endpoints/index.ts
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/api/endpoints/activities.ts
  • web/src/api/endpoints/company.ts
  • web/src/components/ui/metric-card.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/api/types.ts
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/stores/analytics.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/utils/dashboard.ts
web/src/__tests__/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/__tests__/**/*.{test,spec}.{ts,tsx}: Use @vitest/coverage-v8 for React test coverage in web dashboard unit tests
Use fast-check (fc.assert + fc.property) for property-based testing in React test files
Use @testing-library/react for component unit testing in web dashboard

Files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/__tests__/stores/analytics.test.ts
web/src/components/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/components/ui/**/*.{ts,tsx}: Place new shared components in web/src/components/ui/ with kebab-case filenames and create accompanying .stories.tsx file with all component states
Export component props as a TypeScript interface in UI component files

Files:

  • web/src/components/ui/metric-card.tsx
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Docs are in docs/ (Markdown, built with Zensical); design spec in docs/design/ (11 pages); architecture in docs/architecture/; roadmap in docs/roadmap/

Files:

  • docs/design/page-structure.md
web/src/**/*.stories.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.stories.{ts,tsx}: Use Storybook 10 ESM-only imports: storybook/test (not @storybook/test), storybook/actions (not @storybook/addon-actions)
Use parameters.backgrounds.options (object keyed by name) and initialGlobals.backgrounds.value in Storybook stories (Storybook 10 API change)
Set a11y testing with parameters.a11y.test: 'error' | 'todo' | 'off' (not .element and .manual) in Storybook stories

Files:

  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
🧠 Learnings (51)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use Zustand for web dashboard state management (stores in `web/src/stores/`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use React 19 hooks and async component patterns in web dashboard files; React 19 supports Server Components (where applicable)
📚 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/api/endpoints/index.ts
  • CLAUDE.md
  • web/src/pages/dashboard/OrgHealthSection.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/__tests__/**/*.{test,spec}.{ts,tsx} : Use `testing-library/react` for component unit testing in web dashboard

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/__tests__/stores/analytics.test.ts
📚 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/dashboard/DashboardSkeleton.test.tsx
  • CLAUDE.md
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/__tests__/**/*.{test,spec}.{ts,tsx} : Use `vitest/coverage-v8` for React test coverage in web dashboard unit tests

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/__tests__/stores/analytics.test.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Web dashboard testing: `npm --prefix web run test` (Vitest, coverage scoped to files changed vs origin/main)

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/__tests__/stores/analytics.test.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use React 19 hooks and async component patterns in web dashboard files; React 19 supports Server Components (where applicable)

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • CLAUDE.md
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : ESLint must pass `eslint-react/eslint-plugin` checks in web dashboard files

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/components/layout/StatusBar.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/utils/dashboard.test.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/__tests__/**/*.{test,spec}.{ts,tsx} : Use fast-check (`fc.assert` + `fc.property`) for property-based testing in React test files

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/utils/dashboard.test.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/components/ui/**/*.{ts,tsx} : Place new shared components in `web/src/components/ui/` with kebab-case filenames and create accompanying `.stories.tsx` file with all component states

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Always reuse existing components from `web/src/components/ui/` before creating new components (StatusBadge, MetricCard, Sparkline, SectionCard, AgentCard, DeptHealthBar, ProgressGauge, StatPill, Avatar, Button, Toast, Skeleton variants, EmptyState, ErrorBoundary, ConfirmDialog, CommandPalette, InlineEdit, AnimatedPresence, StaggerGroup/StaggerItem)

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/components/ui/metric-card.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Do NOT create metric displays with inline `text-metric font-bold` classes; always use `<MetricCard>` component

Applied to files:

  • web/src/components/ui/metric-card.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/components/ui/**/*.{ts,tsx} : Export component props as a TypeScript interface in UI component files

Applied to files:

  • web/src/components/ui/metric-card.tsx
  • web/src/api/types.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use Zustand for web dashboard state management (stores in `web/src/stores/`)

Applied to files:

  • CLAUDE.md
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/stores/analytics.ts
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Project uses src-layout: `src/synthorg/` for package code, `tests/` for tests, `web/` for React 19 dashboard, `cli/` for Go binary

Applied to files:

  • CLAUDE.md
📚 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-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).

Applied to files:

  • CLAUDE.md
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/pages/DashboardPage.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/utils/dashboard.ts
📚 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 web/package.json : Web dashboard Node.js 20+; dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, ESLint, vue-tsc)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to docs/**/*.md : Docs are in `docs/` (Markdown, built with Zensical); design spec in `docs/design/` (11 pages); architecture in `docs/architecture/`; roadmap in `docs/roadmap/`

Applied to files:

  • CLAUDE.md
📚 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-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.stories.{ts,tsx} : Use Storybook 10 ESM-only imports: `storybook/test` (not `storybook/test`), `storybook/actions` (not `storybook/addon-actions`)

Applied to files:

  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/__tests__/stores/analytics.test.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Update the relevant `docs/design/` page to reflect new reality when approved deviations occur

Applied to files:

  • docs/design/page-structure.md
📚 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 docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/page-structure.md
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use Recharts for charting and data visualization in web dashboard

Applied to files:

  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use Lucide React icons in web dashboard components; import from `lucide-react`

Applied to files:

  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/hooks/useDashboardData.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use Framer Motion for animations in web dashboard components

Applied to files:

  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/tsconfig.json : TypeScript 6.0+ required for web dashboard; configured in `web/tsconfig.json`

Applied to files:

  • web/src/api/types.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Do NOT recreate status dots inline; always use `<StatusBadge>` component

Applied to files:

  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/components/layout/StatusBar.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use `tanstack/react-query` for server state management in web dashboard API calls

Applied to files:

  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/DashboardPage.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/stores/analytics.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Web dashboard type-checking: `npm --prefix web run type-check` (TypeScript compiler)

Applied to files:

  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/tsconfig.json : Explicitly list required types in `tsconfig.json` types field; do not rely on auto-discovery (e.g., `"types": ["vitest/globals"]`)

Applied to files:

  • web/src/__tests__/utils/dashboard.property.test.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Do NOT use `rgba()` with hardcoded values; always use design token variables in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use Tailwind semantic classes (`text-foreground`, `bg-card`, `text-accent`, `text-success`, `bg-danger`) and CSS variables (`var(--so-accent)`) for colors; never hardcode hex values in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use token variables (`var(--so-shadow-card-hover)`, `border-border`, `border-bright`) for shadows and borders instead of hardcoded values in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/components/ui/**/*.stories.tsx : Create Storybook stories (`.stories.tsx` file) for every new shared component with all states (default, hover, loading, error, empty)

Applied to files:

  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.stories.{ts,tsx} : Use `parameters.backgrounds.options` (object keyed by name) and `initialGlobals.backgrounds.value` in Storybook stories (Storybook 10 API change)

Applied to files:

  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.stories.{ts,tsx} : Set a11y testing with `parameters.a11y.test: 'error' | 'todo' | 'off'` (not `.element` and `.manual`) in Storybook stories

Applied to files:

  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Web dashboard Storybook: `npm --prefix web run storybook` for dev server, `npm --prefix web run storybook:build` for production build

Applied to files:

  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
📚 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/pages/dashboard/DashboardPage.stories.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to tests/**/*.py : Never skip or ignore flaky tests; always fix them fully by mocking `time.monotonic()` and `asyncio.sleep()` or using `asyncio.Event().wait()` for indefinite blocks instead of large sleep values

Applied to files:

  • web/src/pages/dashboard/DashboardPage.stories.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Do NOT build card-with-header layouts from scratch; always use `<SectionCard>` component

Applied to files:

  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/.storybook/main.ts : Use TypeScript `defineMain` from `storybook/react-vite/node` and `definePreview` from `storybook/react-vite` in Storybook config files

Applied to files:

  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use density-aware spacing tokens (`p-card`, `gap-section-gap`, `gap-grid-gap`) or standard Tailwind spacing for layout; never hardcode pixel values for layout spacing in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/dashboard/OrgHealthSection.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Do NOT create complex (>8 line) JSX inside `.map()` calls; extract to a shared component

Applied to files:

  • web/src/pages/dashboard/OrgHealthSection.tsx
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/src/**/*.{ts,tsx} : Validate TypeScript/JavaScript imports with explicit type checking; avoid implicit `any` types

Applied to files:

  • web/src/pages/dashboard/BudgetBurnChart.stories.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/stores/analytics.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Applies to web/**/*.{ts,tsx,css} : Use Tailwind CSS 4 for styling web dashboard (configured in `web/tailwind.config.ts`)

Applied to files:

  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.809Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.809Z
Learning: Fix all valid issues found by review agents; never skip or defer valid issues including pre-existing issues in surrounding code and adjacent findings

Applied to files:

  • web/src/utils/dashboard.ts

Comment on lines +37 to +50
it('renders chart when data is provided', () => {
const { container } = render(
<BudgetBurnChart trendData={SAMPLE_TREND} forecast={SAMPLE_FORECAST} budgetTotal={500} />,
)
// Recharts renders an SVG
expect(container.querySelector('svg')).toBeInTheDocument()
})

it('renders without forecast', () => {
const { container } = render(
<BudgetBurnChart trendData={SAMPLE_TREND} forecast={null} budgetTotal={500} />,
)
expect(container.querySelector('svg')).toBeInTheDocument()
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The chart-rendering assertions are false positives.

container.querySelector('svg') can succeed on the header icon alone, so these tests still pass if the plot never renders. Assert against a chart-specific hook instead.

🎯 Make the assertion chart-specific
   it('renders chart when data is provided', () => {
-    const { container } = render(
+    render(
       <BudgetBurnChart trendData={SAMPLE_TREND} forecast={SAMPLE_FORECAST} budgetTotal={500} />,
     )
-    // Recharts renders an SVG
-    expect(container.querySelector('svg')).toBeInTheDocument()
+    expect(screen.getByTestId('budget-burn-chart')).toBeInTheDocument()
   })

   it('renders without forecast', () => {
-    const { container } = render(
+    render(
       <BudgetBurnChart trendData={SAMPLE_TREND} forecast={null} budgetTotal={500} />,
     )
-    expect(container.querySelector('svg')).toBeInTheDocument()
+    expect(screen.getByTestId('budget-burn-chart')).toBeInTheDocument()
   })
// In BudgetBurnChart.tsx
<div data-testid="budget-burn-chart">
  {/* chart body */}
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx` around lines 37 -
50, The svg presence check is a false positive because other page svgs (like
header icons) satisfy container.querySelector('svg'); update the chart component
BudgetBurnChart to wrap the rendered chart body with a unique data-testid (e.g.,
data-testid="budget-burn-chart") and then change the tests in
BudgetBurnChart.test.tsx to assert on that test id (use getByTestId or
container.querySelector('[data-testid="budget-burn-chart"]')) for both the
forecast and no-forecast cases so the assertions are chart-specific.

Comment on lines 76 to +119
<StatusItem>
<Dot color="bg-accent" />
<span>-- agents</span>
<span>{totalAgents} agents</span>
</StatusItem>

<StatusItem>
<Dot color="bg-success" />
<span>-- active</span>
<span>{activeAgents} active</span>
</StatusItem>

<StatusItem>
<Dot color="bg-warning" />
<span>-- tasks</span>
<span>{totalTasks} tasks</span>
</StatusItem>

<Divider />

<StatusItem>
<span className="text-muted-foreground">spend</span>
<span className="ml-1.5 text-foreground">$--</span>
<span className="ml-1.5 text-foreground">{costDisplay}</span>
<span className="ml-1 text-muted-foreground">today</span>
</StatusItem>

<StatusItem>
<span className="text-muted-foreground">budget</span>
<span className="ml-1.5 text-foreground">
{budgetPercent !== undefined && budgetPercent !== null
? `${Math.round(budgetPercent)}%`
: '--%'}
</span>
</StatusItem>

{pendingApprovals > 0 && (
<StatusItem>
<Dot color="bg-danger" />
<span>{pendingApprovals} pending</span>
</StatusItem>
)}

<div className="flex-1" />

<StatusItem>
<Dot color="bg-success" />
<span className="text-muted-foreground">all systems nominal</span>
<Dot color={statusCfg.color} />
<span className="text-muted-foreground">{statusCfg.label}</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use the shared StatusBadge for these indicators.

The new status items keep extending the local dot pattern instead of the shared badge component, so this bar drifts from the rest of the dashboard UI.

As per coding guidelines, "Do NOT recreate status dots inline; always use <StatusBadge> component".

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

In `@web/src/components/layout/StatusBar.tsx` around lines 76 - 119, Several
status indicators in StatusBar.tsx (the StatusItem blocks using the Dot
component and spans for counts/labels, e.g., the agents/active/tasks/pending
approvals and final statusCfg display) recreate the local dot pattern instead of
using the shared StatusBadge; replace each use of <Dot ... /> plus adjacent
spans with the shared <StatusBadge /> component (pass the color/status label and
any count text or children as props) and ensure the conditional pendingApprovals
rendering still uses StatusBadge; update references where statusCfg is used so
you render <StatusBadge color={statusCfg.color} label={statusCfg.label} /> (or
the component's API) to keep styling consistent across the dashboard.

Comment on lines +69 to +76
const NEAR_BUDGET_TREND = TREND_DATA.map((p) => ({ ...p, value: p.value * 10 }))
const NEAR_BUDGET_FORECAST = {
...FORECAST,
daily_projections: FORECAST.daily_projections.map((p) => ({
...p,
projected_spend_usd: p.projected_spend_usd * 10,
})),
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep NearBudget internally consistent.

Only daily_projections are scaled here. projected_total_usd, avg_daily_spend_usd, and days_until_exhausted still describe the base scenario, so the chart can disagree with the summary callouts in the same story.

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

In `@web/src/pages/dashboard/BudgetBurnChart.stories.tsx` around lines 69 - 76,
NEAR_BUDGET_TREND and NEAR_BUDGET_FORECAST are partially scaled (only
daily_projections), causing the chart and summary callouts to disagree; update
NEAR_BUDGET_FORECAST so projected_total_usd and avg_daily_spend_usd are scaled
the same way you scaled daily_projections (use the same multiplier applied to
TREND_DATA), and then recompute days_until_exhausted from those scaled values
(e.g., days_until_exhausted = projected_total_usd / avg_daily_spend_usd) to keep
NEAR_BUDGET_TREND, NEAR_BUDGET_FORECAST, TREND_DATA and FORECAST internally
consistent.

Comment on lines +68 to +83
let departmentHealths: DepartmentHealth[] = []
try {
const deptResult = await listDepartments({ limit: 100 })
const healthPromises = deptResult.data.map((dept) =>
getDepartmentHealth(dept.name).catch((err: unknown) => {
console.warn(`Failed to fetch health for ${dept.name}:`, err)
return null
}),
)
const healthResults = await Promise.all(healthPromises)
departmentHealths = healthResults.filter(
(h): h is DepartmentHealth => h !== null,
)
} catch (err) {
console.warn('Failed to fetch department list:', err)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if listDepartments supports pagination and if there's a max limit
rg -n 'listDepartments' --type=ts -A 5 -B 2

# Check the API types for pagination
ast-grep --pattern 'interface $_ {
  $$$
  limit$_
  $$$
}'

Repository: Aureliolo/synthorg

Length of output: 5305


🏁 Script executed:

#!/bin/bash
# Find PaginationParams type definition
rg -n 'type PaginationParams|interface PaginationParams' --type=ts -A 5

# Check if there's any pagination logic in analytics.ts or company.ts
rg -n 'offset|page' web/src/stores/analytics.ts web/src/api/endpoints/company.ts

# Look for any fetching all pages logic
rg -n 'while.*offset|for.*page|Promise.all.*listDepartments' --type=ts

Repository: Aureliolo/synthorg

Length of output: 957


🏁 Script executed:

#!/bin/bash
# Find PaginatedResult type to see what fields are available
rg -n 'type PaginatedResult|interface PaginatedResult' --type=ts -A 10

# Check what happens with deptResult in analytics.ts after the API call
rg -n 'deptResult\.' web/src/stores/analytics.ts -B 2 -A 2

# Look for any TODOs or comments about departments limit
rg -n 'TODO|FIXME|XXX|department.*limit|100.*department' --type=ts -i

Repository: Aureliolo/synthorg

Length of output: 11216


🏁 Script executed:

#!/bin/bash
# Look for pagination loops or patterns that fetch all pages
rg -n 'while.*offset|for.*offset|let offset|total.*data\.length|offset.*+=' --type=ts -B 2 -A 3

# Check if any endpoint has special handling for all-results fetching
rg -n 'fetchAll|getAll.*department' --type=ts -i

Repository: Aureliolo/synthorg

Length of output: 2277


Pagination not implemented—departments beyond 100 will be excluded.

The listDepartments({ limit: 100 }) call fetches only the first page. Organizations with more than 100 departments will have incomplete health data and an inaccurate overall gauge, since deptResult.total is available but never checked.

Consider implementing pagination to fetch all departments, increasing the limit to a dashboard-appropriate ceiling, or documenting this constraint if intentional.

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

In `@web/src/stores/analytics.ts` around lines 68 - 83, The department fetch uses
listDepartments({ limit: 100 }) which only gets the first page so
departmentHealths (and the overall gauge) will be incomplete for orgs with >100
departments; update the logic around listDepartments and deptResult to implement
pagination (or repeatedly call listDepartments with an offset/page token until
you've fetched all items using deptResult.total or the returned next-page
token), aggregating results before mapping to getDepartmentHealth and assigning
departmentHealths, or explicitly document the 100-department limit if that
ceiling is intentional; key symbols: listDepartments, deptResult.total (or
next-page token), getDepartmentHealth, and departmentHealths.

Aureliolo and others added 7 commits March 27, 2026 09:41
Implement the flagship Dashboard page (#777) -- the first thing users see
after login. Composes from existing design system components (MetricCard,
SectionCard, DeptHealthBar, ProgressGauge, StaggerGroup) and adds:

- 4 metric cards: tasks, active agents, spend (with sparkline), pending approvals
- Org health section: department health bars with overall gauge
- Activity feed: real-time WebSocket-driven timeline of agent actions
- Budget burn chart: Recharts area chart with forecast projection overlay
- StatusBar wiring: live agent/task/spend counts from analytics store
- Loading skeletons, error boundaries, and empty states per section
- Analytics Zustand store with parallel data fetching and WS event handling
- useDashboardData hook: initial fetch + 30s polling + 5-channel WebSocket
- Pure data transformation utilities with property-based tests (fast-check)
- 11 test files (81 tests including property-based), 4 Storybook stories

Closes #777

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-reviewed by 6 agents (docs-consistency, frontend-reviewer,
api-contract-drift, pr-test-analyzer, issue-resolution-verifier,
type-design-analyzer), 20 findings addressed:

- Fix immutability: replace mutation in BudgetBurnChart buildChartData
- Fix computeOrgHealth to return null for empty depts (not 0)
- Fix computeSpendTrend to return undefined for 0% change
- Use readonly arrays in AnalyticsState and component props
- Replace DashboardMetricCardData with Omit<MetricCardProps, 'className'>
- Replace ReturnType extraction in UseDashboardDataReturn with explicit types
- Export MetricCardProps from metric-card.tsx
- Add colored action-type dots to ActivityFeedItem
- Add "Today" reference line to BudgetBurnChart
- Add remaining budget callout (StatPill) to BudgetBurnChart
- Add cost per department display in OrgHealthSection
- Add budget % and pending approvals to StatusBar
- Add WebSocket disconnect warning banner in DashboardPage
- Add 15 missing test cases: store degradation, dept health happy path,
  describeEvent fallback, assigned_to fallback, cost clamping, sparkline
  omission, StatusBar budget/approvals/cost placeholder, BudgetBurnChart
  days_until_exhausted, WS disconnect banner
- Update docs/design/page-structure.md Dashboard API endpoints
- Update CLAUDE.md: API module count (19) and stores description

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

- Fix 4 invalid Tailwind classes (bg-bg-card, ring-bg-card, bg-text-muted, text-text-muted)
- Switch analytics store from Promise.all to Promise.allSettled for graceful per-section degradation
- Add error boundary to updateFromWsEvent preventing malformed WS events from crashing the feed
- Fix UTC date parsing bug in BudgetBurnChart causing day-label shift in US timezones
- Rename PENDING APPROVALS card to IN REVIEW to match actual data source
- Use formatCurrency in StatusBar for consistent currency display
- Improve activity ID uniqueness with monotonic counter
- Add null guard for WS event payload
- Type-safety improvements: ACTION_DOT_COLORS and EVENT_DESCRIPTIONS use Partial<Record<WsEventType, string>>,
  DASHBOARD_CHANNELS as const, computeSpendTrend/computeOrgHealth accept readonly arrays
- Fix weak test assertion (container truthy -> queryByRole link)
- Replace as never type casts with proper mock helpers in 4 test files
- Add missing test coverage: wsSetupError, formatCurrency display, dept cost rendering,
  partial dept health failure, forecast/budgetConfig graceful degradation
- Fix DashboardSkeleton data-testid forwarding (wrap in div)
- Fix EmptyOrg story to set overview: null
- Extract NearBudget story constants for readability
- Document ESLint suppress reason in StatusBar useEffect
- Update CLAUDE.md pages/ description for feature subdirs pattern
- Fix page-structure.md sparklines reference (/analytics/trends -> /analytics/overview)
- Pass limit: 100 to listDepartments to avoid pagination truncation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract DepartmentRow component from OrgHealthSection .map() (>8 line JSX rule)
- Make computeMetricCards work without budgetConfig -- show TASKS, AGENTS, IN REVIEW
  cards even when budget endpoint fails (budgetConfig is now nullable)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The rebase picked up a new `currency` field on OverviewMetrics, BudgetConfig,
and ForecastResponse (default: EUR). Wire it through all formatCurrency call
sites: computeMetricCards uses overview.currency, StatusBar reads currency
from the analytics store, BudgetBurnChart and OrgHealthSection accept a
currency prop passed from DashboardPage.

All mocks and stories use EUR (the project default). Added varied-currency
tests (GBP, JPY) in StatusBar, OrgHealthSection, and dashboard utils to
verify the currency field is respected end-to-end.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The rebase picked up a new `currency` field on OverviewMetrics, BudgetConfig,
and ForecastResponse (default: EUR). Wire it through all formatCurrency call
sites: computeMetricCards uses overview.currency, StatusBar reads currency
from the analytics store, BudgetBurnChart and OrgHealthSection accept a
currency prop passed from DashboardPage.

All mocks and stories use EUR (the project default). Added varied-currency
tests (GBP, JPY) in StatusBar, OrgHealthSection, and dashboard utils.
Replaced Date.now()-based story timestamps with fixed ISO strings for
deterministic visual regression output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add data-testid="budget-burn-chart" to chart wrapper; update tests to use it
  instead of generic SVG query
- StatusBar: add 'unknown' health state (shows "checking..." before first poll),
  replace ?? 0 fallbacks with undefined + "--" placeholders when data not loaded
- ActivityFeed: wrap feed in role="log" aria-live="polite" for screen reader
  notifications on new items
- ActivityFeed.stories: use deterministic base timestamp instead of Date.now()
- BudgetBurnChart: immutable points array in buildChartData (no mutation)
- BudgetBurnChart.stories: scale projected_total_usd, avg_daily_spend_usd, and
  days_until_exhausted consistently in NearBudget story
- Validate department name against known DepartmentName set in
  wsEventToActivityItem instead of blind type assertion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo force-pushed the feat/dashboard-page branch from 126ff66 to fab0c67 Compare March 27, 2026 08:41
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 08:42 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (12)
web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx (1)

17-21: 🧹 Nitpick | 🔵 Trivial

Consider adding a test for the chart skeleton section.

The test suite covers metrics and sections rows but doesn't assert the chart skeleton (skeleton-chart) is present. Adding this would provide complete structural coverage.

♻️ Suggested addition
   it('renders sections row with 2 skeleton cards', () => {
     render(<DashboardSkeleton />)
     const sectionsRow = screen.getByTestId('skeleton-sections-row')
     expect(sectionsRow.children).toHaveLength(2)
   })
+
+  it('renders chart skeleton', () => {
+    render(<DashboardSkeleton />)
+    expect(screen.getByTestId('skeleton-chart')).toBeInTheDocument()
+  })
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx` around lines 17
- 21, The test currently checks metrics and sections rows but misses asserting
the chart skeleton; update the DashboardSkeleton.test to render
<DashboardSkeleton /> and add an assertion that
screen.getByTestId('skeleton-chart') exists (or use
expect(...).toBeInTheDocument()) so the chart skeleton is covered alongside the
existing checks for DashboardSkeleton and the 'skeleton-sections-row' children
length.
web/src/__tests__/hooks/useDashboardData.test.ts (2)

84-99: ⚠️ Potential issue | 🟡 Minor

Make the WebSocket channel assertion exact.

Line 88-98 uses arrayContaining, so extra unintended bindings would still pass. Assert exact channel list (and size).

🎯 Tighten assertion
   it('sets up WebSocket with 5 channel bindings', async () => {
     const { useWebSocket } = await import('@/hooks/useWebSocket')
     renderHook(() => useDashboardData())
 
-    expect(useWebSocket).toHaveBeenCalledWith(
-      expect.objectContaining({
-        bindings: expect.arrayContaining([
-          expect.objectContaining({ channel: 'tasks' }),
-          expect.objectContaining({ channel: 'agents' }),
-          expect.objectContaining({ channel: 'budget' }),
-          expect.objectContaining({ channel: 'system' }),
-          expect.objectContaining({ channel: 'approvals' }),
-        ]),
-      }),
-    )
+    const call = vi.mocked(useWebSocket).mock.calls[0]?.[0]
+    const channels = call?.bindings.map((b) => b.channel)
+    expect(channels).toEqual(['tasks', 'agents', 'budget', 'system', 'approvals'])
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/hooks/useDashboardData.test.ts` around lines 84 - 99, The
test currently uses expect.arrayContaining which allows extra bindings; update
the assertion in the 'sets up WebSocket with 5 channel bindings' test to assert
exact channels and size: after rendering useDashboardData, access the argument
passed to useWebSocket (the bindings array) and assert that bindings.map(b =>
b.channel) strictly equals the exact array
['tasks','agents','budget','system','approvals'] (using toEqual) to ensure no
extra channels are present; keep references to useDashboardData, useWebSocket
and the bindings/channel properties when locating and changing the assertion.

106-111: ⚠️ Potential issue | 🟡 Minor

Avoid late remocking usePolling inside the test body.

Line 107-111 remocks after module-level setup; this can be brittle with module caching. Prefer configuring usePolling in shared setup (beforeEach) or with mockReturnValueOnce before renderHook.

♻️ More stable mock setup
+import { usePolling } from '@/hooks/usePolling'
+
+const mockPollingStart = vi.fn()
+
 describe('useDashboardData', () => {
   beforeEach(() => {
     vi.clearAllMocks()
     resetStore()
+    mockPollingStart.mockReset()
+    vi.mocked(usePolling).mockReturnValue({
+      active: false,
+      error: null,
+      start: mockPollingStart,
+      stop: vi.fn(),
+    })
   })
@@
   it('starts polling on mount', async () => {
-    const { usePolling } = await import('@/hooks/usePolling')
-    const mockStart = vi.fn()
-    vi.mocked(usePolling).mockReturnValue({
-      active: false, error: null, start: mockStart, stop: vi.fn(),
-    })
-
     renderHook(() => useDashboardData())
     await waitFor(() => {
-      expect(mockStart).toHaveBeenCalled()
+      expect(mockPollingStart).toHaveBeenCalled()
     })
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/hooks/useDashboardData.test.ts` around lines 106 - 111, The
test remocks usePolling late inside the test body which is brittle; move the
mock setup for usePolling (the vi.mocked(usePolling).mockReturnValue({...}) call
that returns active/error/start/stop and mockStart) into a shared beforeEach, or
if you need per-test behavior use vi.mocked(usePolling).mockReturnValueOnce(...)
immediately before calling renderHook so the hook sees the mock on first import;
also ensure mocks are reset/cleared between tests (clearAllMocks or resetMocks)
so mockStart and other mocked return values don’t leak across tests.
web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx (1)

40-45: ⚠️ Potential issue | 🟡 Minor

Use property-based testing for the 10-item cap invariant.

Line 40-45 validates only one input size (15). Convert this to fc.assert(fc.property(...)) so the cap guarantee is exercised across arbitrary list sizes.

♻️ Proposed update
+import * as fc from 'fast-check'
@@
-  it('caps displayed items at 10', () => {
-    renderWithRouter(<ActivityFeed activities={makeActivities(15)} />)
-    // Only 10 should render
-    expect(screen.queryByText('agent-10')).not.toBeInTheDocument()
-    expect(screen.getByText('agent-9')).toBeInTheDocument()
-  })
+  it('caps displayed items at 10', () => {
+    fc.assert(
+      fc.property(fc.integer({ min: 0, max: 200 }), (count) => {
+        const { unmount } = renderWithRouter(<ActivityFeed activities={makeActivities(count)} />)
+        const expectedVisible = Math.min(count, 10)
+
+        for (let i = 0; i < expectedVisible; i += 1) {
+          expect(screen.getByText(`agent-${i}`)).toBeInTheDocument()
+        }
+        if (count > 10) {
+          expect(screen.queryByText('agent-10')).not.toBeInTheDocument()
+          expect(screen.getByText('agent-9')).toBeInTheDocument()
+        }
+        unmount()
+      }),
+    )
+  })

As per coding guidelines: "Use fast-check (fc.assert + fc.property) for property-based testing in React test files".

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

In `@web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx` around lines 40 -
45, Replace the fixed-size test in ActivityFeed.test.tsx with a property-based
assertion: use fast-check's fc.assert and fc.property to generate arbitrary list
sizes (e.g., fc.integer({min:0, max:100})) and for each size call
makeActivities(size), renderWithRouter(<ActivityFeed activities={...}/>), then
assert the rendered count is <= 10 by using screen.getAllByRole or equivalent
DOM selector; update the test that references ActivityFeed and makeActivities to
use fc.assert(fc.property(...)) so the 10-item cap invariant is exercised across
many input sizes.
web/src/__tests__/components/layout/StatusBar.test.tsx (1)

130-143: ⚠️ Potential issue | 🟡 Minor

Verify the exact currency output, not just the digits.

Both assertions still pass if StatusBar renders the wrong symbol or precision. Use formatCurrency(...) in the expectations so the test follows the same formatting path as the component.

💚 Suggested fix
 import { render, screen } from '@testing-library/react'
 import { useAnalyticsStore } from '@/stores/analytics'
 import { StatusBar } from '@/components/layout/StatusBar'
+import { formatCurrency } from '@/utils/format'
 import type { OverviewMetrics } from '@/api/types'
@@
   it('shows formatted currency for cost display (EUR default)', () => {
     useAnalyticsStore.setState({
       overview: makeOverview({ total_cost_usd: 1234.56 }),
     })
     render(<StatusBar />)
-    expect(screen.getByText(/1,234\.56/)).toBeInTheDocument()
+    expect(screen.getByText(formatCurrency(1234.56))).toBeInTheDocument()
   })
@@
   it('shows formatted currency for non-default currency', () => {
     useAnalyticsStore.setState({
       overview: makeOverview({ total_cost_usd: 99.5, currency: 'GBP' }),
     })
     render(<StatusBar />)
-    expect(screen.getByText(/99\.50/)).toBeInTheDocument()
+    expect(screen.getByText(formatCurrency(99.5, 'GBP'))).toBeInTheDocument()
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/components/layout/StatusBar.test.tsx` around lines 130 -
143, Tests currently assert only numeric digits for currency which can pass
despite wrong symbol/precision; update both tests in StatusBar.test.tsx to call
formatCurrency(...) with the same inputs used in useAnalyticsStore.setState
(e.g., makeOverview({ total_cost_usd: 1234.56 }) and makeOverview({
total_cost_usd: 99.5, currency: 'GBP' })) and assert
screen.getByText(formatCurrency(...)) so the expectation matches the component's
formatting logic; reference the StatusBar component, useAnalyticsStore.setState,
makeOverview and the formatCurrency helper when making the change.
web/src/__tests__/utils/dashboard.property.test.ts (1)

10-25: 🧹 Nitpick | 🔵 Trivial

Stop duplicating the event and department source-of-truth lists.

These literals only prove that each element is valid, not that the unions are fully covered. If WsEventType or DepartmentName grows, this suite can silently stop exercising the new members. Import canonical as const lists from @/api/types, or export them there and reuse them here.

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

In `@web/src/__tests__/utils/dashboard.property.test.ts` around lines 10 - 25, The
test duplicates source-of-truth literal arrays (WS_EVENT_TYPES, DEPT_NAMES)
instead of reusing the canonical lists for WsEventType and DepartmentName;
remove these local literal definitions and import the canonical as‑const arrays
(e.g., export or locate EVENT_TYPES_LIST and DEPARTMENT_NAMES_LIST) from the
shared types module (import from "@/api/types"), then update the test to iterate
those imported arrays (keeping any type assertions) so the test continues to
validate every union member as the authoritative source changes.
web/src/pages/dashboard/ActivityFeedItem.tsx (1)

46-54: 🛠️ Refactor suggestion | 🟠 Major

Use the shared StatusBadge instead of the overlay <span>.

This is another local reimplementation of the dashboard status dot. Swapping it to StatusBadge keeps the indicator styling and accessibility behavior centralized. As per coding guidelines, "Do NOT recreate status dots inline; always use <StatusBadge> component".

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

In `@web/src/pages/dashboard/ActivityFeedItem.tsx` around lines 46 - 54, Replace
the inline status dot <span> in ActivityFeedItem with the shared StatusBadge
component: remove the absolute <span> that uses dotColor and the aria-label, and
render <StatusBadge> positioned the same way (near Avatar) passing the
equivalent status/color prop (derived from dotColor or activity.action_type) and
the aria-label built from activity.action_type.replace(/[._]/g, ' '). Ensure you
import StatusBadge and preserve the existing positioning classes (absolute
-bottom-0.5 -right-0.5) and ring/card styling by mapping them into StatusBadge
props or wrapper classes so behavior and accessibility remain identical.
web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx (1)

44-53: ⚠️ Potential issue | 🟡 Minor

Assert the exact formatted currency here.

These regexes only verify the numeric substring, so the JPY case still passes if the symbol or decimal precision is wrong. Use formatCurrency(...) so the test matches the component contract exactly.

💚 Suggested fix
 import { render, screen } from '@testing-library/react'
 import { OrgHealthSection } from '@/pages/dashboard/OrgHealthSection'
 import type { DepartmentHealth } from '@/api/types'
+import { formatCurrency } from '@/utils/format'
@@
   it('renders department cost when cost_usd is non-null (EUR default)', () => {
     const depts = makeDepts(1).map((d) => ({ ...d, cost_usd: 24.5 }))
     render(<OrgHealthSection departments={depts} overallHealth={80} />)
-    expect(screen.getByText(/24\.50/)).toBeInTheDocument()
+    expect(screen.getByText(formatCurrency(24.5))).toBeInTheDocument()
   })
@@
   it('renders department cost in specified currency', () => {
     const depts = makeDepts(1).map((d) => ({ ...d, cost_usd: 100 }))
     render(<OrgHealthSection departments={depts} overallHealth={80} currency="JPY" />)
-    expect(screen.getByText(/100/)).toBeInTheDocument()
+    expect(screen.getByText(formatCurrency(100, 'JPY'))).toBeInTheDocument()
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx` around lines 44
- 53, The test is only asserting the numeric substring; update the JPY test to
assert the exact formatted currency string by calling the shared formatter used
by the component: import and use formatCurrency when building the expected value
(e.g. const expected = formatCurrency(100, 'JPY') ) and then assert
expect(screen.getByText(expected)).toBeInTheDocument(); do the same for the
EUR/default test (use formatCurrency(24.5, 'EUR') or formatCurrency(24.5) to
match OrgHealthSection's default) so the test verifies symbol and precision
exactly for OrgHealthSection.
web/src/pages/dashboard/OrgHealthSection.stories.tsx (1)

16-27: 🛠️ Refactor suggestion | 🟠 Major

Add the required Storybook a11y policy to this meta.

This story meta still omits the explicit accessibility policy required for Storybook 10 in this repo.

💚 Suggested fix
 const meta = {
   title: 'Dashboard/OrgHealthSection',
   component: OrgHealthSection,
   tags: ['autodocs'],
+  parameters: {
+    a11y: { test: 'error' },
+  },
   decorators: [
     (Story) => (
       <div className="max-w-md">
As per coding guidelines, "Set a11y testing with `parameters.a11y.test: 'error' | 'todo' | 'off'` (not `.element` and `.manual`) in Storybook stories".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/dashboard/OrgHealthSection.stories.tsx` around lines 16 - 27,
The story meta for OrgHealthSection is missing the required Storybook a11y
policy; update the meta object (named meta) to include a parameters.a11y.test
entry set to one of 'error' | 'todo' | 'off' (instead of using .element or
.manual). Locate the meta constant in OrgHealthSection.stories.tsx and
add/modify parameters.a11y.test accordingly so Storybook 10 a11y testing is
explicitly configured.
web/src/pages/dashboard/DashboardPage.stories.tsx (1)

84-96: 🛠️ Refactor suggestion | 🟠 Major

Add the required Storybook a11y policy to this meta.

This story still relies on defaults, so it can bypass the repo’s WCAG gate. Set parameters.a11y.test explicitly on the meta.

💚 Suggested fix
 const meta = {
   title: 'Pages/Dashboard',
   component: DashboardPage,
+  parameters: {
+    a11y: { test: 'error' },
+  },
   decorators: [
     (Story) => (
       <MemoryRouter>
As per coding guidelines, "Set a11y testing with `parameters.a11y.test: 'error' | 'todo' | 'off'` (not `.element` and `.manual`) in Storybook stories".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/dashboard/DashboardPage.stories.tsx` around lines 84 - 96, The
Storybook meta for DashboardPage is missing an explicit a11y policy; update the
meta object (the `meta` constant for DashboardPage) to include a
parameters.a11y.test key set to one of the allowed values ('error' | 'todo' |
'off') so the story no longer relies on defaults and conforms to the repo WCAG
gate.
web/src/components/layout/StatusBar.tsx (2)

78-121: 🛠️ Refactor suggestion | 🟠 Major

Use StatusBadge for these indicators.

These items still hand-roll the dot + label pattern, so the top bar is bypassing the shared status primitive. Replace the local dot composition with StatusBadge to keep styling and semantics centralized. As per coding guidelines, "Do NOT recreate status dots inline; always use <StatusBadge> component".

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

In `@web/src/components/layout/StatusBar.tsx` around lines 78 - 121, The StatusBar
currently hand-rolls Dot + label pairs (e.g., the StatusItem blocks showing
agents, active, tasks, the pendingApprovals block, and the final status item
using statusCfg) — replace those usages with the shared StatusBadge component to
centralize styling and semantics: remove Dot and its adjacent span labels and
render <StatusBadge> (or the project's StatusBadge API) passing the color (from
Dot.color or statusCfg.color) and the label text (e.g., `${totalAgents} agents`,
`${activeAgents} active`, `${totalTasks} tasks`, `{pendingApprovals} pending`,
and statusCfg.label); preserve conditional rendering (dataLoaded and
pendingApprovals checks), and keep surrounding StatusItem wrappers and any
auxiliary text (spend/budget) unchanged.

40-46: ⚠️ Potential issue | 🟠 Major

Don’t translate poll failures into system down.

A transient network error now flips the indicator to system down, even when backend health is simply unknown. Preserve the last known state, or fall back to unknown, and reserve down for an actual health payload.

💚 Suggested fix
   } catch {
-      setHealthStatus('down')
+      setHealthStatus((prev) => (prev === 'unknown' ? 'unknown' : prev))
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/layout/StatusBar.tsx` around lines 40 - 46, In pollHealth,
avoid mapping network/fetch errors to 'down' — in the catch block for
getHealth() either leave the existing state unchanged or explicitly set it to
'unknown' instead of calling setHealthStatus('down'); update the catch in the
pollHealth function (which calls getHealth and setHealthStatus and works with
the HealthStatus type) to preserve last known status or assign 'unknown' so that
only a real health payload can set 'down'.
🤖 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/DashboardPage.test.tsx`:
- Around line 6-11: The helper makeTasksByStatus uses an unsafe cast and accepts
arbitrary string keys; change its parameter type from Partial<Record<string,
number>> to Partial<OverviewMetrics['tasks_by_status']> and update the function
return type to OverviewMetrics['tasks_by_status'] (or let TS infer it) so you
can drop the force-cast "as OverviewMetrics['tasks_by_status']" and ensure
type-safe keys matching the status shape used throughout tests.

In `@web/src/api/types.ts`:
- Around line 485-490: The ActivityItem interface currently types action_type as
WsEventType which is too restrictive for the /activities REST payloads; change
ActivityItem.action_type to a broader string (or a union that includes string)
so arbitrary backend event_type values are accepted, then optionally introduce a
KnownActivityActionType = WsEventType for UI-only normalization; update any
usages of ActivityItem.action_type and places expecting WsEventType to handle
the broader string type or perform runtime normalization using
KnownActivityActionType.

In `@web/src/components/layout/StatusBar.tsx`:
- Around line 31-37: StatusBar mounts call to fetchOverview via useEffect causes
duplicate requests because useAnalyticsStore.fetchOverview lacks in-flight
coalescing; to fix, either remove the mount-time call in StatusBar (delete the
useEffect block in StatusBar.tsx that calls
useAnalyticsStore.getState().fetchOverview) so the dashboard bootstrap
(useDashboardData) remains the single owner of initial load, or add
de-duplication inside the store by introducing an inFlight flag (e.g.,
analyticsStore.isFetchingOverview) checked/set in fetchOverview() so subsequent
callers (StatusBar.useEffect and useDashboardData) return early when a fetch is
already underway; reference useEffect in StatusBar.tsx,
useAnalyticsStore.getState(), state.overview/state.loading,
state.fetchOverview(), and useDashboardData when applying the change.

In `@web/src/pages/dashboard/ActivityFeedItem.tsx`:
- Around line 12-32: Add explicit color entries for the remaining WsEventType
values (so they don't fall back to 'bg-muted-foreground') by extending
ACTION_DOT_COLORS: include a mapping for 'message.sent' and for every concrete
meeting.* and coordination.* event variant defined in WsEventType (e.g.,
meeting.created/updated/canceled or whatever specific meeting and coordination
enum members exist) with the appropriate color classes (choose bg-accent for
informational events like messages, bg-success for positive events,
bg-warning/bg-danger for warnings/errors as appropriate). Modify the
ACTION_DOT_COLORS object (not getActionDotColor) to include those keys so
getActionDotColor(ACTION) returns the intended class for message.sent, meeting.*
and coordination.* events.

---

Duplicate comments:
In `@web/src/__tests__/components/layout/StatusBar.test.tsx`:
- Around line 130-143: Tests currently assert only numeric digits for currency
which can pass despite wrong symbol/precision; update both tests in
StatusBar.test.tsx to call formatCurrency(...) with the same inputs used in
useAnalyticsStore.setState (e.g., makeOverview({ total_cost_usd: 1234.56 }) and
makeOverview({ total_cost_usd: 99.5, currency: 'GBP' })) and assert
screen.getByText(formatCurrency(...)) so the expectation matches the component's
formatting logic; reference the StatusBar component, useAnalyticsStore.setState,
makeOverview and the formatCurrency helper when making the change.

In `@web/src/__tests__/hooks/useDashboardData.test.ts`:
- Around line 84-99: The test currently uses expect.arrayContaining which allows
extra bindings; update the assertion in the 'sets up WebSocket with 5 channel
bindings' test to assert exact channels and size: after rendering
useDashboardData, access the argument passed to useWebSocket (the bindings
array) and assert that bindings.map(b => b.channel) strictly equals the exact
array ['tasks','agents','budget','system','approvals'] (using toEqual) to ensure
no extra channels are present; keep references to useDashboardData, useWebSocket
and the bindings/channel properties when locating and changing the assertion.
- Around line 106-111: The test remocks usePolling late inside the test body
which is brittle; move the mock setup for usePolling (the
vi.mocked(usePolling).mockReturnValue({...}) call that returns
active/error/start/stop and mockStart) into a shared beforeEach, or if you need
per-test behavior use vi.mocked(usePolling).mockReturnValueOnce(...) immediately
before calling renderHook so the hook sees the mock on first import; also ensure
mocks are reset/cleared between tests (clearAllMocks or resetMocks) so mockStart
and other mocked return values don’t leak across tests.

In `@web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx`:
- Around line 40-45: Replace the fixed-size test in ActivityFeed.test.tsx with a
property-based assertion: use fast-check's fc.assert and fc.property to generate
arbitrary list sizes (e.g., fc.integer({min:0, max:100})) and for each size call
makeActivities(size), renderWithRouter(<ActivityFeed activities={...}/>), then
assert the rendered count is <= 10 by using screen.getAllByRole or equivalent
DOM selector; update the test that references ActivityFeed and makeActivities to
use fc.assert(fc.property(...)) so the 10-item cap invariant is exercised across
many input sizes.

In `@web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx`:
- Around line 17-21: The test currently checks metrics and sections rows but
misses asserting the chart skeleton; update the DashboardSkeleton.test to render
<DashboardSkeleton /> and add an assertion that
screen.getByTestId('skeleton-chart') exists (or use
expect(...).toBeInTheDocument()) so the chart skeleton is covered alongside the
existing checks for DashboardSkeleton and the 'skeleton-sections-row' children
length.

In `@web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx`:
- Around line 44-53: The test is only asserting the numeric substring; update
the JPY test to assert the exact formatted currency string by calling the shared
formatter used by the component: import and use formatCurrency when building the
expected value (e.g. const expected = formatCurrency(100, 'JPY') ) and then
assert expect(screen.getByText(expected)).toBeInTheDocument(); do the same for
the EUR/default test (use formatCurrency(24.5, 'EUR') or formatCurrency(24.5) to
match OrgHealthSection's default) so the test verifies symbol and precision
exactly for OrgHealthSection.

In `@web/src/__tests__/utils/dashboard.property.test.ts`:
- Around line 10-25: The test duplicates source-of-truth literal arrays
(WS_EVENT_TYPES, DEPT_NAMES) instead of reusing the canonical lists for
WsEventType and DepartmentName; remove these local literal definitions and
import the canonical as‑const arrays (e.g., export or locate EVENT_TYPES_LIST
and DEPARTMENT_NAMES_LIST) from the shared types module (import from
"@/api/types"), then update the test to iterate those imported arrays (keeping
any type assertions) so the test continues to validate every union member as the
authoritative source changes.

In `@web/src/components/layout/StatusBar.tsx`:
- Around line 78-121: The StatusBar currently hand-rolls Dot + label pairs
(e.g., the StatusItem blocks showing agents, active, tasks, the pendingApprovals
block, and the final status item using statusCfg) — replace those usages with
the shared StatusBadge component to centralize styling and semantics: remove Dot
and its adjacent span labels and render <StatusBadge> (or the project's
StatusBadge API) passing the color (from Dot.color or statusCfg.color) and the
label text (e.g., `${totalAgents} agents`, `${activeAgents} active`,
`${totalTasks} tasks`, `{pendingApprovals} pending`, and statusCfg.label);
preserve conditional rendering (dataLoaded and pendingApprovals checks), and
keep surrounding StatusItem wrappers and any auxiliary text (spend/budget)
unchanged.
- Around line 40-46: In pollHealth, avoid mapping network/fetch errors to 'down'
— in the catch block for getHealth() either leave the existing state unchanged
or explicitly set it to 'unknown' instead of calling setHealthStatus('down');
update the catch in the pollHealth function (which calls getHealth and
setHealthStatus and works with the HealthStatus type) to preserve last known
status or assign 'unknown' so that only a real health payload can set 'down'.

In `@web/src/pages/dashboard/ActivityFeedItem.tsx`:
- Around line 46-54: Replace the inline status dot <span> in ActivityFeedItem
with the shared StatusBadge component: remove the absolute <span> that uses
dotColor and the aria-label, and render <StatusBadge> positioned the same way
(near Avatar) passing the equivalent status/color prop (derived from dotColor or
activity.action_type) and the aria-label built from
activity.action_type.replace(/[._]/g, ' '). Ensure you import StatusBadge and
preserve the existing positioning classes (absolute -bottom-0.5 -right-0.5) and
ring/card styling by mapping them into StatusBadge props or wrapper classes so
behavior and accessibility remain identical.

In `@web/src/pages/dashboard/DashboardPage.stories.tsx`:
- Around line 84-96: The Storybook meta for DashboardPage is missing an explicit
a11y policy; update the meta object (the `meta` constant for DashboardPage) to
include a parameters.a11y.test key set to one of the allowed values ('error' |
'todo' | 'off') so the story no longer relies on defaults and conforms to the
repo WCAG gate.

In `@web/src/pages/dashboard/OrgHealthSection.stories.tsx`:
- Around line 16-27: The story meta for OrgHealthSection is missing the required
Storybook a11y policy; update the meta object (named meta) to include a
parameters.a11y.test entry set to one of 'error' | 'todo' | 'off' (instead of
using .element or .manual). Locate the meta constant in
OrgHealthSection.stories.tsx and add/modify parameters.a11y.test accordingly so
Storybook 10 a11y testing is explicitly configured.
🪄 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: 2bbb4e26-39af-4d3d-8f5b-fe3e5fb5f3f0

📥 Commits

Reviewing files that changed from the base of the PR and between 126ff66 and fab0c67.

📒 Files selected for processing (32)
  • CLAUDE.md
  • docs/design/page-structure.md
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeedItem.test.tsx
  • web/src/__tests__/pages/dashboard/BudgetBurnChart.test.tsx
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/stores/analytics.test.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/__tests__/utils/dashboard.test.ts
  • web/src/api/endpoints/activities.ts
  • web/src/api/endpoints/company.ts
  • web/src/api/endpoints/index.ts
  • web/src/api/types.ts
  • web/src/components/layout/StatusBar.tsx
  • web/src/components/ui/metric-card.tsx
  • web/src/hooks/useDashboardData.ts
  • web/src/pages/DashboardPage.tsx
  • web/src/pages/dashboard/ActivityFeed.stories.tsx
  • web/src/pages/dashboard/ActivityFeed.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/pages/dashboard/BudgetBurnChart.stories.tsx
  • web/src/pages/dashboard/BudgetBurnChart.tsx
  • web/src/pages/dashboard/DashboardPage.stories.tsx
  • web/src/pages/dashboard/DashboardSkeleton.tsx
  • web/src/pages/dashboard/OrgHealthSection.stories.tsx
  • web/src/pages/dashboard/OrgHealthSection.tsx
  • web/src/stores/analytics.ts
  • web/src/utils/dashboard.ts

Comment on lines +485 to +490
export interface ActivityItem {
id: string
timestamp: string
agent_name: string
action_type: WsEventType
description: string
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

ActivityItem.action_type is too narrow for /activities API payloads.

Line 489 ties REST activity items to WsEventType, but the backend activity feed model accepts arbitrary event_type strings and merges multiple sources. This mismatch can break downstream assumptions when non-WS event strings appear.

🛠️ Suggested contract fix
 export interface ActivityItem {
   id: string
   timestamp: string
   agent_name: string
-  action_type: WsEventType
+  action_type: string
   description: string
   task_id: string | null
   department: DepartmentName | null
 }
// Optional follow-up: normalize for UI-only usage
export type KnownActivityActionType = WsEventType
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/api/types.ts` around lines 485 - 490, The ActivityItem interface
currently types action_type as WsEventType which is too restrictive for the
/activities REST payloads; change ActivityItem.action_type to a broader string
(or a union that includes string) so arbitrary backend event_type values are
accepted, then optionally introduce a KnownActivityActionType = WsEventType for
UI-only normalization; update any usages of ActivityItem.action_type and places
expecting WsEventType to handle the broader string type or perform runtime
normalization using KnownActivityActionType.

Comment on lines +31 to +37
// Trigger overview fetch on mount if data isn't loaded yet
useEffect(() => {
const state = useAnalyticsStore.getState()
if (!state.overview && !state.loading) {
state.fetchOverview()
}
}, [])
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid double-loading overview data from here.

This mount-time fetchOverview() can race the dashboard bootstrap path. The store action has no in-flight coalescing, so mounting StatusBar beside useDashboardData can send duplicate overview requests. Keep one owner for the initial load, or dedupe the request in the store before calling it from multiple places.

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

In `@web/src/components/layout/StatusBar.tsx` around lines 31 - 37, StatusBar
mounts call to fetchOverview via useEffect causes duplicate requests because
useAnalyticsStore.fetchOverview lacks in-flight coalescing; to fix, either
remove the mount-time call in StatusBar (delete the useEffect block in
StatusBar.tsx that calls useAnalyticsStore.getState().fetchOverview) so the
dashboard bootstrap (useDashboardData) remains the single owner of initial load,
or add de-duplication inside the store by introducing an inFlight flag (e.g.,
analyticsStore.isFetchingOverview) checked/set in fetchOverview() so subsequent
callers (StatusBar.useEffect and useDashboardData) return early when a fetch is
already underway; reference useEffect in StatusBar.tsx,
useAnalyticsStore.getState(), state.overview/state.loading,
state.fetchOverview(), and useDashboardData when applying the change.

Valid fixes:
- makeTasksByStatus: use Partial<OverviewMetrics['tasks_by_status']> instead of
  unsafe Record<string, number> cast
- ACTION_DOT_COLORS: add explicit colors for message.sent, meeting.*, and
  coordination.* event types (no more fallback for known events)
- StatusBar pollHealth: preserve last known state on transient fetch failures
  instead of immediately setting 'down'
- DashboardSkeleton.test: add chart skeleton assertion
- StatusBar.test + OrgHealthSection.test: use formatCurrency for exact assertions
  instead of regex patterns

Nice-to-haves:
- Export WS_EVENT_TYPE_VALUES and DEPARTMENT_NAME_VALUES from api/types.ts as
  canonical source-of-truth arrays; use them in property tests and dashboard utils
- useDashboardData.test: exact channel binding assertion (toEqual instead of
  arrayContaining), use vi.hoisted for shared polling mocks
- ActivityFeed.test: property-based test for 10-item cap using fast-check

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 09:11 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
web/src/pages/dashboard/ActivityFeedItem.tsx (1)

54-62: 🛠️ Refactor suggestion | 🟠 Major

Use StatusBadge for the action indicator.

This overlay span is another local status-dot implementation, so color and accessibility behavior now live outside the shared badge component again. As per coding guidelines, "Do NOT recreate status dots inline; always use <StatusBadge> component".

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

In `@web/src/pages/dashboard/ActivityFeedItem.tsx` around lines 54 - 62, Replace
the inline overlay span status-dot in ActivityFeedItem (the span using cn,
dotColor, and aria-label with activity.action_type) with the shared
<StatusBadge> component: remove the local dot implementation and render
<StatusBadge> as the overlay next to <Avatar>, passing the computed status/color
(dotColor) or deriving status from activity.action_type, and preserve the same
accessible label by forwarding aria-label={`Action:
${activity.action_type.replace(/[._]/g, ' ')}`} to StatusBadge; ensure any CSS
positioning (absolute -bottom -right) is kept on the StatusBadge wrapper so the
visual placement remains identical.
web/src/components/layout/StatusBar.tsx (2)

31-37: ⚠️ Potential issue | 🟠 Major

Keep a single owner for the initial overview fetch.

fetchOverview() in web/src/stores/analytics.ts:102-109 still has no in-flight guard, so this mount effect can issue a second overview request when StatusBar mounts alongside useDashboardData. Remove this bootstrap here or coalesce fetchOverview() in the store.

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

In `@web/src/components/layout/StatusBar.tsx` around lines 31 - 37, The mount
effect in StatusBar.tsx (the useEffect that reads useAnalyticsStore.getState()
and calls state.fetchOverview()) can race with useDashboardData because
fetchOverview() in analytics.ts lacks an in-flight guard; either remove this
bootstrap useEffect from StatusBar.tsx so only the dashboard initiates the
fetch, or modify fetchOverview() in web/src/stores/analytics.ts (the
fetchOverview function) to coalesce concurrent calls by adding an in-flight flag
(e.g., isFetching/overviewLoading) that is checked and set before starting the
network request and cleared on completion/error so duplicate requests from
useDashboardData and StatusBar are prevented.

79-123: 🛠️ Refactor suggestion | 🟠 Major

Replace these hand-rolled indicators with StatusBadge.

The bar is still rebuilding badge-like UI with Dot + text, so styling and accessibility semantics now have a second source of truth. As per coding guidelines, "Do NOT recreate status dots inline; always use <StatusBadge> component".

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

In `@web/src/components/layout/StatusBar.tsx` around lines 79 - 123, Replace the
hand-rolled Dot+text indicators inside StatusItem with the shared StatusBadge
component: for each block that currently renders <Dot color=... /> plus a span
(the agents, active, tasks items, the conditional pendingApprovals item, and the
final statusCfg item) swap to <StatusBadge> and pass the visual color and label
text from the same sources (use statusCfg.color and statusCfg.label for the
final item, use computed strings like `${totalAgents} agents` / `${activeAgents}
active` / `${totalTasks} tasks` for the first three, and `${pendingApprovals}
pending` for the conditional item); keep the same dataLoaded and conditional
rendering logic, ensure aria/accessible props are forwarded by StatusBadge, and
preserve existing spacing/text-muted styles around non-badge items
(spend/budget) unchanged.
web/src/utils/dashboard.ts (1)

107-128: 🧹 Nitpick | 🔵 Trivial

Prefer a stable payload identifier before the module counter.

++wsActivityCounter guarantees uniqueness only for the current JS module instance. If the same backend event is replayed after reconnects or merged with fetched history, it still becomes a brand-new ActivityItem.id. Prefer a stable payload identifier first, then fall back to the counter only when nothing stable is available.

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

In `@web/src/utils/dashboard.ts` around lines 107 - 128, The ActivityItem.id
currently uses ++wsActivityCounter making IDs unstable across reconnects; update
wsEventToActivityItem to prefer a stable payload identifier (e.g., payload.id,
payload.event_id, payload.payload_id, or taskId if present, or event.id if the
event object has a stable id) as the primary component of ActivityItem.id and
only append/increment wsActivityCounter when no stable identifier exists; ensure
references to wsActivityCounter, wsEventToActivityItem, and ActivityItem.id are
updated so the counter is used solely as a fallback and still yields unique IDs
within the module.
🤖 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/components/layout/StatusBar.tsx`:
- Line 27: The code is mislabeling tasks_by_status.in_review as "pending
approvals"; update the StatusBar component so the data and label match: either
replace the selector pendingApprovals = useAnalyticsStore((s) =>
s.overview?.tasks_by_status?.in_review) with a real approvals count selector
(e.g., overview?.approvals?.pending or similar) if such metric exists, or keep
the current selector and rename the UI copy from "pending" to "in review"
(adjust the variable name from pendingApprovals to inReviewCount and the
displayed label accordingly). Make the same change for the other occurrences
noted (around lines 111–115) so the label accurately reflects
tasks_by_status.in_review.

---

Duplicate comments:
In `@web/src/components/layout/StatusBar.tsx`:
- Around line 31-37: The mount effect in StatusBar.tsx (the useEffect that reads
useAnalyticsStore.getState() and calls state.fetchOverview()) can race with
useDashboardData because fetchOverview() in analytics.ts lacks an in-flight
guard; either remove this bootstrap useEffect from StatusBar.tsx so only the
dashboard initiates the fetch, or modify fetchOverview() in
web/src/stores/analytics.ts (the fetchOverview function) to coalesce concurrent
calls by adding an in-flight flag (e.g., isFetching/overviewLoading) that is
checked and set before starting the network request and cleared on
completion/error so duplicate requests from useDashboardData and StatusBar are
prevented.
- Around line 79-123: Replace the hand-rolled Dot+text indicators inside
StatusItem with the shared StatusBadge component: for each block that currently
renders <Dot color=... /> plus a span (the agents, active, tasks items, the
conditional pendingApprovals item, and the final statusCfg item) swap to
<StatusBadge> and pass the visual color and label text from the same sources
(use statusCfg.color and statusCfg.label for the final item, use computed
strings like `${totalAgents} agents` / `${activeAgents} active` / `${totalTasks}
tasks` for the first three, and `${pendingApprovals} pending` for the
conditional item); keep the same dataLoaded and conditional rendering logic,
ensure aria/accessible props are forwarded by StatusBadge, and preserve existing
spacing/text-muted styles around non-badge items (spend/budget) unchanged.

In `@web/src/pages/dashboard/ActivityFeedItem.tsx`:
- Around line 54-62: Replace the inline overlay span status-dot in
ActivityFeedItem (the span using cn, dotColor, and aria-label with
activity.action_type) with the shared <StatusBadge> component: remove the local
dot implementation and render <StatusBadge> as the overlay next to <Avatar>,
passing the computed status/color (dotColor) or deriving status from
activity.action_type, and preserve the same accessible label by forwarding
aria-label={`Action: ${activity.action_type.replace(/[._]/g, ' ')}`} to
StatusBadge; ensure any CSS positioning (absolute -bottom -right) is kept on the
StatusBadge wrapper so the visual placement remains identical.

In `@web/src/utils/dashboard.ts`:
- Around line 107-128: The ActivityItem.id currently uses ++wsActivityCounter
making IDs unstable across reconnects; update wsEventToActivityItem to prefer a
stable payload identifier (e.g., payload.id, payload.event_id,
payload.payload_id, or taskId if present, or event.id if the event object has a
stable id) as the primary component of ActivityItem.id and only append/increment
wsActivityCounter when no stable identifier exists; ensure references to
wsActivityCounter, wsEventToActivityItem, and ActivityItem.id are updated so the
counter is used solely as a fallback and still yields unique IDs within the
module.
🪄 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: 46f15587-f5bd-4c6d-8ee0-2228d4d1afe6

📥 Commits

Reviewing files that changed from the base of the PR and between fab0c67 and b0d558b.

📒 Files selected for processing (11)
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
  • web/src/api/types.ts
  • web/src/components/layout/StatusBar.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/utils/dashboard.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). (5)
  • 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 (3)
web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/**/*.{ts,tsx}: Validate TypeScript/JavaScript imports with explicit type checking; avoid implicit any types
Always reuse existing components from web/src/components/ui/ before creating new components (StatusBadge, MetricCard, Sparkline, SectionCard, AgentCard, DeptHealthBar, ProgressGauge, StatPill, Avatar, Button, Toast, Skeleton variants, EmptyState, ErrorBoundary, ConfirmDialog, CommandPalette, InlineEdit, AnimatedPresence, StaggerGroup/StaggerItem)
Use Tailwind semantic classes (text-foreground, bg-card, text-accent, text-success, bg-danger) and CSS variables (var(--so-accent)) for colors; never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono for typography (maps to Geist tokens); never set fontFamily directly in .tsx/.ts files
Use density-aware spacing tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing for layout; never hardcode pixel values for layout spacing in .tsx/.ts files
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows and borders instead of hardcoded values in .tsx/.ts files
Import cn from @/lib/utils for conditional class merging in React components
Do NOT recreate status dots inline; always use <StatusBadge> component
Do NOT build card-with-header layouts from scratch; always use <SectionCard> component
Do NOT create metric displays with inline text-metric font-bold classes; always use <MetricCard> component
Do NOT render initials circles manually; always use <Avatar> component
Do NOT create complex (>8 line) JSX inside .map() calls; extract to a shared component
Do NOT use rgba() with hardcoded values; always use design token variables in .tsx/.ts files
Use React 19 hooks and async component patterns in web dashboard files; React 19 supports Server Components (where applicable)
ESLint must pass @eslint-react/eslint-plugin checks in web dashboard files
Use Lucide React icons in web das...

Files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/api/types.ts
  • web/src/utils/dashboard.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
web/src/__tests__/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/src/__tests__/**/*.{test,spec}.{ts,tsx}: Use @vitest/coverage-v8 for React test coverage in web dashboard unit tests
Use fast-check (fc.assert + fc.property) for property-based testing in React test files
Use @testing-library/react for component unit testing in web dashboard

Files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
web/**/*.{ts,tsx,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Tailwind CSS 4 for styling web dashboard (configured in web/tailwind.config.ts)

Files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/api/types.ts
  • web/src/utils/dashboard.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
🧠 Learnings (27)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use Zustand for web dashboard state management (stores in `web/src/stores/`)
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/__tests__/**/*.{test,spec}.{ts,tsx} : Use `testing-library/react` for component unit testing in web dashboard

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/__tests__/**/*.{test,spec}.{ts,tsx} : Use `vitest/coverage-v8` for React test coverage in web dashboard unit tests

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
📚 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/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/utils/dashboard.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Web dashboard testing: `npm --prefix web run test` (Vitest, coverage scoped to files changed vs origin/main)

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use React 19 hooks and async component patterns in web dashboard files; React 19 supports Server Components (where applicable)

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : ESLint must pass `eslint-react/eslint-plugin` checks in web dashboard files

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/components/layout/StatusBar.tsx
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/__tests__/**/*.{test,spec}.{ts,tsx} : Use fast-check (`fc.assert` + `fc.property`) for property-based testing in React test files

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/__tests__/utils/dashboard.property.test.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/components/ui/**/*.{ts,tsx} : Place new shared components in `web/src/components/ui/` with kebab-case filenames and create accompanying `.stories.tsx` file with all component states

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Always reuse existing components from `web/src/components/ui/` before creating new components (StatusBadge, MetricCard, Sparkline, SectionCard, AgentCard, DeptHealthBar, ProgressGauge, StatPill, Avatar, Button, Toast, Skeleton variants, EmptyState, ErrorBoundary, ConfirmDialog, CommandPalette, InlineEdit, AnimatedPresence, StaggerGroup/StaggerItem)

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx
  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
  • web/src/utils/dashboard.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.stories.{ts,tsx} : Use Storybook 10 ESM-only imports: `storybook/test` (not `storybook/test`), `storybook/actions` (not `storybook/addon-actions`)

Applied to files:

  • web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx
  • web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use Zustand for web dashboard state management (stores in `web/src/stores/`)

Applied to files:

  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use `tanstack/react-query` for server state management in web dashboard API calls

Applied to files:

  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.828Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.828Z
Learning: Applies to web/tsconfig.json : TypeScript 6.0+ required for web dashboard; configured in `web/tsconfig.json`

Applied to files:

  • web/src/__tests__/hooks/useDashboardData.test.ts
  • web/src/__tests__/pages/DashboardPage.test.tsx
  • web/src/utils/dashboard.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Validate TypeScript/JavaScript imports with explicit type checking; avoid implicit `any` types

Applied to files:

  • web/src/__tests__/pages/DashboardPage.test.tsx
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Do NOT recreate status dots inline; always use `<StatusBadge>` component

Applied to files:

  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/components/layout/StatusBar.tsx
  • web/src/__tests__/components/layout/StatusBar.test.tsx
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use Lucide React icons in web dashboard components; import from `lucide-react`

Applied to files:

  • web/src/pages/dashboard/ActivityFeedItem.tsx
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Do NOT use `rgba()` with hardcoded values; always use design token variables in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/dashboard/ActivityFeedItem.tsx
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use Tailwind semantic classes (`text-foreground`, `bg-card`, `text-accent`, `text-success`, `bg-danger`) and CSS variables (`var(--so-accent)`) for colors; never hardcode hex values in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/dashboard/ActivityFeedItem.tsx
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use token variables (`var(--so-shadow-card-hover)`, `border-border`, `border-bright`) for shadows and borders instead of hardcoded values in `.tsx`/`.ts` files

Applied to files:

  • web/src/pages/dashboard/ActivityFeedItem.tsx
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Do NOT create metric displays with inline `text-metric font-bold` classes; always use `<MetricCard>` component

Applied to files:

  • web/src/pages/dashboard/ActivityFeedItem.tsx
  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/src/**/*.{ts,tsx} : Use Recharts for charting and data visualization in web dashboard

Applied to files:

  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-15T18:17:43.675Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:17:43.675Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).

Applied to files:

  • web/src/utils/dashboard.ts
  • web/src/__tests__/utils/dashboard.property.test.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/**/*.{ts,tsx,css} : Use Tailwind CSS 4 for styling web dashboard (configured in `web/tailwind.config.ts`)

Applied to files:

  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-15T21:49:53.264Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:49:53.264Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Fix all valid issues found by review agents; never skip or defer valid issues including pre-existing issues in surrounding code and adjacent findings

Applied to files:

  • web/src/utils/dashboard.ts
📚 Learning: 2026-03-27T07:39:50.827Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T07:39:50.827Z
Learning: Applies to web/tsconfig.json : Explicitly list required types in `tsconfig.json` types field; do not rely on auto-discovery (e.g., `"types": ["vitest/globals"]`)

Applied to files:

  • web/src/__tests__/utils/dashboard.property.test.ts
🔇 Additional comments (9)
web/src/__tests__/pages/dashboard/DashboardSkeleton.test.tsx (1)

1-27: LGTM!

The test suite properly covers the DashboardSkeleton component's structure:

  • Loading status accessibility role
  • Exact child counts for metrics (4) and sections (2) rows matching the implementation
  • Chart skeleton presence

The chart skeleton assertion requested in the past review has been addressed.

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

1-108: LGTM!

The test suite properly validates the useDashboardData hook:

  • Uses vi.hoisted correctly for mock function references
  • The resetStore() helper correctly resets all analytics store fields
  • WebSocket channel bindings are now asserted exactly (not loosely via arrayContaining)
  • Polling mock setup has been consolidated with vi.hoisted pattern

Both past review comments have been addressed.

web/src/__tests__/pages/dashboard/ActivityFeed.test.tsx (1)

41-56: LGTM!

The property-based test using fast-check correctly validates the 10-item cap behavior across randomized input sizes (0-50). The test properly unmounts between iterations and uses numRuns: 20 for reasonable test duration.

The hardcoded 10 on line 45 mirrors the component's internal MAX_VISIBLE constant. Since this tests the expected contract (cap at 10 items) rather than implementation details, this is acceptable.

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

1-143: LGTM!

The test suite comprehensively covers DashboardPage states and behaviors:

  • Loading skeleton when loading: true with no data
  • Metric cards rendering with correct labels
  • All dashboard sections (Org Health, Activity, Budget Burn)
  • Error banner display
  • WebSocket disconnect warnings

Both past review issues have been addressed:

  • mockBudgetConfig now includes the required currency field
  • makeTasksByStatus uses proper typing without unsafe casts
web/src/__tests__/pages/dashboard/OrgHealthSection.test.tsx (1)

1-56: LGTM!

The test suite properly covers OrgHealthSection states:

  • Section title and empty state
  • Department health bars rendering
  • Overall health gauge presence
  • Currency formatting using the shared formatCurrency utility

The past review issue regarding locale-coupled currency assertions has been addressed by using formatCurrency() instead of hardcoded strings.

web/src/__tests__/utils/dashboard.property.test.ts (1)

1-162: LGTM!

The property-based test suite using fast-check properly validates dashboard utility invariants:

  • computeMetricCards: always 4 cards, non-empty labels, progress constraints
  • computeOrgHealth: null for empty array, [0, 100] range otherwise
  • describeEvent: non-empty string for all event types
  • wsEventToActivityItem: valid ActivityItem structure

Both past review issues have been addressed:

  • Event/department lists now imported from @/api/types canonical exports
  • arbOverview and arbBudgetConfig include required currency field
web/src/api/types.ts (2)

483-504: LGTM!

The new DTOs are well-defined:

  • ActivityItem includes all required fields for activity feed rendering
  • DepartmentHealth supports the org health section with nullable cost_usd

845-859: LGTM!

The exported runtime arrays with as const satisfies readonly T[] pattern provide:

  • Type-safe iteration for tests and utilities
  • Compile-time validation that all union members are included
  • Single source of truth for event types and department names

This addresses the past review recommendation to export canonical lists from the types module.

web/src/__tests__/components/layout/StatusBar.test.tsx (1)

1-146: LGTM!

The test suite comprehensively covers StatusBar states:

  • Brand text and placeholder values when data is not loaded
  • Live values from analytics store
  • System status before first poll
  • Budget percentage display
  • Pending approvals count
  • Currency formatting for both default (EUR) and non-default (GBP) currencies

Both past review issues have been addressed:

  • makeOverview helper consolidates repeated fixture setup
  • Currency assertions use formatCurrency() for locale-safe comparisons

- StatusBar: rename pendingApprovals -> inReviewCount, label "pending" -> "in review"
  to match the IN REVIEW metric card rename (data source is tasks_by_status.in_review)
- wsEventToActivityItem: prefer payload.id or taskId as stable activity ID,
  fall back to timestamp+counter only when no stable identifier exists

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo merged commit 7d519d5 into main Mar 27, 2026
26 of 28 checks passed
@Aureliolo Aureliolo deleted the feat/dashboard-page branch March 27, 2026 09:43
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 27, 2026 09:43 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 30, 2026
🤖 I have created a release *beep* *boop*
---
#MAJOR CHANGES; We got a somewhat working webui :)

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


### Features

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


### Bug Fixes

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


### Refactoring

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


### Documentation

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


### Tests

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


### CI/CD

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


### Maintenance

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

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

---------

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Dashboard page (metric cards, sparklines, activity feed, budget burn)

1 participant