planpage — interactive HTML plans for skills
Like mcp-ui, but for terminal skills. This skill is a thin consumer of the open-source planpage package (on npm — npm i planpage, or zero-install with npx planpage). The components, the render engine, the auto-captured gallery, and the post-back server all live there as the single source of truth. Don't hand-roll HTML here; author with the package.
What the package gives you
- Components —
SectionCard, PickBlock, DiffBlock, TreePanel, Flow, CodeBlock, CodeExplorer (IDE-style file tree + editor pane, per-file before/after), plus plan-native pieces: Callout, RiskList, Steps, Timeline, OptionCompare, PlanSummary, Accordion, AnnotatedCode. Browse them live: npx planpage library --open.
- Templates —
plan-brief (a whole agent plan on one page), before-after, code-style-plan (its canonical takes either a single code snippet or a multi-file files list → the CodeExplorer), library.
render() / renderHighlighted() — render(<Template {...data} />) → a self-contained HTML string (Tailwind + Mermaid from CDN, light/dark theme). await renderHighlighted(…) also bakes in real VSCode syntax colour (Shiki, TS/JS-first) at render time — no CDN, no client JS to read colour. npx planpage render … highlights for you.
serve — a never-hang post-back server (OS-assigned ephemeral port 49152+, idle timeout, file:// + clipboard fallback) that collects one Decision.
Quick start (how a skill uses it)
- Assemble the page from the package's components (or a whole template):
import { render, PlanBrief } from "planpage";
const html = render(<PlanBrief title="…" steps={/* … */} risks={/* … */} />);
or straight from the CLI: npx planpage render plan-brief --data plan.json.
- Write it to the OS temp dir (
$TMPDIR/<name>-<timestamp>.html) — nothing lands in the repo.
- Serve + collect the decision on a safe ephemeral port:
npx planpage serve <html-path> <decision-out.json> --timeout 600
It prints the local URL, opens the browser, and blocks until the user clicks Approve / Adjust (or the idle timeout fires), then writes the decision JSON and exits 0.
- Read
<decision-out.json> and act on it.
Fallback — never hang. Headless / no Node / port blocked → open the file directly (file://); the page's Copy decision button puts the same JSON (base64) on the clipboard to paste back. A non-TTY caller always has a decision path.
The decision contract
The page posts one JSON object; the server writes it verbatim to the out-path:
{ "approved": true, "flips": ["rule.function-form"], "revisit": ["rule.error-shape"], "notes": "keep classes only in the DB layer" }
approved — Approve (true) vs Adjust (false).
flips — data-ids of PickBlocks the user flipped (chosen ↔ rejected). Re-open the grill on these.
revisit — data-ids marked "revisit" without a firm decision.
notes — free text from the notes box.
Every interactive component carries a stable data-id; that id is what appears in flips / revisit. Name ids meaningfully (rule.error-shape, move.orders-dir) so a bare id tells you what to re-open.
Rules
- The package owns the HTML; the skill owns the content. Reference
planpage and plug in content — don't re-derive the shell. When a report needs a new widget, add it to the package (with a gallery entry) so every skill inherits it.
- Self-contained. No repo assets, no build step at render time; Tailwind + Mermaid from CDN, and syntax colour baked into the HTML by Shiki (no CDN, no client JS).
- Never block a script.
serve carries an idle timeout; the fallback is copy-paste. A headless / non-TTY caller must still reach a decision.
- Installing the package adds no skills.
npx planpage init scaffolds one ready render-plan skill (wired to npx planpage) into .claude/skills/ — ship that to end users; the package itself is just the engine.
1---2name: planpage3description: Render a skill's plan, review gate, or report as a beautiful, self-contained, INTERACTIVE HTML page — via the open-source planpage package (Preact components → static HTML + a local post-back server so the user can approve / adjust / flip decisions in the browser and the choice comes straight back to the agent). Use whenever a skill needs an approval gate, a decision review, or a shareable before/after report — author with the kit's components instead of hand-rolling HTML each time.4---56# planpage — interactive HTML plans for skills78Like `mcp-ui`, but for terminal skills. This skill is a **thin consumer of the open-source [`planpage`](https://github.com/YosefHayim/planpage) package** (on npm — `npm i planpage`, or zero-install with `npx planpage`). The components, the render engine, the auto-captured gallery, and the post-back server all live there as the single source of truth. Don't hand-roll HTML here; author with the package.910## What the package gives you1112- **Components** — `SectionCard`, `PickBlock`, `DiffBlock`, `TreePanel`, `Flow`, `CodeBlock`, `CodeExplorer` (IDE-style file tree + editor pane, per-file before/after), plus plan-native pieces: `Callout`, `RiskList`, `Steps`, `Timeline`, `OptionCompare`, `PlanSummary`, `Accordion`, `AnnotatedCode`. Browse them live: `npx planpage library --open`.13- **Templates** — `plan-brief` (a whole agent plan on one page), `before-after`, `code-style-plan` (its `canonical` takes either a single `code` snippet or a multi-file `files` list → the `CodeExplorer`), `library`.14- **`render()` / `renderHighlighted()`** — `render(<Template {...data} />)` → a self-contained HTML string (Tailwind + Mermaid from CDN, light/dark theme). `await renderHighlighted(…)` also bakes in **real VSCode syntax colour** (Shiki, TS/JS-first) at render time — no CDN, no client JS to read colour. `npx planpage render …` highlights for you.15- **`serve`** — a never-hang post-back server (OS-assigned ephemeral port 49152+, idle timeout, `file://` + clipboard fallback) that collects one `Decision`.1617## Quick start (how a skill uses it)18191. **Assemble** the page from the package's components (or a whole template):20 ```tsx21 import { render, PlanBrief } from "planpage";22 const html = render(<PlanBrief title="…" steps={/* … */} risks={/* … */} />);23 ```24 or straight from the CLI: `npx planpage render plan-brief --data plan.json`.252. **Write** it to the OS temp dir (`$TMPDIR/<name>-<timestamp>.html`) — nothing lands in the repo.263. **Serve + collect** the decision on a safe ephemeral port:27 ```bash28 npx planpage serve <html-path> <decision-out.json> --timeout 60029 ```30 It prints the local URL, opens the browser, and blocks until the user clicks **Approve** / **Adjust** (or the idle timeout fires), then writes the decision JSON and exits `0`.314. **Read** `<decision-out.json>` and act on it.3233**Fallback — never hang.** Headless / no Node / port blocked → `open` the file directly (`file://`); the page's **Copy decision** button puts the same JSON (base64) on the clipboard to paste back. A non-TTY caller always has a decision path.3435## The decision contract3637The page posts one JSON object; the server writes it verbatim to the out-path:3839```json40{ "approved": true, "flips": ["rule.function-form"], "revisit": ["rule.error-shape"], "notes": "keep classes only in the DB layer" }41```4243- **`approved`** — Approve (`true`) vs Adjust (`false`).44- **`flips`** — `data-id`s of `PickBlock`s the user flipped (chosen ↔ rejected). Re-open the grill on these.45- **`revisit`** — `data-id`s marked "revisit" without a firm decision.46- **`notes`** — free text from the notes box.4748Every interactive component carries a stable `data-id`; that id is what appears in `flips` / `revisit`. Name ids meaningfully (`rule.error-shape`, `move.orders-dir`) so a bare id tells you what to re-open.4950## Rules5152- **The package owns the HTML; the skill owns the content.** Reference `planpage` and plug in content — don't re-derive the shell. When a report needs a new widget, add it to the package (with a gallery entry) so every skill inherits it.53- **Self-contained.** No repo assets, no build step at render time; Tailwind + Mermaid from CDN, and syntax colour baked into the HTML by Shiki (no CDN, no client JS).54- **Never block a script.** `serve` carries an idle timeout; the fallback is copy-paste. A headless / non-TTY caller must still reach a decision.55- **Installing the package adds no skills.** `npx planpage init` scaffolds one ready `render-plan` skill (wired to `npx planpage`) into `.claude/skills/` — ship that to end users; the package itself is just the engine.