feat: Claude Code Monitor — lanes, pipelines and a merged workspace

Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
This commit is contained in:
2026-07-29 17:07:45 +07:00
commit 57dc91585d
783 changed files with 221743 additions and 0 deletions
@@ -0,0 +1,548 @@
/**
* @file screens.snapshot.test.tsx
* @description Render snapshot tests for every routed screen. Each page is
* rendered inside a MemoryRouter with the API layer mocked to a deterministic
* loaded-empty state (empty collections + zeroed scalars) so snapshots capture
* real structure / layout / i18n without noisy chart DOM or live data. The
* system clock and timezone are pinned so any relative/absolute timestamps are
* stable across machines and CI.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
// Pin timezone before anything reads it, so date formatting is machine-stable
// (referenced via globalThis so the browser-targeted tsconfig doesn't need node types).
const nodeProcess = (
globalThis as unknown as { process?: { env: Record<string, string | undefined> } }
).process;
if (nodeProcess) nodeProcess.env.TZ = "UTC";
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
import type { ReactNode } from "react";
import { render, act } from "@testing-library/react";
import { MemoryRouter, Routes, Route } from "react-router-dom";
import i18n from "i18next";
// ── Mock the API layer with deterministic, crash-safe empty fixtures ──────────
// Keep the module's other named exports (RUN_EFFORT_CHOICES, etc.) real and
// override only `api`.
vi.mock("../../lib/api", async (importOriginal) => {
const actual = await importOriginal<Record<string, unknown>>();
const r = (value: unknown) => vi.fn().mockResolvedValue(value);
const items = { items: [] };
const cost = {
total_cost: 0,
breakdown: [],
daily_costs: [],
feature_costs: {},
unpriced_models: [],
};
const emptyWorkflow = {
stats: {
totalSessions: 0,
totalAgents: 0,
totalSubagents: 0,
avgSubagents: 0,
successRate: 0,
avgDepth: 0,
avgDurationSec: 0,
totalCompactions: 0,
avgCompactions: 0,
topFlow: null,
},
orchestration: {
sessionCount: 0,
mainCount: 0,
subagentTypes: [],
edges: [],
outcomes: [],
compactions: { total: 0, sessions: 0 },
},
toolFlow: { transitions: [], toolCounts: [] },
effectiveness: [],
patterns: { patterns: [], soloSessionCount: 0, soloPercentage: 0 },
modelDelegation: { mainModels: [], subagentModels: [], tokensByModel: [] },
errorPropagation: {
byDepth: [],
byType: [],
eventErrors: [],
sessionsWithErrors: 0,
totalSessions: 0,
errorRate: 0,
},
concurrency: { aggregateLanes: [] },
complexity: [],
compaction: {
totalCompactions: 0,
tokensRecovered: 0,
perSession: [],
sessionsWithCompactions: 0,
totalSessions: 0,
},
cooccurrence: [],
};
const analytics = {
tokens: { total_input: 0, total_output: 0, total_cache_read: 0, total_cache_write: 0 },
tool_usage: [],
daily_events: [],
daily_sessions: [],
agent_types: [],
event_types: [],
avg_events_per_session: 0,
total_subagents: 0,
overview: {
total_sessions: 0,
active_sessions: 0,
active_agents: 0,
total_agents: 0,
total_events: 0,
},
agents_by_status: {},
sessions_by_status: {},
};
const settingsInfo = {
db: {
path: "/tmp/test.db",
size: 0,
counts: {},
pragmas: {
journal_mode: "wal",
synchronous: 1,
auto_vacuum: 0,
encoding: "UTF-8",
foreign_keys: 1,
busy_timeout: 5000,
},
load_stats: { m5: 0, m15: 0, h1: 0 },
},
hooks: { installed: true, path: "/tmp/settings.json", hooks: {} },
server: {
uptime: 0,
node_version: "v22.0.0",
platform: "linux",
ws_connections: 0,
memory: { rss: 0, heapTotal: 0, heapUsed: 0, external: 0 },
cpu_load: [0, 0, 0],
arch: "x64",
total_mem: 0,
free_mem: 0,
cpus: 1,
},
transcript_cache: { size: 0, maxSize: 100, hits: 0, misses: 0, keys: [] },
};
const session = {
id: "sess-1",
name: "Test Session",
status: "active",
cwd: "/test",
model: "claude-opus-4-6",
started_at: "2026-06-10T12:00:00.000Z",
ended_at: null,
metadata: null,
};
const stats = {
total_sessions: 0,
active_sessions: 0,
active_agents: 0,
total_agents: 0,
total_events: 0,
events_today: 0,
ws_connections: 0,
agents_by_status: {},
sessions_by_status: {},
};
const sampleWorkflowRun = {
run_id: "wf_sample1",
session_id: "sess-1",
task_id: "task-1",
name: "review-changes",
status: "completed",
default_model: "claude-opus-4-6",
started_at: "2026-06-10T12:30:00.000Z",
ended_at: "2026-06-10T12:35:00.000Z",
duration_ms: 300000,
agent_count: 2,
total_tokens: 48000,
total_tool_calls: 9,
phases: [{ title: "Review" }, { title: "Verify" }],
progress: [
{ type: "workflow_phase", index: 1, title: "Review" },
{ type: "workflow_phase", index: 2, title: "Verify" },
{
type: "workflow_agent",
agentId: "a1",
agentType: "reviewer",
state: "done",
label: "review:bugs",
phaseTitle: "Review",
tokens: 22000,
toolCalls: 5,
durationMs: 120000,
lastToolName: "Read",
resultPreview: "Found 3 issues",
},
{
type: "workflow_agent",
agentId: "a2",
agentType: "verifier",
state: "done",
label: "verify:bugs",
phaseTitle: "Verify",
tokens: 26000,
toolCalls: 4,
durationMs: 180000,
lastToolName: "Bash",
resultPreview: "All confirmed",
},
],
script_path: null,
journal_path: "/x/wf_sample1.json",
source: "journal",
created_at: "2026-06-10T12:30:00.000Z",
updated_at: "2026-06-10T12:35:00.000Z",
};
return {
...actual,
api: {
stats: { get: r(stats), facets: r({ cwds: [] }) },
sessions: {
list: r({ sessions: [], total: 0, limit: 50, offset: 0 }),
facets: r({ cwds: [], sources: ["local"] }),
get: r({ session, agents: [], events: [], workflows: [sampleWorkflowRun] }),
stats: r({
session_id: "sess-1",
total_events: 0,
events_by_type: [],
tools_used: [],
error_count: 0,
first_event_at: null,
last_event_at: null,
agents: { total: 0, main: 0, subagent: 0, compaction: 0, by_status: {} },
subagent_types: [],
tokens: {
input_tokens: 0,
output_tokens: 0,
cache_read_tokens: 0,
cache_write_tokens: 0,
},
}),
transcripts: r({ transcripts: [] }),
transcript: r({ messages: [], session_id: "sess-1" }),
},
agents: { list: r({ agents: [] }) },
remoteSources: {
list: r({ sources: [] }),
create: r({ source: {} }),
update: r({ source: {} }),
remove: r({ ok: true, purged: 0 }),
test: r({ ok: true, message: "" }),
sync: r({ ok: true }),
},
events: {
list: r({ events: [], total: 0, limit: 50, offset: 0 }),
facets: r({ event_types: [], tool_names: [] }),
},
analytics: { get: r(analytics) },
workflows: {
get: r(emptyWorkflow),
session: r({}),
runs: r({
runs: [sampleWorkflowRun],
total: 1,
counts: { completed: 1 },
limit: 200,
offset: 0,
}),
run: r({ workflow: sampleWorkflowRun, agents: [], events: [] }),
},
pricing: {
list: r({ pricing: [] }),
upsert: r({ pricing: {} }),
delete: r({ ok: true }),
totalCost: r(cost),
sessionCost: r(cost),
},
settings: {
info: r(settingsInfo),
claudeHome: {
get: r({ claude_home: "/home/test/.claude" }),
set: r({ ok: true, claude_home: "/home/test/.claude" }),
},
clearData: r({ ok: true, cleared: {} }),
reimport: r({ ok: true, imported: 0, skipped: 0, errors: 0 }),
reinstallHooks: r({ ok: true, hooks: { installed: true, hooks: {} } }),
resetPricing: r({ ok: true, pricing: [] }),
exportData: () => "/api/settings/export",
cleanup: r({
ok: true,
abandoned: 0,
purged_sessions: 0,
purged_events: 0,
purged_agents: 0,
}),
},
import: {
guide: r({
platform: "linux",
default_projects_dir: "/home/test/.claude/projects",
default_projects_dir_display: "~/.claude/projects",
default_projects_dir_exists: true,
default_projects_dir_stats: { projects: 0, jsonl_files: 0 },
archive_command: "tar",
supported_extensions: [".jsonl"],
max_upload_bytes: 1000000,
max_upload_files: 10,
steps: [],
}),
rescan: r({}),
scanPath: r({}),
},
ccConfig: {
overview: r({
roots: {
claudeHome: "/home/test/.claude",
projectClaudeDir: "/test/.claude",
projectRoot: "/test",
claudeJson: "/home/test/.claude.json",
},
counts: {
skills: { user: 0, project: 0 },
agents: { user: 0, project: 0 },
commands: { user: 0, project: 0 },
outputStyles: { user: 0, project: 0 },
plugins: 0,
pluginsEnabled: 0,
pluginsDisabled: 0,
marketplaces: 0,
keybindings: 0,
mcpServers: { user: 0, project: 0 },
hooks: {},
memory: 0,
settingsFiles: 0,
},
}),
skills: r(items),
agents: r(items),
commands: r(items),
outputStyles: r(items),
plugins: r({ manifestPath: "", manifestExists: false, plugins: [] }),
mcp: r({ servers: [], items: [] }),
hooks: r(items),
settings: r(items),
memory: r(items),
file: r({
scope: "user",
name: "x",
path: "/x",
size: 0,
mtime: 0,
truncated: false,
frontmatter: {},
preview: "",
content: "",
}),
write: r({ ok: true }),
delete: r({ ok: true }),
marketplaces: r({ marketplaces: [], items: [] }),
keybindings: r({ items: [], bindings: [] }),
statusline: r({ configured: false }),
hookScripts: r({ items: [], scripts: [] }),
backups: r({ items: [] }),
},
run: {
list: r({ runs: [], items: [] }),
history: r({ items: [] }),
binary: r({ found: true, path: "/usr/bin/claude" }),
cwds: r({ items: [] }),
files: r({ items: [] }),
start: r({ id: "run-1", status: "running" }),
get: r({ id: "run-1", status: "running", messages: [], envelopes: [] }),
send: r({ messageId: "m-1" }),
kill: r({ ok: true }),
},
alerts: {
list: r({ alerts: [], total: 0, unacked: 0, limit: 50, offset: 0 }),
ack: r({ alert: {} }),
ackAll: r({ ok: true, acknowledged: 0 }),
rules: {
list: r({ rules: [] }),
create: r({ rule: {} }),
update: r({ rule: {} }),
remove: r({ ok: true }),
},
},
webhooks: {
list: r({ targets: [] }),
providers: r({ providers: [] }),
create: r({ target: {} }),
update: r({ target: {} }),
remove: r({ ok: true }),
test: r({ ok: true, status: 200, attempts: 1, error: null }),
deliveries: r({ deliveries: [], limit: 20, offset: 0 }),
},
lanes: {
list: r({ lanes: [], counts: { total: 0, running: 0, needs_you: 0, dead: 0 } }),
get: r({ lane: {} }),
create: r({ lane: {} }),
update: r({ lane: {} }),
stage: r({ lane: {} }),
// A realistic shape: `{}` would crash blockingReason on `blocked` if any
// screen ever opened the destructive modal.
preflight: r({
action: "reset",
lane: 1,
kind: "managed",
branch: "feat/demo",
head: "abc1234",
dirty: 0,
untracked: 0,
unpushed: 0,
blocked: [],
warnings: [],
}),
action: r({ ok: true }),
},
updates: { check: r({ behind: 0, ahead: 0, current: "", upstream: "" }), status: r({}) },
},
};
});
// eventBus: no-op pub/sub so pages mount without a live socket. onConnection +
// connected back the useSyncExternalStore(eventBus.onConnection, …) reads.
vi.mock("../../lib/eventBus", () => ({
eventBus: {
subscribe: () => () => {},
publish: () => {},
onConnection: () => () => {},
connected: true,
setConnected: () => {},
},
}));
// push notifications: avoid real service-worker / Notification calls.
vi.mock("../../lib/push", () => ({
subscribeToPush: vi.fn().mockResolvedValue(undefined),
unsubscribeFromPush: vi.fn().mockResolvedValue(undefined),
}));
// Page components (imported after the mocks above; vi.mock is hoisted).
import { Dashboard } from "../Dashboard";
import { KanbanBoard } from "../KanbanBoard";
import { Sessions } from "../Sessions";
import { SessionDetail } from "../SessionDetail";
import { ActivityFeed } from "../ActivityFeed";
import { Analytics } from "../Analytics";
import { Workflows } from "../Workflows";
import { CcConfig } from "../CcConfig";
import { Workspace } from "../Workspace";
import { Settings } from "../Settings";
import { NotFound } from "../NotFound";
// jsdom lacks these browser APIs that chart / responsive components rely on.
class ObserverStub {
observe() {}
unobserve() {}
disconnect() {}
takeRecords() {
return [];
}
}
globalThis.ResizeObserver =
globalThis.ResizeObserver || (ObserverStub as unknown as typeof ResizeObserver);
globalThis.IntersectionObserver =
globalThis.IntersectionObserver || (ObserverStub as unknown as typeof IntersectionObserver);
if (!window.matchMedia) {
window.matchMedia = (query: string) =>
({
matches: false,
media: query,
onchange: null,
addListener() {},
removeListener() {},
addEventListener() {},
removeEventListener() {},
dispatchEvent() {
return false;
},
}) as unknown as MediaQueryList;
}
for (const fn of ["scrollIntoView", "scrollBy", "scrollTo"] as const) {
if (!(Element.prototype as unknown as Record<string, unknown>)[fn]) {
(Element.prototype as unknown as Record<string, unknown>)[fn] = function () {};
}
}
// Flush pending promises (mocked API resolves) + effects so the loaded state
// is rendered before snapshotting.
async function settle() {
await act(async () => {
await new Promise((r) => setTimeout(r, 0));
await new Promise((r) => setTimeout(r, 0));
});
}
async function snapshot(ui: ReactNode, route = "/") {
const { container } = render(<MemoryRouter initialEntries={[route]}>{ui}</MemoryRouter>);
await settle();
expect(container).toMatchSnapshot();
}
beforeAll(() => {
// Fake only Date so relative/absolute times are deterministic; leave timers
// real so setTimeout-based flushing in settle() still works.
vi.useFakeTimers({ now: new Date("2026-06-10T13:00:00.000Z"), toFake: ["Date"] });
});
afterAll(() => {
vi.useRealTimers();
});
beforeEach(() => {
i18n.changeLanguage("en");
});
afterEach(() => {
vi.clearAllMocks();
});
describe("screen snapshots", () => {
it("Dashboard", async () => {
await snapshot(<Dashboard />, "/");
});
it("Kanban board", async () => {
await snapshot(<KanbanBoard />, "/kanban");
});
it("Sessions", async () => {
await snapshot(<Sessions />, "/sessions");
});
it("Session detail", async () => {
await snapshot(
<Routes>
<Route path="/sessions/:id" element={<SessionDetail />} />
</Routes>,
"/sessions/sess-1"
);
});
it("Activity feed", async () => {
await snapshot(<ActivityFeed />, "/activity");
});
it("Analytics", async () => {
await snapshot(<Analytics />, "/analytics");
});
it("Workflows", async () => {
await snapshot(<Workflows />, "/workflows");
});
it("Claude Config", async () => {
await snapshot(<CcConfig />, "/cc-config");
});
it("Run", async () => {
await snapshot(<Workspace />, "/run");
});
it("Settings", async () => {
await snapshot(<Settings />, "/settings");
});
it("Not found", async () => {
await snapshot(<NotFound />, "/nope");
});
});