/** @canvas * title: Canvas plugin self-check * description: Every kit surface the host test suite compiles, rendered as one board. * icon: board */ import { H1, H2, Text, Code, Stack, Row, Grid, Divider, Card, CardBody, CardHeader, Callout, Stat, Table, BarChart, TodoList, Pill, Button, CollapsibleSection, Progress, KeyValue, Timeline, useCanvasState, useCanvasOverlay, useCanvasAction, useMemo, } from "dsh/canvas"; type Status = "pending" | "in_progress" | "completed" | "cancelled"; type Tone = "neutral" | "info" | "success" | "warning" | "danger"; export const DATA = { checks: [ { id: "c1", title: "compile -> serve -> import -> render", status: "completed", host: "test/serve.test.mjs", note: "22 assertions" }, { id: "c2", title: "literal DATA extraction", status: "completed", host: "test/core.test.mjs", note: "15 assertions" }, { id: "c3", title: "client bundle served and rendered by the GUI", status: "completed", host: "/plugins/@local/dsh-canvas/client.js", note: "renders in the running page" }, { id: "c4", title: "sidecar overlay round-trip", status: "completed", host: ".canvas/.state.json", note: "verified on disk" }, { id: "c5", title: "human edits merge back into DATA", status: "completed", host: "test/merge.test.mjs + test/tools.test.mjs", note: "20 assertions" }, { id: "c6", title: "canvas intent entry on agent/pre-step", status: "completed", host: "test/intent.test.mjs", note: "13 assertions" }, { id: "c7", title: "canvas tab owns its scroll container and padding", status: "completed", host: "test/client.test.mjs", note: "asserted on both render paths" }, { id: "c8", title: "typography mapped to the harness scale", status: "completed", host: "test/client.test.mjs", note: "13px base, no multiplier" }, { id: "c9", title: "startTurn passes mode + AbortSignal", status: "completed", host: "test/serve.test.mjs", note: "the button really queues a turn" }, { id: "c10", title: "action envelope matches on both halves", status: "completed", host: "test/client.test.mjs", note: "client body asserted, host tolerant" }, { id: "c11", title: "runCommand verified on the live host", status: "completed", host: "POST /canvas/action", note: "gate:tests + gate:templates exit=0" }, { id: "c12", title: "intent entry ignores canvas-submitted tasks", status: "completed", host: "test/intent.test.mjs", note: "canvas-* rpcId filtered" }, ], series: [ { name: "host tests", data: [16, 15, 0] , tone: "info" }, { name: "browser tests", data: [0, 0, 0], tone: "warning" }, ], stages: ["host e2e", "host core", "browser"], activity: [ { id: "a1", at: "2026-09-27", title: "host pipeline verified", tone: "success", detail: "compile / module / action / sidecar over HTTP", ref: "test/serve.test.mjs" }, { id: "a2", at: "2026-09-28", title: "canvas tab renders in the GUI", tone: "success", detail: "kit v1.1 components render; tab body now owns its scroll container", ref: "lib/client.js" }, { id: "a3", at: "2026-09-28", title: "typography mapped to the harness scale", tone: "success", detail: "sidebar base is 13px; the invalid --dsw-font-mono token is gone", ref: "lib/client.js" }, ], } as const; const STATUS_TONE: Record = { pending: "neutral", in_progress: "warning", completed: "success", cancelled: "danger", }; function label(s: Status) { if (s === "completed") return "done"; if (s === "in_progress") return "active"; if (s === "cancelled") return "cancelled"; return "todo"; } export default function SelfCheck() { const dispatch = useCanvasAction(); const [filter, setFilter] = useCanvasState("filter", "all"); const [activeId, setActiveId] = useCanvasState("active", "c1"); const overlay = useCanvasOverlay("checks", DATA.checks as unknown as Array<{ id: string; status: Status }>); const rows = useMemo( () => overlay.items.map((row) => ({ ...row, status: (row.status ?? "pending") as Status })), [overlay.items], ); const open = rows.filter((row) => row.status !== "completed" && row.status !== "cancelled"); const visible = filter === "open" ? open : rows; const active = rows.find((row) => row.id === activeId) ?? rows[0]; const done = rows.filter((row) => row.status === "completed").length; return (

Canvas plugin self-check

The host compiles this file and renders it live. Edit it and the tab updates; the buttons below hand work back to the agent. The client bundle is only served after the GUI restarts, because the running page composed its module graph before this plugin was installed.

Assertions per stage

({ name: s.name, data: s.data as unknown as number[], tone: s.tone as Tone }))} beginAtZero height={200} /> setFilter("all")}>All {rows.length} setFilter("open")}>Open {open.length} overlay.set("c1", { status: "pending" })}>Reset c1

Checks

({ id: row.id, status: row.status, content: row.title }))} onTodoClick={(todo) => setActiveId(todo.id)} /> Clicking a row writes to the canvas sidecar, which the agent reads through canvas_read. Without it, human clicks would be invisible to the agent.
{active ? ( {label(active.status)}}> {active.id.toUpperCase()} {active.title} {active.host} }, { label: "Note", value: active.note }, ]} /> ) : null}

Detail

STATUS_TONE[row.status])} rows={rows.map((row) => [row.id, row.title, row.host, row.status])} onRowClick={(index) => setActiveId(rows[index].id)} /> ({ id: event.id, at: event.at, title: event.title, tone: event.tone, detail: event.detail, ref: event.ref, }))} /> ); }