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,75 @@
|
||||
/**
|
||||
* @file MarkdownContent.test.tsx
|
||||
* @description Tests for the lightweight markdown renderer used by the conversation viewer.
|
||||
* Focuses on the block parser since the inline parser is well-exercised by snapshot-style
|
||||
* DOM assertions.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { MarkdownContent } from "../MarkdownContent";
|
||||
|
||||
describe("<MarkdownContent />", () => {
|
||||
it("renders fenced code blocks with the language label", () => {
|
||||
render(<MarkdownContent text={"Here is some code:\n```js\nconst x = 1;\n```"} />);
|
||||
// The CodeBlock header shows the language
|
||||
expect(screen.getByText(/javascript/i)).toBeInTheDocument();
|
||||
// The code text is present (split across syntax-highlighted spans, so use a substring)
|
||||
expect(screen.getByText(/const/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders headings as semantic-looking elements", () => {
|
||||
render(<MarkdownContent text={"# Title\n\nbody"} />);
|
||||
expect(screen.getByText("Title")).toBeInTheDocument();
|
||||
expect(screen.getByText("body")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders unordered and ordered lists", () => {
|
||||
const { container } = render(<MarkdownContent text={"- one\n- two\n\n1. first\n2. second"} />);
|
||||
expect(container.querySelectorAll("ul li")).toHaveLength(2);
|
||||
expect(container.querySelectorAll("ol li")).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("renders blockquotes", () => {
|
||||
const { container } = render(<MarkdownContent text={"> a quote"} />);
|
||||
expect(container.querySelector("blockquote")).not.toBeNull();
|
||||
expect(screen.getByText("a quote")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders inline code, bold, and italic", () => {
|
||||
const { container } = render(
|
||||
<MarkdownContent text={"This has `code`, **bold**, and *italic*."} />
|
||||
);
|
||||
expect(container.querySelector("code")).not.toBeNull();
|
||||
expect(container.querySelector("strong")).not.toBeNull();
|
||||
expect(container.querySelector("em")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("auto-links bare URLs and renders explicit markdown links", () => {
|
||||
const { container } = render(
|
||||
<MarkdownContent text={"See https://example.com or [docs](https://example.com/docs)."} />
|
||||
);
|
||||
const links = container.querySelectorAll("a");
|
||||
expect(links.length).toBe(2);
|
||||
expect(links[0]!.getAttribute("href")).toBe("https://example.com");
|
||||
expect(links[1]!.getAttribute("href")).toBe("https://example.com/docs");
|
||||
// Both should open in a new tab safely
|
||||
for (const a of links) {
|
||||
expect(a.getAttribute("target")).toBe("_blank");
|
||||
expect(a.getAttribute("rel")).toContain("noopener");
|
||||
}
|
||||
});
|
||||
|
||||
it("renders plain text without any markdown features", () => {
|
||||
render(<MarkdownContent text={"just a normal sentence."} />);
|
||||
expect(screen.getByText("just a normal sentence.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles empty input safely", () => {
|
||||
const { container } = render(<MarkdownContent text="" />);
|
||||
// Wrapper exists but no block elements
|
||||
expect(container.firstChild).not.toBeNull();
|
||||
expect(container.querySelectorAll("p, ul, ol, blockquote, pre, hr").length).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @file MessageList.sender.test.tsx
|
||||
* @description Verifies the transcript renders each message under its TRUE
|
||||
* sender label — User / Assistant / Main agent / System — instead of labeling
|
||||
* every `type:"user"` line "User" (reported transcript mis-attribution).
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { MessageList } from "../MessageList";
|
||||
import type { TranscriptMessage } from "../../../lib/types";
|
||||
|
||||
function msg(partial: Partial<TranscriptMessage>): TranscriptMessage {
|
||||
return {
|
||||
type: "user",
|
||||
timestamp: "2026-06-26T08:14:00.000Z",
|
||||
content: [{ type: "text", text: "hello" }],
|
||||
...partial,
|
||||
} as TranscriptMessage;
|
||||
}
|
||||
|
||||
describe("MessageList — sender attribution", () => {
|
||||
it("labels each row by its sender, not blanket 'User'", () => {
|
||||
const messages: TranscriptMessage[] = [
|
||||
msg({ sender: "user", content: [{ type: "text", text: "spin up a team" }] }),
|
||||
msg({
|
||||
type: "assistant",
|
||||
sender: "assistant",
|
||||
content: [{ type: "text", text: "on it" }],
|
||||
}),
|
||||
msg({
|
||||
sender: "system",
|
||||
content: [{ type: "text", text: "<task-notification>\n<task-id>x</task-id>\n" }],
|
||||
}),
|
||||
msg({ sender: "orchestrator", content: [{ type: "text", text: "Light research task…" }] }),
|
||||
];
|
||||
render(<MessageList messages={messages} loading={false} />);
|
||||
|
||||
expect(screen.getByText("User")).toBeInTheDocument();
|
||||
expect(screen.getByText("Assistant")).toBeInTheDocument();
|
||||
expect(screen.getByText("System")).toBeInTheDocument();
|
||||
expect(screen.getByText("Main agent")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("falls back to type-based labels when sender is absent (legacy payloads)", () => {
|
||||
const messages: TranscriptMessage[] = [
|
||||
msg({ content: [{ type: "text", text: "hi there" }] }), // no sender → "User"
|
||||
msg({ type: "assistant", content: [{ type: "text", text: "hello" }] }), // → "Assistant"
|
||||
];
|
||||
render(<MessageList messages={messages} loading={false} />);
|
||||
expect(screen.getByText("User")).toBeInTheDocument();
|
||||
expect(screen.getByText("Assistant")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user