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
+227
View File
@@ -0,0 +1,227 @@
/**
* @file Select.tsx
* @description Generic styled dropdown that replaces native `<select>` elements
* for consistent cross-platform rendering. Native macOS/Chromium selects reserve
* checkmark space inconsistently; this control aligns labels, supports arrow-key
* navigation, flips above the trigger when viewport space is tight, and marks
* the active option with accent color plus a Lucide check.
*
* ## Consumers
* Used on the Run Claude page and webhook settings form wherever a compact
* enum picker is needed.
*
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
/* =============================================================================
* MODULE_GUIDE — extended in-file reference (comments only; safe to read, never executed)
* =============================================================================
* **Purpose:** Dashboard module consumed by the React client, MCP tools, or desktop shell depending on deployment mode.
*
* ## Design constraints
* - Local-first: no telemetry leaves the machine unless the user configures webhooks.
* - Fail-safe hooks path on the server must never block Claude Code; UI mirrors that
* philosophy by degrading gracefully (empty states, stale badges, reconnect loops).
* - Destructive flows stay behind explicit confirmation modals and server-side gates.
* - Internationalization: user-visible strings belong in i18n JSON, not literals here.
*
* ## Remote data & SSH
* Remote Data Sources let operators aggregate multiple machines. SSH entries describe
* how to reach a peer dashboard; the global data scope (`dataScope.ts`) narrows every
* scoped GET via `?sources=`. Health checks and import history surface in Settings.
*
* ## Observability
* Prometheus scrapes `GET /api/metrics` (see `monitoring/`). Grafana ships four
* provisioned boards (overview, sessions, tools, alerts). Native npm scripts and
* Docker Compose profiles are documented in `monitoring/README.md`.
*
* ## Public surface
* - `SelectOption` — exported API; see TSDoc on the symbol for behavior.
* - `SelectProps` — exported API; see TSDoc on the symbol for behavior.
* - `Select` — exported API; see TSDoc on the symbol for behavior.
*
* ## Testing pointers
* - Prefer colocated `__tests__` with Vitest + Testing Library for UI.
* - Server contract changes require `npm run test:server` and OpenAPI sync.
* - MCP edits: `npm run mcp:typecheck` and `npm run mcp:build`.
*
* ## Related docs
* - `ARCHITECTURE.md` — hooks → API → SQLite → WebSocket → UI pipeline.
* - `docs/API.md` — REST reference.
* - `.claude/skills/file-headers/` — mandatory `@author` header policy.
* ============================================================================= */
/* -----------------------------------------------------------------------------
* EXPORT CATALOG — quick index of symbols defined below (documentation only).
* -----------------------------------------------------------------------------
* **SelectOption**
* Part of this module's public contract. Downstream imports should treat
* the signature and return type as stable unless release notes say otherwise.
* When behavior changes, update the `@file` overview and relevant tests.
*
* **SelectProps**
* Part of this module's public contract. Downstream imports should treat
* the signature and return type as stable unless release notes say otherwise.
* When behavior changes, update the `@file` overview and relevant tests.
*
* **Select**
* Part of this module's public contract. Downstream imports should treat
* the signature and return type as stable unless release notes say otherwise.
* When behavior changes, update the `@file` overview and relevant tests.
*
* ----------------------------------------------------------------------------- */
import { useEffect, useRef, useState } from "react";
import { ChevronDown, Check } from "lucide-react";
/** Single option in a {@link Select} list. */
export interface SelectOption<T extends string> {
/** Stored value emitted via `onChange`. */
value: T;
/** Primary label shown in the trigger and list. */
label: string;
/** Optional secondary line (smaller gray text). */
hint?: string;
}
/** Props for {@link Select}. */
export interface SelectProps<T extends string> {
/** Currently selected value. */
value: T;
/** Called when the user picks a new option. */
onChange: (v: T) => void;
/** All choices; must be non-empty for sensible rendering. */
options: SelectOption<T>[];
/** Disables opening the list. */
disabled?: boolean;
}
/**
* Accessible custom select control.
* @typeParam T - string union of allowed option values.
*/
export function Select<T extends string>({ value, onChange, options, disabled }: SelectProps<T>) {
const [open, setOpen] = useState(false);
const [active, setActive] = useState(() =>
Math.max(
0,
options.findIndex((o) => o.value === value)
)
);
const [openUp, setOpenUp] = useState(false);
const containerRef = useRef<HTMLDivElement | null>(null);
const buttonRef = useRef<HTMLButtonElement | null>(null);
useEffect(() => {
if (!open) return;
const onClick = (e: MouseEvent) => {
if (!containerRef.current) return;
if (!containerRef.current.contains(e.target as Node)) setOpen(false);
};
document.addEventListener("mousedown", onClick);
return () => document.removeEventListener("mousedown", onClick);
}, [open]);
// When opening, decide whether to render the popover above the trigger if
// the viewport doesn't have room below (common when this select sits at
// the bottom of a form). 288 px = `max-h-72`.
useEffect(() => {
if (!open || !buttonRef.current) return;
const rect = buttonRef.current.getBoundingClientRect();
const below = window.innerHeight - rect.bottom;
const above = rect.top;
setOpenUp(below < 288 && above > below);
}, [open]);
// Sync active highlight with current value when reopening
useEffect(() => {
if (open) {
const idx = options.findIndex((o) => o.value === value);
setActive(idx >= 0 ? idx : 0);
}
}, [open, value, options]);
const choose = (opt: SelectOption<T>) => {
onChange(opt.value);
setOpen(false);
buttonRef.current?.focus();
};
const onKey = (e: React.KeyboardEvent<HTMLButtonElement>) => {
if (
!open &&
(e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Enter" || e.key === " ")
) {
e.preventDefault();
setOpen(true);
return;
}
if (e.key === "ArrowDown") {
e.preventDefault();
setActive((a) => Math.min(options.length - 1, a + 1));
} else if (e.key === "ArrowUp") {
e.preventDefault();
setActive((a) => Math.max(0, a - 1));
} else if (e.key === "Enter") {
e.preventDefault();
const opt = options[active];
if (opt) choose(opt);
} else if (e.key === "Escape") {
setOpen(false);
}
};
const current = options.find((o) => o.value === value) || options[0];
return (
<div ref={containerRef} className="relative">
<button
ref={buttonRef}
type="button"
disabled={disabled}
onClick={() => setOpen((v) => !v)}
onKeyDown={onKey}
className="w-full flex items-center justify-between gap-2 bg-surface-2 border border-border rounded-md px-3 py-1.5 text-[11px] text-gray-100 focus:outline-none focus:border-accent/50 hover:bg-surface-3 transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
>
<span className="truncate">{current?.label ?? "-"}</span>
<ChevronDown className="w-3.5 h-3.5 text-gray-500 flex-shrink-0" />
</button>
{open && (
<div
className={`absolute z-30 left-0 right-0 rounded-md border border-border bg-surface-1 shadow-lg shadow-black/40 max-h-72 overflow-auto py-1 ${
openUp ? "bottom-full mb-1" : "top-full mt-1"
}`}
>
{options.map((opt, idx) => {
const isSelected = opt.value === value;
const isActive = idx === active;
return (
<button
key={opt.value || "__default__"}
type="button"
onMouseDown={(e) => e.preventDefault()}
onClick={() => choose(opt)}
onMouseEnter={() => setActive(idx)}
className={`w-full text-left px-3 py-1.5 transition-colors ${
isActive ? "bg-accent/15" : isSelected ? "bg-surface-3" : "hover:bg-surface-3"
}`}
>
<div className="flex items-center gap-2">
<span
className={`text-[11px] flex-1 truncate ${
isSelected ? "text-accent font-medium" : "text-gray-200"
}`}
>
{opt.label}
</span>
{isSelected && <Check className="w-3 h-3 text-accent flex-shrink-0" />}
</div>
{opt.hint && (
<div className="text-[10px] text-gray-500 truncate mt-0.5">{opt.hint}</div>
)}
</button>
);
})}
</div>
)}
</div>
);
}