Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
45567c7
chore: add .worktrees/ to .gitignore
PryceHedrick Feb 6, 2026
45fe49a
fix(dashboard): CRIT-001 BudgetPanel crash from missing throttle field
PryceHedrick Feb 6, 2026
2341532
fix(dashboard): CRIT-002 ErrorBoundary resets on page navigation
PryceHedrick Feb 6, 2026
9c1f6c1
fix(dashboard): HIGH-004 WebSocket subscription memory leak
PryceHedrick Feb 6, 2026
84253b8
feat(dashboard): configure Tailwind 4 @theme for CSS var utilities
PryceHedrick Feb 6, 2026
20ef93f
feat(dashboard): shared Card, MetricCard, CollapsibleSection, TabGrou…
PryceHedrick Feb 6, 2026
92f861a
feat(dashboard): CRIT-003 React Router v7 with lazy loading
PryceHedrick Feb 6, 2026
1d5efdd
feat(dashboard): merge Home+Health into SystemStatus page
PryceHedrick Feb 6, 2026
b08c56b
feat(dashboard): merge Agents+Tools into AgentsAndTools page
PryceHedrick Feb 6, 2026
e525d63
feat(dashboard): merge Governance+Audit into GovernanceAndAudit page
PryceHedrick Feb 6, 2026
7db946e
feat(dashboard): merge Budget+E2E into Operations page
PryceHedrick Feb 6, 2026
ace7363
refactor(dashboard): CRIT-004 split Cognition into sub-components
PryceHedrick Feb 6, 2026
8e553b0
perf(dashboard): HIGH-005 reduce polling from ~174 to ~35 req/min
PryceHedrick Feb 6, 2026
d7b5aeb
chore(dashboard): add default export to Memory, mock interceptor in d…
PryceHedrick Feb 6, 2026
7b7f1d2
chore(dashboard): remove archived pages replaced by merged pages
PryceHedrick Feb 6, 2026
3bf11b5
fix(dashboard): resolve all TypeScript errors from page consolidation
PryceHedrick Feb 6, 2026
6cc5ce0
style(dashboard): update StatusBadge and Skeleton to design system
PryceHedrick Feb 6, 2026
c085fc7
refactor(dashboard): cleanly separate Agents from Council
PryceHedrick Feb 6, 2026
12c0b84
feat(governance): pillar quorum, dissent reports, emergency fast-trac…
PryceHedrick Feb 6, 2026
720d56f
feat(dashboard): surface council improvements in governance page
PryceHedrick Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Thumbs.db

# Development artifacts
.overhaul/
.worktrees/
*.local
.claude/settings.local.json

Expand Down
58 changes: 58 additions & 0 deletions dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"date-fns": "^4.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "7.13.0",
"recharts": "^3.7.0"
},
"devDependencies": {
Expand Down
84 changes: 34 additions & 50 deletions dashboard/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { useState } from 'react';
import { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route, Navigate, useLocation, useNavigate } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Layout } from './components/Layout';
import { ErrorBoundary } from './components/ErrorBoundary';
import { WebSocketProvider, useWebSocketContext } from './contexts/WebSocketContext';
import { AlertBanner } from './components/alerts/AlertBanner';
import { CommandPalette } from './components/CommandPalette';
import { Home } from './pages/Home';
import { Health } from './pages/Health';
import { Autonomy } from './pages/Autonomy';
import { Governance } from './pages/Governance';
import { Memory } from './pages/Memory';
import { Tools } from './pages/Tools';
import { Agents } from './pages/Agents';
import { Audit } from './pages/Audit';
import { Cognition } from './pages/Cognition';
import { E2E } from './pages/E2E';
import { Budget } from './pages/Budget';
import { PageSkeleton } from './components/PageSkeleton';

const SystemStatus = lazy(() => import('./pages/SystemStatus'));
const AgentsAndTools = lazy(() => import('./pages/AgentsAndTools'));
const Cognition = lazy(() => import('./pages/cognition'));
const Autonomy = lazy(() => import('./pages/Autonomy'));
const GovernanceAndAudit = lazy(() => import('./pages/GovernanceAndAudit'));
const Memory = lazy(() => import('./pages/Memory'));
const Operations = lazy(() => import('./pages/Operations'));

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -28,47 +27,30 @@ const queryClient = new QueryClient({
});

function AppContent() {
const [currentPage, setCurrentPage] = useState('home');
const location = useLocation();
const navigate = useNavigate();
const { status: wsStatus } = useWebSocketContext();

const renderPage = () => {
switch (currentPage) {
case 'health':
return <Health />;
case 'autonomy':
return <Autonomy />;
case 'cognition':
return <Cognition />;
case 'governance':
return <Governance />;
case 'memory':
return <Memory />;
case 'tools':
return <Tools />;
case 'agents':
return <Agents />;
case 'audit':
return <Audit />;
case 'e2e':
return <E2E />;
case 'budget':
return <Budget />;
default:
return <Home />;
}
};
const currentPage = location.pathname.split('/')[1] || 'system';

return (
<>
{/* Critical Alert Banner */}
<AlertBanner onNavigateToAlerts={() => setCurrentPage('audit')} />

{/* Command Palette */}
<CommandPalette onNavigate={setCurrentPage} />

<Layout currentPage={currentPage} onNavigate={setCurrentPage} wsStatus={wsStatus}>
<ErrorBoundary>
{renderPage()}
<AlertBanner onNavigateToAlerts={() => navigate('/governance')} />
<CommandPalette onNavigate={(page) => navigate(`/${page}`)} />
<Layout currentPage={currentPage} wsStatus={wsStatus}>
<ErrorBoundary key={currentPage}>
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/" element={<Navigate to="/system" replace />} />
<Route path="/system" element={<SystemStatus />} />
<Route path="/agents" element={<AgentsAndTools />} />
<Route path="/cognition" element={<Cognition />} />
<Route path="/autonomy" element={<Autonomy />} />
<Route path="/governance" element={<GovernanceAndAudit />} />
<Route path="/memory" element={<Memory />} />
<Route path="/operations" element={<Operations />} />
<Route path="*" element={<Navigate to="/system" replace />} />
</Routes>
</Suspense>
</ErrorBoundary>
</Layout>
</>
Expand All @@ -79,7 +61,9 @@ function App() {
return (
<QueryClientProvider client={queryClient}>
<WebSocketProvider>
<AppContent />
<BrowserRouter>
<AppContent />
</BrowserRouter>
</WebSocketProvider>
</QueryClientProvider>
);
Expand Down
16 changes: 16 additions & 0 deletions dashboard/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import type {
BudgetProfileName,
BudgetProfileChangeResult,
ApprovalQueueResponse,
DissentReport,
EmergencyVote,
PendingFeedback,
FeedbackStats,
} from '../types/api';

const API_BASE = '/api';
Expand Down Expand Up @@ -92,6 +96,18 @@ export const getGovernanceRules = (): Promise<ConstitutionalRule[]> =>
export const getQualityGates = (): Promise<QualityGate[]> =>
fetchAPI('/governance/gates');

export const getDissentReports = (): Promise<DissentReport[]> =>
fetchAPI('/governance/dissent-reports');

export const getEmergencyVotes = (): Promise<EmergencyVote[]> =>
fetchAPI('/governance/emergency-votes');

export const getPendingFeedback = (): Promise<PendingFeedback[]> =>
fetchAPI('/governance/pending-feedback');

export const getFeedbackStats = (): Promise<FeedbackStats> =>
fetchAPI('/governance/feedback-stats');

// Memory Endpoints
export const getMemories = (params?: {
type?: string;
Expand Down
11 changes: 6 additions & 5 deletions dashboard/src/components/BudgetPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ export function BudgetPanel() {
);
}

const throttleColor = THROTTLE_COLORS[data.throttle.level];
const throttleLabel = THROTTLE_LABELS[data.throttle.level];
const throttleIcon = THROTTLE_ICONS[data.throttle.level];
const throttleLevel = data.throttle?.level ?? 'normal';
const throttleColor = THROTTLE_COLORS[throttleLevel];
const throttleLabel = THROTTLE_LABELS[throttleLevel];
const throttleIcon = THROTTLE_ICONS[throttleLevel];

return (
<div
Expand All @@ -100,7 +101,7 @@ export function BudgetPanel() {
className="text-2xl font-bold font-mono"
style={{ color: throttleColor }}
>
{data.usage.percentUsed.toFixed(1)}%
{(data.usage?.percentUsed ?? 0).toFixed(1)}%
</span>
<span
className="text-xs px-2 py-0.5 rounded flex items-center gap-1"
Expand Down Expand Up @@ -176,7 +177,7 @@ export function BudgetPanel() {
</div>

{/* Projected EOD */}
{data.throttle.projectedEOD > 0 && (
{(data.throttle?.projectedEOD ?? 0) > 0 && (
<div
className="mt-4 pt-4"
style={{ borderTop: '1px solid var(--border-muted)' }}
Expand Down
11 changes: 5 additions & 6 deletions dashboard/src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ interface SearchItem {
}

const PAGES: SearchItem[] = [
{ id: 'home', type: 'page', title: 'Overview', description: 'Dashboard home', icon: '◉', onSelect: () => {} },
{ id: 'health', type: 'page', title: 'Health', description: 'System status', icon: '♥', onSelect: () => {} },
{ id: 'system', type: 'page', title: 'System Status', description: 'Overview & Health', icon: '◉', onSelect: () => {} },
{ id: 'agents', type: 'page', title: 'Agents & Tools', description: 'Agent status & tool registry', icon: '⬡', onSelect: () => {} },
{ id: 'cognition', type: 'page', title: 'Cognition', description: 'LOGOS/ETHOS/PATHOS', icon: '🧠', onSelect: () => {} },
{ id: 'autonomy', type: 'page', title: 'Autonomy', description: 'Scheduler & Subagents', icon: '↻', onSelect: () => {} },
{ id: 'agents', type: 'page', title: 'Agents', description: 'Agent status', icon: '⬡', onSelect: () => {} },
{ id: 'governance', type: 'page', title: 'Governance', description: 'Council & Rules', icon: '⚖', onSelect: () => {} },
{ id: 'governance', type: 'page', title: 'Governance', description: 'Council, Rules & Audit', icon: '⚖', onSelect: () => {} },
{ id: 'memory', type: 'page', title: 'Memory', description: 'Knowledge base', icon: '⬢', onSelect: () => {} },
{ id: 'tools', type: 'page', title: 'Tools', description: 'Tool registry', icon: '⚙', onSelect: () => {} },
{ id: 'audit', type: 'page', title: 'Audit Trail', description: 'Hash chain', icon: '⊞', onSelect: () => {} },
{ id: 'operations', type: 'page', title: 'Operations', description: 'Budget & E2E Tests', icon: '⚙', onSelect: () => {} },
];

export function CommandPalette({ onNavigate }: CommandPaletteProps) {
Expand Down
13 changes: 3 additions & 10 deletions dashboard/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,15 @@ import type { ConnectionStatus } from '../hooks/useWebSocket';
interface LayoutProps {
children: ReactNode;
currentPage: string;
onNavigate: (page: string) => void;
wsStatus?: ConnectionStatus;
}

export function Layout({ children, currentPage, onNavigate, wsStatus = 'disconnected' }: LayoutProps) {
export function Layout({ children, currentPage, wsStatus = 'disconnected' }: LayoutProps) {
return (
<>
<SkipLink />
<div
className="flex h-screen"
style={{
background: 'var(--bg-primary)',
color: 'var(--text-primary)',
}}
>
<Sidebar currentPage={currentPage} onNavigate={onNavigate} wsStatus={wsStatus} />
<div className="flex h-screen bg-bg-primary text-text-primary">
<Sidebar currentPage={currentPage} wsStatus={wsStatus} />
<main
id="main-content"
className="flex-1 overflow-auto custom-scrollbar bg-ari-radial"
Expand Down
31 changes: 31 additions & 0 deletions dashboard/src/components/PageSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export function PageSkeleton() {
return (
<div className="space-y-6 animate-pulse p-6">
{/* Header skeleton */}
<div className="flex items-center gap-3">
<div className="h-8 w-8 rounded-lg bg-bg-tertiary" />
<div className="space-y-1.5">
<div className="h-5 w-40 rounded bg-bg-tertiary" />
<div className="h-3 w-24 rounded bg-bg-tertiary" />
</div>
</div>

{/* Tab bar skeleton */}
<div className="h-10 w-64 rounded-xl bg-bg-tertiary" />

{/* Metric cards skeleton */}
<div className="grid grid-cols-3 gap-4">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="h-20 rounded-xl bg-bg-tertiary" />
))}
</div>

{/* Content skeleton */}
<div className="space-y-3">
<div className="h-12 rounded-xl bg-bg-tertiary" />
<div className="h-12 rounded-xl bg-bg-tertiary" />
<div className="h-12 rounded-xl bg-bg-tertiary" />
</div>
</div>
);
}
Loading
Loading