feat(metrics): add Dashboard Metrics Panel with live health and SVG sparkline history

New MetricsPanel.svelte component as ProjectBox tab (PERSISTED-LAZY, all projects).
Live view: fleet aggregates, project health grid, task board summary, attention queue.
History view: 5 switchable SVG sparklines (cost/tokens/turns/tools/duration), stats row,
recent sessions table. 25 tests for pure utility functions.
This commit is contained in:
Hibryda 2026-03-12 00:15:09 +01:00
parent d9d67b2bc6
commit 6ca3ffdb8d
3 changed files with 1025 additions and 1 deletions

View file

@ -14,6 +14,7 @@
import TaskBoardTab from './TaskBoardTab.svelte';
import ArchitectureTab from './ArchitectureTab.svelte';
import TestingTab from './TestingTab.svelte';
import MetricsPanel from './MetricsPanel.svelte';
import { getTerminalTabs, getActiveGroup } from '../../stores/workspace.svelte';
import { getProjectHealth, setStallThreshold } from '../../stores/health.svelte';
import { fsWatchProject, fsUnwatchProject, onFsWriteDetected, fsWatcherStatus } from '../../adapters/fs-watcher-bridge';
@ -34,7 +35,7 @@
let mainSessionId = $state<string | null>(null);
let terminalExpanded = $state(false);
type ProjectTab = 'model' | 'docs' | 'context' | 'files' | 'ssh' | 'memories' | 'tasks' | 'architecture' | 'selenium' | 'tests';
type ProjectTab = 'model' | 'docs' | 'context' | 'files' | 'ssh' | 'memories' | 'metrics' | 'tasks' | 'architecture' | 'selenium' | 'tests';
let activeTab = $state<ProjectTab>('model');
let activeGroup = $derived(getActiveGroup());
@ -156,6 +157,11 @@
class:active={activeTab === 'memories'}
onclick={() => switchTab('memories')}
>Memory</button>
<button
class="ptab"
class:active={activeTab === 'metrics'}
onclick={() => switchTab('metrics')}
>Metrics</button>
{#if isAgent && agentRole === 'manager'}
<button class="ptab ptab-role" class:active={activeTab === 'tasks'} onclick={() => switchTab('tasks')}>Tasks</button>
{/if}
@ -199,6 +205,11 @@
<MemoriesTab />
</div>
{/if}
{#if everActivated['metrics']}
<div class="content-pane" style:display={activeTab === 'metrics' ? 'flex' : 'none'}>
<MetricsPanel {project} groupId={activeGroup?.id} />
</div>
{/if}
{#if everActivated['tasks'] && activeGroup}
<div class="content-pane" style:display={activeTab === 'tasks' ? 'flex' : 'none'}>
<TaskBoardTab groupId={activeGroup.id} projectId={project.id} />