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,193 @@
/**
* @file AddLaneModal.tsx
* @description The "+ Add lane" flow: pick a SOURCE repo (not a folder to
* adopt), pick which of its branches to fork from, name the feature, and the
* dashboard provisions a managed git worktree via `POST /api/lanes/worktree`
* — the dashboard invents the lane's own directory and branch name, the same
* way Shipyard's "+ Add lane" never asks a human to name a folder. The lane
* returned is `status: "provisioning"`; the existing `lane_update` WebSocket
* subscription in the Workspace page flips it to idle when the worktree is
* actually ready, so this component does not poll.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { ConfirmModal } from "../ConfirmModal";
import { CwdAutocomplete } from "../run/RunSetup";
import { api } from "../../lib/api";
import type { CwdSuggestion } from "../../lib/api";
import type { Lane } from "../../lib/types";
export function AddLaneModal({
open,
onClose,
onAdded,
cwdSuggestions,
}: {
open: boolean;
onClose: () => void;
/** Called with the newly provisioned (still-provisioning) lane. */
onAdded: (lane: Lane) => void;
/** The same suggestion list the Run form already fetched (dashboard cwd,
* home, recently-used paths) — reused rather than fetched a second time. */
cwdSuggestions: CwdSuggestion[];
}) {
const { t } = useTranslation(["lanes"]);
const [sourceRepo, setSourceRepo] = useState("");
const [title, setTitle] = useState("");
const [branches, setBranches] = useState<string[] | null>(null);
const [base, setBase] = useState("");
const [branchesError, setBranchesError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const reset = () => {
setSourceRepo("");
setTitle("");
setBranches(null);
setBase("");
setBranchesError(null);
setError(null);
setBusy(false);
};
// Look up the repo's branches once the path settles - debounced so every
// keystroke while typing a path doesn't fire a request against a path that
// isn't finished yet.
const lookedUpFor = useRef<string>("");
useEffect(() => {
const path = sourceRepo.trim();
if (!path) {
setBranches(null);
setBase("");
setBranchesError(null);
return;
}
const timer = window.setTimeout(async () => {
lookedUpFor.current = path;
try {
const r = await api.lanes.branches(path);
if (lookedUpFor.current !== path) return; // a newer path superseded this one
setBranches(r.branches);
setBase(r.current || r.branches[0] || "");
setBranchesError(null);
} catch {
if (lookedUpFor.current !== path) return;
// Not yet a valid repo path (still being typed, or genuinely wrong) -
// quiet by design, the same way CwdAutocomplete never errors either.
setBranches(null);
setBase("");
setBranchesError(t("addLaneNotARepo"));
}
}, 300);
return () => window.clearTimeout(timer);
}, [sourceRepo, t]);
const submit = async () => {
const repo = sourceRepo.trim();
const name = title.trim();
if (!repo || !branches || !name) return;
setBusy(true);
setError(null);
try {
const result = await api.lanes.worktree({
sourceRepo: repo,
title: name,
base: base || undefined,
});
reset();
onAdded(result.lane);
onClose();
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
setBusy(false);
}
};
// ConfirmModal re-focuses its Cancel button in an effect keyed on `onCancel`'s
// identity. Every keystroke in the fields below re-renders this component; an
// inline `() => {...}` handed to `onCancel` would get a new identity each
// time, re-running that effect and yanking focus off the field being typed
// into after the very first character. useCallback keeps the identity stable
// across renders so only mount/unmount (and a real onClose change) refocuses.
const handleCancel = useCallback(() => {
reset();
onClose();
}, [onClose]);
return (
<ConfirmModal
open={open}
title={t("addLane")}
confirmLabel={t("add")}
cancelLabel={t("destructive.cancel")}
destructive={false}
busy={busy}
disabled={!sourceRepo.trim() || !title.trim() || !branches}
onConfirm={submit}
onCancel={handleCancel}
>
<div className="space-y-3">
<div>
<label className="mb-1 block text-xs text-neutral-400" htmlFor="add-lane-repo">
{t("addLaneRepoLabel")}
</label>
<CwdAutocomplete
inputId="add-lane-repo"
value={sourceRepo}
onChange={setSourceRepo}
suggestions={cwdSuggestions}
/>
<p className="mt-1 text-[10px] text-neutral-500">{t("addLaneRepoHint")}</p>
</div>
<div>
<label className="mb-1 block text-xs text-neutral-400" htmlFor="add-lane-title">
{t("addLaneTitleLabel")}
</label>
<input
id="add-lane-title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder={t("addLaneTitlePlaceholder")}
className="w-full rounded-md border border-neutral-700 bg-neutral-900 px-3 py-1.5 text-xs text-neutral-100 placeholder:text-neutral-600 focus:border-blue-400 focus:outline-none"
/>
</div>
{branches && (
<div>
<label className="mb-1 block text-xs text-neutral-400" htmlFor="add-lane-base">
{t("addLaneBaseLabel")}
</label>
{branches.length === 0 ? (
<p className="text-[10px] text-neutral-500">{t("addLaneNoBranches")}</p>
) : (
<select
id="add-lane-base"
value={base}
onChange={(e) => setBase(e.target.value)}
className="w-full rounded-md border border-neutral-700 bg-neutral-900 px-3 py-1.5 text-xs text-neutral-100 focus:border-blue-400 focus:outline-none"
>
{branches.map((b) => (
<option key={b} value={b}>
{b}
</option>
))}
</select>
)}
</div>
)}
{branchesError && !branches && (
<p className="text-[10px] text-amber-400">{branchesError}</p>
)}
{error && (
<p role="alert" className="text-xs text-red-400">
{error}
</p>
)}
</div>
</ConfirmModal>
);
}