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:
@@ -0,0 +1,457 @@
|
||||
/**
|
||||
* @file Tests for DestructiveLaneModal: the preflight-gated confirmation for
|
||||
* reset/remove/purge. Covers that the displayed counts are exactly the
|
||||
* preflight facts, that `reset` is refused for adopted/missing/unreadable
|
||||
* lanes while `remove` stays available for all of them (the server permits it,
|
||||
* so the UI must not be stricter), that the Force checkbox appears exactly when
|
||||
* the server would demand force, and that confirming echoes back exactly the
|
||||
* `expect` block the modal displayed.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||
|
||||
const { preflightMock } = vi.hoisted(() => ({ preflightMock: vi.fn() }));
|
||||
vi.mock("../../../lib/api", () => ({
|
||||
api: { lanes: { preflight: preflightMock } },
|
||||
}));
|
||||
|
||||
import { DestructiveLaneModal } from "../DestructiveLaneModal";
|
||||
import type { Lane, LanePurgePreflight, LaneWorktreePreflight } from "../../../lib/types";
|
||||
|
||||
function makeLane(overrides: Partial<Lane> = {}): Lane {
|
||||
return {
|
||||
id: 1,
|
||||
title: "demo",
|
||||
cwd: "/work/demo",
|
||||
branch: "lane/demo",
|
||||
kind: "managed",
|
||||
pipeline: "default",
|
||||
session_id: null,
|
||||
run_id: null,
|
||||
stage: "plan",
|
||||
stage_since: null,
|
||||
status: "idle",
|
||||
gate_decision: null,
|
||||
ci_status: null,
|
||||
needs_action: null,
|
||||
links: {},
|
||||
stages: {},
|
||||
notes: null,
|
||||
pipeline_name: "Default",
|
||||
pipeline_nodes: [],
|
||||
progress: 0,
|
||||
stage_seconds: null,
|
||||
last_event_seconds: null,
|
||||
liveness: "idle",
|
||||
detected_stage: null,
|
||||
detected_signal: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function worktreePreflight(overrides: Partial<LaneWorktreePreflight> = {}): LaneWorktreePreflight {
|
||||
return {
|
||||
action: "reset",
|
||||
lane: 1,
|
||||
kind: "managed",
|
||||
branch: "lane/demo",
|
||||
head: "abc1234",
|
||||
dirty: 2,
|
||||
untracked: 3,
|
||||
unpushed: 0,
|
||||
blocked: [],
|
||||
warnings: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
preflightMock.mockReset();
|
||||
});
|
||||
|
||||
describe("DestructiveLaneModal", () => {
|
||||
it("renders exactly the counts the preflight returned", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({ head: "deadbee", dirty: 4, untracked: 5, unpushed: 0 })
|
||||
);
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="reset"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={vi.fn()}
|
||||
/>
|
||||
);
|
||||
expect(await screen.findByText("deadbee")).toBeInTheDocument();
|
||||
expect(screen.getByText("4")).toBeInTheDocument();
|
||||
expect(screen.getByText("5")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("disables RESET for an adopted lane (a worktree action can never touch it)", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({ action: "reset", kind: "adopted", blocked: ["adopted"] })
|
||||
);
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane({ kind: "adopted" })}
|
||||
action="reset"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={vi.fn()}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Reset worktree" });
|
||||
await waitFor(() => expect(confirmButton).toBeDisabled());
|
||||
});
|
||||
|
||||
it("ENABLES remove for an adopted lane and sends the payload — the server forgets the row and leaves the directory alone", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({
|
||||
action: "remove",
|
||||
kind: "adopted",
|
||||
head: "adopt01",
|
||||
dirty: 0,
|
||||
untracked: 0,
|
||||
unpushed: 0,
|
||||
blocked: ["adopted"],
|
||||
})
|
||||
);
|
||||
const onConfirm = vi.fn();
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane({ kind: "adopted" })}
|
||||
action="remove"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Remove lane" });
|
||||
await waitFor(() => expect(confirmButton).not.toBeDisabled());
|
||||
// "adopted" is shown as context, not as a refusal.
|
||||
expect(screen.getByText(/Only the dashboard's record of it is dropped/)).toBeInTheDocument();
|
||||
fireEvent.click(confirmButton);
|
||||
expect(onConfirm).toHaveBeenCalledWith({
|
||||
expect: { head: "adopt01", dirty: 0, untracked: 0, unpushed: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("forgets an adopted lane with unpushed commits without offering Force — the server does not require it", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({
|
||||
action: "remove",
|
||||
kind: "adopted",
|
||||
head: "adopt02",
|
||||
dirty: 0,
|
||||
untracked: 0,
|
||||
unpushed: 7,
|
||||
blocked: ["adopted", "unpushed-commits"],
|
||||
})
|
||||
);
|
||||
const onConfirm = vi.fn();
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane({ kind: "adopted" })}
|
||||
action="remove"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Remove lane" });
|
||||
await waitFor(() => expect(confirmButton).not.toBeDisabled());
|
||||
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
|
||||
fireEvent.click(confirmButton);
|
||||
expect(onConfirm).toHaveBeenCalledWith({
|
||||
expect: { head: "adopt02", dirty: 0, untracked: 0, unpushed: 7 },
|
||||
});
|
||||
});
|
||||
|
||||
it("disables RESET when the worktree directory is missing", async () => {
|
||||
preflightMock.mockResolvedValue(worktreePreflight({ action: "reset", blocked: ["missing"] }));
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="reset"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={vi.fn()}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Reset worktree" });
|
||||
await waitFor(() => expect(confirmButton).toBeDisabled());
|
||||
});
|
||||
|
||||
it("ENABLES remove when the worktree directory is missing — the server takes the prune path", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({
|
||||
action: "remove",
|
||||
head: null,
|
||||
dirty: 0,
|
||||
untracked: 0,
|
||||
unpushed: 0,
|
||||
blocked: ["missing"],
|
||||
})
|
||||
);
|
||||
const onConfirm = vi.fn();
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="remove"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Remove lane" });
|
||||
await waitFor(() => expect(confirmButton).not.toBeDisabled());
|
||||
expect(screen.getByText(/The lane directory is already gone/)).toBeInTheDocument();
|
||||
fireEvent.click(confirmButton);
|
||||
expect(onConfirm).toHaveBeenCalledWith({
|
||||
expect: { head: null, dirty: 0, untracked: 0, unpushed: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("disables RESET when the worktree directory is unreadable", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({ action: "reset", blocked: ["unreadable"] })
|
||||
);
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="reset"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={vi.fn()}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Reset worktree" });
|
||||
await waitFor(() => expect(confirmButton).toBeDisabled());
|
||||
});
|
||||
|
||||
it("ENABLES remove when the worktree directory is unreadable — the server attempts removal and falls back to deregistering it", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({
|
||||
action: "remove",
|
||||
head: null,
|
||||
dirty: 0,
|
||||
untracked: 0,
|
||||
unpushed: 0,
|
||||
blocked: ["unreadable"],
|
||||
})
|
||||
);
|
||||
const onConfirm = vi.fn();
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="remove"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Remove lane" });
|
||||
await waitFor(() => expect(confirmButton).not.toBeDisabled());
|
||||
expect(screen.getByText(/cannot be read as a Git worktree/)).toBeInTheDocument();
|
||||
fireEvent.click(confirmButton);
|
||||
expect(onConfirm).toHaveBeenCalledWith({
|
||||
expect: { head: null, dirty: 0, untracked: 0, unpushed: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("shows the Force checkbox only when the sole blocker is unpushed commits, and ticking it enables confirm", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({ action: "remove", unpushed: 3, blocked: ["unpushed-commits"] })
|
||||
);
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="remove"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={vi.fn()}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Remove lane" });
|
||||
await waitFor(() => expect(confirmButton).toBeDisabled());
|
||||
fireEvent.click(screen.getByRole("checkbox"));
|
||||
expect(confirmButton).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it("a local-only managed lane (no-remote warning, unpushed-commits blocker) is resettable with Force", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({
|
||||
action: "reset",
|
||||
unpushed: 3,
|
||||
blocked: ["unpushed-commits"],
|
||||
warnings: ["no-remote"],
|
||||
})
|
||||
);
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="reset"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={vi.fn()}
|
||||
/>
|
||||
);
|
||||
// The warning is shown as context, not as an obstacle.
|
||||
expect(await screen.findByText(/No Git remote is configured/)).toBeInTheDocument();
|
||||
const confirmButton = await screen.findByRole("button", { name: "Reset worktree" });
|
||||
await waitFor(() => expect(confirmButton).toBeDisabled());
|
||||
fireEvent.click(screen.getByRole("checkbox"));
|
||||
expect(confirmButton).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it("does not offer the Force checkbox when a hard blocker makes confirming impossible anyway", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({ action: "reset", unpushed: 3, blocked: ["unpushed-commits", "missing"] })
|
||||
);
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="reset"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={vi.fn()}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Reset worktree" });
|
||||
await waitFor(() => expect(confirmButton).toBeDisabled());
|
||||
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("confirming a worktree action passes back exactly the expect block that was displayed", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({ head: "cafefeed", dirty: 1, untracked: 2, unpushed: 0, blocked: [] })
|
||||
);
|
||||
const onConfirm = vi.fn();
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="reset"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Reset worktree" });
|
||||
await waitFor(() => expect(confirmButton).not.toBeDisabled());
|
||||
fireEvent.click(confirmButton);
|
||||
expect(onConfirm).toHaveBeenCalledWith({
|
||||
expect: { head: "cafefeed", dirty: 1, untracked: 2, unpushed: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("confirming an unpushed-commits removal with Force ticked sends force:true plus the same expect block", async () => {
|
||||
preflightMock.mockResolvedValue(
|
||||
worktreePreflight({
|
||||
action: "remove",
|
||||
head: "abc0000",
|
||||
dirty: 0,
|
||||
untracked: 0,
|
||||
unpushed: 2,
|
||||
blocked: ["unpushed-commits"],
|
||||
})
|
||||
);
|
||||
const onConfirm = vi.fn();
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="remove"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Remove lane" });
|
||||
fireEvent.click(screen.getByRole("checkbox"));
|
||||
await waitFor(() => expect(confirmButton).not.toBeDisabled());
|
||||
fireEvent.click(confirmButton);
|
||||
expect(onConfirm).toHaveBeenCalledWith({
|
||||
expect: { head: "abc0000", dirty: 0, untracked: 0, unpushed: 2 },
|
||||
force: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("confirming a purge passes back the purge-specific expect block", async () => {
|
||||
const purgePreflight: LanePurgePreflight = {
|
||||
action: "purge",
|
||||
lane: 1,
|
||||
sessions: 4,
|
||||
events: 120,
|
||||
tokenRows: 30,
|
||||
bytesEstimate: 4096,
|
||||
activeSessionSkipped: false,
|
||||
};
|
||||
preflightMock.mockResolvedValue(purgePreflight);
|
||||
const onConfirm = vi.fn();
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="purge"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
);
|
||||
const confirmButton = await screen.findByRole("button", { name: "Purge history" });
|
||||
await waitFor(() => expect(confirmButton).not.toBeDisabled());
|
||||
fireEvent.click(confirmButton);
|
||||
expect(onConfirm).toHaveBeenCalledWith({
|
||||
expect: { sessions: 4, events: 120, tokenRows: 30 },
|
||||
});
|
||||
});
|
||||
|
||||
it("shows the purge size estimate and says when a live session was spared", async () => {
|
||||
const purgePreflight: LanePurgePreflight = {
|
||||
action: "purge",
|
||||
lane: 1,
|
||||
sessions: 2,
|
||||
events: 8,
|
||||
tokenRows: 2,
|
||||
bytesEstimate: 5120,
|
||||
activeSessionSkipped: true,
|
||||
};
|
||||
preflightMock.mockResolvedValue(purgePreflight);
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="purge"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={vi.fn()}
|
||||
/>
|
||||
);
|
||||
expect(await screen.findByText("5.0 KB")).toBeInTheDocument();
|
||||
expect(screen.getByText(/still active and will be kept/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not claim a session was spared when none was", async () => {
|
||||
const purgePreflight: LanePurgePreflight = {
|
||||
action: "purge",
|
||||
lane: 1,
|
||||
sessions: 1,
|
||||
events: 1,
|
||||
tokenRows: 0,
|
||||
bytesEstimate: 512,
|
||||
activeSessionSkipped: false,
|
||||
};
|
||||
preflightMock.mockResolvedValue(purgePreflight);
|
||||
render(
|
||||
<DestructiveLaneModal
|
||||
lane={makeLane()}
|
||||
action="purge"
|
||||
open
|
||||
onClose={vi.fn()}
|
||||
onConfirm={vi.fn()}
|
||||
/>
|
||||
);
|
||||
expect(await screen.findByText("512 B")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/still active and will be kept/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user