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,62 @@
/**
* @file The lane pipeline map: a horizontal chain of stage nodes coloured by
* state. Layout is computed from the node list (flex + connectors), never from
* hardcoded coordinates, so a lane can use a longer or shorter template without
* touching this component. "passed without evidence" is deliberately its own
* colour: a stage the agent claimed but left no artifact for is not the same as
* a stage that is genuinely done. A `detected` node (the server's heuristic saw
* tool-event evidence but the agent never declared it) gets a FOURTH treatment —
* dashed amber, overriding whatever `state` it carries — because it must never
* be mistaken for the solid green of a real "done".
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import type { LaneNode } from "../../lib/types";
const STATE_CLASS: Record<LaneNode["state"], string> = {
done: "border-emerald-500 text-emerald-400 bg-emerald-500/10",
current: "border-blue-400 text-blue-300 bg-blue-500/20 ring-2 ring-blue-400/40",
"passed-no-evidence": "border-amber-500 text-amber-400 bg-amber-500/10",
failed: "border-red-500 text-red-400 bg-red-500/10",
pending: "border-neutral-700 text-neutral-500 bg-transparent",
};
// Dashed border distinguishes an inferred stage from every other class above,
// including the solid amber of "passed-no-evidence" — never let it read as done.
const DETECTED_CLASS = "border-dashed border-amber-400 text-amber-300 bg-amber-500/5";
export default function PipelineMap({
nodes,
detectedSignal,
}: {
nodes: LaneNode[];
detectedSignal?: string | null;
}) {
if (!nodes.length) return <div className="text-xs text-neutral-500">no pipeline</div>;
return (
// The map spans the panel: every node takes an equal share and the
// connectors absorb the slack, so the pipeline reads as one track across
// the width rather than a short cluster hugging the left edge.
<div className="flex w-full items-center py-1">
{nodes.map((n, i) => (
<div key={n.id} className="flex min-w-0 flex-1 items-center">
<div
data-testid={`pipeline-node-${n.id}`}
data-state={n.state}
data-detected={n.detected ? "true" : undefined}
title={
n.detected && detectedSignal
? `${n.label}${detectedSignal}`
: `${n.label}${n.state}`
}
className={`flex min-w-0 flex-1 items-center justify-center gap-1.5 rounded-full border px-2 py-1.5 text-[11px] ${n.detected ? DETECTED_CLASS : STATE_CLASS[n.state]}`}
>
<span className="shrink-0 text-[13px] leading-none">{n.icon}</span>
<span className="truncate">{n.label}</span>
</div>
{i < nodes.length - 1 && <div className="h-px w-2 shrink-0 bg-neutral-700 sm:w-3" />}
</div>
))}
</div>
);
}