Five near-identical 25-line entity result blocks collapse into one
PaletteResultSection. Extract closeAndReset() callback used in 5 places.
Drop dead fallback: runs.find(...)?.id.slice(0,8) === run.id.slice(0,8).
Function signature reduced from 16 args to 4; setters sourced from
useAppStore.getState() inside callbacks (stable refs, no deps needed).
useCompanyController no longer has to thread 14 setters through.
Delete the useUIController.navigateTo duplicate; augment slice version with
task-with-run lookup (via get().snapshot) so notification-click and command
palette share one codepath. App.tsx now reads navigateTo from the store.
Sections now subscribe directly to the Zustand store via useSectionContext
(shared data derivation) and useAppStore fine-grained selectors. Delete
useAppController, useExecutionController, useOrganizationController, and
useCommunicationController; the three domain forwarders held no logic
beyond re-exporting store slices.
SectionContentView becomes a 50-line layout dispatcher taking only
{section, error, pageTransition}; cross-domain run actions (startRun /
cancelRun) move into App.tsx and are exposed to sections through a new
AppActionsContext alongside refresh / setError / openPathWithFeedback.
App.tsx now calls useUIController + useCompanyController directly and
composes palette + navigation plumbing itself.
OnboardingStepPanel previously forwarded ~50 props to seven step
components, so adding a single wizard field meant touching four layers.
Introduce a renderer-local React Context (OnboardingProvider /
useOnboarding) that carries all wizard state and actions (connectors,
node status, company inputs, model selection, execution mode, mission,
launch summary, locale, t, error/loading). Step panels now receive only
their navigation callbacks (onContinue / onBack / onLaunch / onFinish)
and read everything else via useOnboarding(). CompanyOnboarding wraps
the step panel tree in OnboardingProvider with a memoised value.
Business logic is unchanged: each onContinueFrom* transition, the
bootstrap call, and the welcome-step branch remain exactly as before;
this refactor only moves the data-passing mechanism.
All six step panels previously hard-coded "X / 5" or "X / 6" in their
header captions, which drifted out of sync with the 7-step STEP_ORDER in
OnboardingHero. Export STEP_ORDER / TOTAL_STEPS / getStepNumber from
OnboardingHero and consume them in every step so the counter is always
the single source of truth.
All 14+ non-specially-handled events were pushing into the batcher with
entityKeyFromEvent returning null, which set hasDomainChanged = true and
triggered a full snapshot refresh on every agent-message, run-status, and
heartbeat-status -- defeating the granular patch machinery.
Per-type classification:
- run-status -> granular run-updated (patchRun by runId)
- heartbeat-status -> granular agent-updated (patchAgent by agentId)
- agent-message -> granular task-updated (patchTask refetches comments)
- new-message, thread-wait-state-updated, domain-changed
-> full refresh, coalesced via 500ms batcher window
- automation-fired, recovery-needed
-> snapshot-irrelevant, skipped at event source
Widen batcher window from 50ms to 500ms so bursts coalesce into one reload.
Granular batches still flush at the same window cadence.
logRevision was bumped on every run-log event but no component subscribed,
so ref mutations to logBufferRef never triggered a re-render -- the console
pre stayed stale until something else forced a render.
Subscribe to logRevision via useAppStore and depend on it in the useMemo
that reads logBufferRef, keeping the ref-based buffer (cheap writes) while
restoring re-renders.
CompanySlice's standalone currentCompanyId field + setCurrentCompanyId action
were never written by any caller, so useDesktopEvents.ts read null and fell
through to full refresh for every entity event -- the granular patch
machinery was dead code.
- Remove currentCompanyId field + setCurrentCompanyId from CompanySlice
- refreshBadges reads from snapshot.currentCompanyId
- useDesktopEvents reads companyId from snapshot.currentCompanyId
- Update store unit test to seed snapshot instead of calling removed action
setSearchQuery only incremented searchRequestId without flipping
searchLoading, so the UI could not distinguish "searching" from "no
results". Now loading flips true on non-empty query and clears
(results + loading) on empty query. setSearchResults keeps the
existing id-match logic to flip loading false on arrival.
- ChannelSidebar: replace inline color-mix style with Tailwind classes for task discussion icons
- MessageItem: replace broken priority config (color-mix inline) with Tailwind utility classes
- MessageItem: replace broken department badge hex-alpha pattern with Tailwind classes
- automation-config: restore ACCENT/LINE as hex values (used in template literal alpha suffixes)
- desktop-shell-service: set nativeTheme.themeSource to sync Windows title bar with app theme
Priority badges had identical bg and text colors (both solid destructive
red for urgent), creating oversized solid-color blocks. Use 15% tinted
backgrounds so text remains readable and badges appear as subtle pills.
The task discussion avatar used solid var(--primary) background making a
dark square. Use 12% tinted primary instead (matching other channel icons).
Also change selected state from bg-muted to bg-accent for consistency
with regular channel items.
Change outer container from min-h-[100dvh] to h-[100dvh] and hero panel
from overflow-y-auto to overflow-hidden to prevent unwanted scrollbar
caused by ASCII animation background content.
The mass find-and-replace incorrectly replaced the word 'surface' in
human-readable text strings and type literals, not just CSS class contexts.
Fixed: App.tsx control surface text, DocumentCenter knowledge base text,
i18n en.ts, and Panel tone prop type literal.
- Replace all bg-[color:var(--old)] with bg-background/bg-card/bg-muted/bg-primary etc.
- Replace all text-[color:var(--old)] with text-foreground/text-muted-foreground/text-primary etc.
- Replace all border-[color:var(--line)] with border-border
- Replace all inline var(--old) with var(--new) equivalents
- Replace all rounded-[6px]/rounded-[8px] with rounded-lg
- Replace .surface class with bg-card border border-border shadow-sm
- Replace .focus-ring class with focus-visible Tailwind utilities
- Replace .mono class with font-mono
- Replace data-theme attribute with class-based dark mode
- 99 files updated, ~1265 lines changed, zero old references remaining
- Replace entire styles.css with OKLCH token system (light + dark modes)
- Switch dark mode from data-theme attribute to .dark CSS class
- Update App.tsx theme toggle to use classList.toggle with system preference detection
- Switch fonts from Inter/JetBrains Mono to Maple Mono/Maple Mono NF CN
- Add chart tokens, sidebar tokens, 0px border radius
- Preserve sidebar-drag, ascii-scroll-track, reduced-motion support
- Remove fontsource font imports from main.tsx
- Replace createEventBatcher with createDomainEventBatcher in App.tsx
- Add handleGranularBatch for task, run, approval, agent, and budget events
- Task/run/approval events trigger targeted IPC fetch and Zustand patch
- Run completion and budget changes trigger full refresh (cross-entity)
- Failed entity fetches fall back to removeTask or full refresh
- Unknown event types trigger full refresh (PERF-03 safety)
- Sidebar badges refresh after every successful granular batch
- Add 12 integration tests for handleGranularBatch in selective-ipc.test.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add createDomainEventBatcher with entity type+ID dedup within 50ms window
- Domain-changed overrides all granular events in same batch
- More than 10 unique entities triggers full refresh fallback
- Delete action preserved during coalescing (last-write-wins otherwise)
- Unknown event types trigger full refresh as safety fallback
- Legacy createEventBatcher preserved for backward compatibility
- Add 12 comprehensive tests for all coalescing behaviors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add RunActivityChart (area chart), SuccessRateChart (line chart), CostTrendChart (area chart), AgentWorkloadChart (bar chart) using Recharts
- Add BudgetUtilization with CSS progress bars, color thresholds (success/warn/danger), and empty state
- Create ChartSection container orchestrating all charts with Framer Motion entrance animation
- Integrate ChartSection into Dashboard.tsx between MetricsGrid and NeedsAttention
- Add companyId prop to DashboardProps and pass from App.tsx
- Include Plan 01 infrastructure: chart-utils.ts, ChartCard.tsx, ChartTooltip.tsx, recharts dependency
- All charts respect reduced motion via useReducedMotion hook
- 16 unit tests pass for aggregation functions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ChartCard: shared chart card with title, period badge, empty state, role="img" accessibility
- ChartTooltip: custom Recharts tooltip with design system tokens, color dots, mono font
- Both use CSS custom properties for automatic dark mode support
- TypeScript compiles cleanly with Recharts TooltipContentProps type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Install recharts@3.8.1 as dependency
- Create chart-utils.ts with 4 exported pure functions: aggregateRunsByDay, aggregateCostsByDay, aggregateAgentWorkload, formatShortDate
- Create dashboard-charts.test.ts with 16 unit tests covering all aggregation behaviors
- All tests pass: 14-day bucketing with zero-fill, timed_out/interrupted as failed, null cost handling, top-8 + Others grouping, name truncation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- BudgetSummary shows policy-level utilization bars when companyId provided, falls back to agent-level
- BudgetAlertBanner loads open incidents for policy-based alerts (warn_threshold/hard_stop), falls back to agent thresholds
- AgentEditor reads budget from budget_policies and writes budget as policy on save (D-11)
- Dashboard passes companyId through to BudgetAlertBanner