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 648fea8dcb
783 changed files with 221554 additions and 0 deletions
@@ -0,0 +1,172 @@
/**
* @file RunConsole.test.tsx
* @description Pins the props-only boundary of `RunConsole` after its move out
* of `pages/Run.tsx`: the envelope stream renders from the `envelopes` prop
* (no stream subscription of its own), the token meter rolls up usage from
* those same envelopes, the prompt editor's `/` autocomplete filters and fills
* the prompt through `onFollowUpChange`, and `onSend` / `onStop` fire from the
* send and stop controls.
*
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { useState } from "react";
import { MemoryRouter } from "react-router-dom";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { RunConsole, type SlashCommand } from "../RunConsole";
import type { Envelope } from "../../../hooks/useRunStream";
import type { RunHandle } from "../../../lib/api";
const HANDLE: RunHandle = {
id: "run-1",
pid: 4242,
mode: "conversation",
cwd: "/tmp/project",
model: "claude-opus-5",
permissionMode: "acceptEdits",
effort: "",
prompt: "hi",
argv: [],
resumeSessionId: null,
status: "running",
startedAt: 1,
endedAt: null,
exitCode: null,
signal: null,
error: null,
sessionId: null,
envelopeCount: 0,
stdoutTail: "",
stderrTail: "",
};
const COMMANDS: SlashCommand[] = [
{ name: "code-review", description: "Review the working diff", source: "project" },
{ name: "compact", description: "Compact the conversation context", source: "builtin" },
{ name: "logout", description: "Sign out", source: "builtin" },
];
/**
* Mount the console with the parent-owned follow-up state it expects, so the
* autocomplete assertions exercise the real controlled-input round trip.
*/
function renderConsole(
props: Partial<React.ComponentProps<typeof RunConsole>> = {},
onFollowUp?: (s: string) => void
) {
const seen = { followUp: "" };
function Harness() {
const [followUp, setFollowUp] = useState("");
seen.followUp = followUp;
return (
<RunConsole
handle={HANDLE}
envelopes={[]}
mode="conversation"
isLive
hasFinished={false}
followUp={followUp}
onFollowUpChange={(s) => {
setFollowUp(s);
onFollowUp?.(s);
}}
busy={null}
onSend={() => {}}
onStop={() => {}}
onNewRun={() => {}}
slashCommands={COMMANDS}
{...props}
/>
);
}
render(
<MemoryRouter>
<Harness />
</MemoryRouter>
);
return seen;
}
describe("RunConsole", () => {
it("renders assistant text from the envelopes prop", () => {
const envelopes: Envelope[] = [
{ type: "user", message: { content: "explain this repo" } },
{ type: "assistant", message: { content: [{ type: "text", text: "Here is the answer." }] } },
] as Envelope[];
renderConsole({ envelopes });
expect(screen.getByText("explain this repo")).toBeInTheDocument();
expect(screen.getByText("Here is the answer.")).toBeInTheDocument();
});
it("shows the empty-stream placeholder when there are no envelopes", () => {
renderConsole({ isLive: false });
expect(screen.getByText("Nothing yet")).toBeInTheDocument();
});
it("shows the token totals computed from the envelopes", () => {
// Transcript-shaped assistant envelope (no `message.id`), which is the
// branch computeTokens folds into the running totals.
const envelopes: Envelope[] = [
{
type: "assistant",
message: {
content: [{ type: "text", text: "done" }],
usage: { input_tokens: 12_000, output_tokens: 2_500, cache_read_input_tokens: 8_000 },
},
},
] as Envelope[];
renderConsole({ envelopes });
// Context gauge: (input + cache read) / default 200k window.
// The CLI-style meter is one status line: context usage as a single label,
// then output and cache-hit figures with terminal glyphs. Input is implied
// by the context total rather than listed separately.
expect(screen.getByText("20.0k / 200k (10%)")).toBeInTheDocument();
expect(screen.getByText("↑2.5k")).toBeInTheDocument(); // Output
expect(screen.getByText("⚡8.0k")).toBeInTheDocument(); // Cache hit
});
it("filters slash commands as the user types and fills the prompt on pick", () => {
const seen = renderConsole();
const textarea = screen.getByRole("textbox");
fireEvent.change(textarea, { target: { value: "/co" } });
expect(screen.getByText("/code-review")).toBeInTheDocument();
expect(screen.getByText("/compact")).toBeInTheDocument();
expect(screen.queryByText("/logout")).not.toBeInTheDocument();
fireEvent.click(screen.getByText("/code-review"));
expect(seen.followUp).toBe("/code-review");
expect(screen.queryByText("/compact")).not.toBeInTheDocument(); // dropdown closed
});
it("fires onSend from the send button with the prompt the parent holds", () => {
const onSend = vi.fn();
const seen = renderConsole({ onSend });
fireEvent.change(screen.getByRole("textbox"), { target: { value: "follow up please" } });
fireEvent.click(screen.getByRole("button", { name: /send/i }));
expect(onSend).toHaveBeenCalledTimes(1);
expect(seen.followUp).toBe("follow up please");
});
it("fires onStop from the stop control while live, and hides it when not", () => {
const onStop = vi.fn();
renderConsole({ onStop });
fireEvent.click(screen.getByRole("button", { name: /stop/i }));
expect(onStop).toHaveBeenCalledTimes(1);
});
it("hides the stop control and the follow-up editor once the run is not live", () => {
renderConsole({ isLive: false, hasFinished: true });
expect(screen.queryByRole("button", { name: /stop/i })).not.toBeInTheDocument();
expect(screen.queryByRole("textbox")).not.toBeInTheDocument();
});
});