Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/mermaid-surface-parts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"sideshow": minor
---

Add a `mermaid` surface part. Agents hand over diagram source (flowchart, sequence, ERD, gantt, state, …) and the viewer renders it natively to an SVG — themed in the sideshow palette and font (light and dark), with flowchart nodes/edges that can opt into the accent color via `:::accent` / `accentLine`. Available on all three tiers: a `mermaid` part over MCP and `POST /api/surfaces`, plus the CLI (`sideshow mermaid`, and `--mermaid` on `sideshow publish`). Rendered as data, not sandboxed markup — mermaid runs with securityLevel `strict`, and an invalid diagram shows its source in an error fallback instead of breaking the card.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,13 @@ from the viewer. Regenerate them with `node scripts/shoot-surfaces.mjs`.
</tr>
<tr>
<td width="50%" valign="top">
<img src="docs/surfaces/07-combined.png" width="100%" alt="markdown + diff — two parts composed in one card">
<img src="docs/surfaces/07-mermaid.png" width="100%" alt="mermaid part — a flowchart rendered from a few lines of text">
<p><b><code>mermaid</code></b> — a few lines of diagram source, rendered to an SVG in the sideshow palette. Tag nodes with <code>:::accent</code> to highlight them.</p>
</td>
<td width="50%" valign="top">
<img src="docs/surfaces/08-combined.png" width="100%" alt="markdown + diff — two parts composed in one card">
<p><b>Parts compose.</b> One card can carry several — here a <code>markdown</code> rationale stacked above its <code>diff</code>, so a single surface holds the why and the what.</p>
</td>
<td width="50%" valign="top"></td>
</tr>
</table>

Expand Down
23 changes: 23 additions & 0 deletions bin/sideshow.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ usage:
sideshow publish <file|-> [options] publish an HTML surface (one html part)
--title <t> surface title
--md <file|-> add a markdown part (prose) — combine with html
--mermaid <file|-> add a mermaid part (diagram source → SVG) — combine with html
--diff <file|-> add a diff part from a unified/git patch (combine with html)
--terminal <file|-> add a terminal part from monospace/ANSI output
--image <file> upload an image and append it as an image part
Expand Down Expand Up @@ -48,6 +49,9 @@ usage:
--term-title <t> label shown in the terminal window chrome
--cols <n> render width hint, in columns
(also: --session, --session-title, --agent, --new-session)
sideshow mermaid <file|-> [options] publish a mermaid surface (diagram → SVG)
--title <t> surface title
(also: --session, --session-title, --agent, --new-session)
sideshow update <id> <file|-> revise a surface (new version, same card)
--title <t> replace title
sideshow wait [options] block until the user comments (long-poll)
Expand Down Expand Up @@ -410,6 +414,7 @@ const commands = {
options: {
title: { type: "string" },
md: { type: "string" },
mermaid: { type: "string" },
diff: { type: "string" },
image: { type: "string" },
terminal: { type: "string" },
Expand All @@ -424,6 +429,9 @@ const commands = {
if (flags.md !== undefined) {
parts.push({ kind: "markdown", markdown: readContent(flags.md || "-") });
}
if (flags.mermaid !== undefined) {
parts.push({ kind: "mermaid", mermaid: readContent(flags.mermaid || "-") });
}
if (flags.diff !== undefined) {
parts.push({
kind: "diff",
Expand Down Expand Up @@ -577,6 +585,21 @@ const commands = {
out({ ...surface, url: `${BASE}/s/${surface.id}` });
},

async mermaid() {
const { values: flags, positionals } = parse({
allowPositionals: true,
options: {
title: { type: "string" },
session: { type: "string" },
"session-title": { type: "string" },
agent: { type: "string" },
"new-session": { type: "boolean" },
},
});
const parts = [{ kind: "mermaid", mermaid: readContent(positionals[0]) }];
outSurface(await publishSurface(parts, flags));
},

async update() {
const { values: flags, positionals } = parse({
allowPositionals: true,
Expand Down
Binary file added docs/surfaces/07-mermaid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
48 changes: 48 additions & 0 deletions e2e/mermaid.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { expect, publishParts, test } from "./fixtures.ts";

const DIAGRAM = [
"graph TD",
" A[Start] --> B{Choice}",
" B -->|yes| C[Do it]",
" B -->|no| D[Skip]",
].join("\n");

test("a mermaid part renders a diagram as inline SVG in the viewer", async ({ page, server }) => {
await publishParts(server.url, {
title: "Flow",
agent: "e2e",
parts: [{ kind: "mermaid", mermaid: DIAGRAM }],
});

await page.goto(server.url);
const card = page.locator(".card:not(#sessionThread)");
const mermaid = card.locator(".mermaidpart");

// rendered natively in the viewer document (no sandboxed iframe), as an SVG
const svg = mermaid.locator("svg");
await expect(svg).toBeVisible();
// the node labels made it into the rendered graph
await expect(mermaid).toContainText("Start");
await expect(mermaid).toContainText("Choice");
// it's structured SVG, not an error fallback
await expect(mermaid.locator(".mermaid-error")).toHaveCount(0);
});

test("an invalid mermaid part shows the source in an error fallback, not a crash", async ({
page,
server,
}) => {
await publishParts(server.url, {
title: "Broken",
agent: "e2e",
// no diagram-type keyword → mermaid can't even pick a parser, so it throws
parts: [{ kind: "mermaid", mermaid: "this is definitely not a valid diagram" }],
});

await page.goto(server.url);
const card = page.locator(".card:not(#sessionThread)");
const err = card.locator(".mermaidpart .mermaid-error");
await expect(err).toBeVisible();
// the original source is echoed so the agent can see what failed
await expect(err.locator("pre")).toContainText("not a valid diagram");
});
18 changes: 16 additions & 2 deletions guide/DESIGN_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ a `kind`:
interleave prose, tables, code, and pictures. Only raw _HTML_ in the source is
escaped, not rendered — reach for an `html` part when you need live markup
(interactivity, vector graphics, custom layout), not just to show a picture.
- **`mermaid`** — diagram source you hand over as _text_; the viewer renders it
to an SVG with mermaid (flowcharts, sequence diagrams, ERDs, gantt, state, …).
Reach for it when the shape of a system is the point — a flow, a state
machine, a schema — and you'd rather describe it than draw SVG by hand. Like
markdown it renders as data, not sandboxed markup (securityLevel `strict`); for
bespoke vector art hand-write inline `<svg>` in an `html` part instead. The
viewer themes the diagram in the sideshow palette and font automatically (light
and dark) — don't set your own colors. To highlight, two classes are
pre-wired to the accent color: in a flowchart, tag nodes with `:::accent`
(e.g. `B[Live render]:::accent`) or `class A,B accent`, and recolor an edge by
giving it `accentLine` (pair with `linkStyle`). Accents apply to flowcharts;
sequence diagrams style actors globally only.
- **`diff`** — a patch you hand over as _data_; the trusted viewer renders it
natively as a syntax-highlighted code review (split or unified). Reach for it
to show a changeset or review code, not to draw.
Expand All @@ -39,14 +51,15 @@ a `kind`:
A surface can combine parts, e.g. `[html, diff]` is a diagram with its code
review in one card, and `[markdown, diff]` is a written rationale above its
changeset. Trust differs: html parts are sandboxed because you author the
markup; markdown/diff/image/trace/terminal parts are rendered by the viewer
from data — send data, never markup.
markup; markdown/mermaid/diff/image/trace/terminal parts are rendered by the
viewer from data — send data, never markup.

A **`SurfacePart`** is one of:

```
{ "kind": "html", "html": "<p>...</p>" }
{ "kind": "markdown", "markdown": "## Plan\n\n1. ...\n2. ..." }
{ "kind": "mermaid", "mermaid": "graph TD; A[Start] --> B{Ok?}; B -->|yes| C; B -->|no| D" }
{ "kind": "diff", "patch": "<unified or git diff text>" } # preferred — compact
{ "kind": "diff", "files": [{ "filename": "a.ts", "before": "...", "after": "...", "language": "ts" }] } # fallback
{ "kind": "image", "assetId": "<id from an upload>", "alt": "...", "caption": "..." }
Expand Down Expand Up @@ -137,6 +150,7 @@ CLI equivalents:
```
sideshow publish sketch.html --title "Cache layout" # html surface
sideshow markdown plan.md --title "Migration plan" # standalone markdown surface
sideshow mermaid flow.mmd --title "Request flow" # standalone mermaid surface
sideshow diff change.patch --title "Add retry" --layout split # standalone diff surface
sideshow publish sketch.html --diff change.patch --title "Retry flow" # combined [html, diff]
```
Expand Down
Loading
Loading