# plan-design-system

This is the visual + structural toolkit for **HTML plans** rendered by the `plan-html` pi extension. The extension parses a `plan-json` block emitted by the model and renders it through this design system.

> If you're a model authoring a plan: read **"Plan schema"** and **"Available components"**. Then emit your plan as a single ```` ```plan-json ```` fenced block.

---

## Plan schema

Emit one fenced block — and nothing else after it — in this exact form:

````
```plan-json
{
  "title": "Short, specific name for the plan",
  "context": "1–3 sentences. Why this work, what it changes, what success looks like.",
  "header_chips": [
    { "label": "complexity: medium", "tone": "info" },
    { "label": "blast radius: contained", "tone": "ok" }
  ],
  "steps": [
    {
      "key": "bootstrap",
      "title": "Bootstrap the extension",
      "body": "Markdown description of the step. May include lists, tables, links, inline `code`, and **emphasis**.",
      "files": ["src/extension.ts", "package.json"],
      "callouts": [
        { "tone": "warn", "title": "Heads up", "body": "Don't forget to check existing patterns first." }
      ],
      "code": [
        { "lang": "ts", "label": "src/extension.ts", "source": "export default function () { /* ... */ }" }
      ],
      "mockup": "┌───────────────────────────────┐\n│   Header                      │\n│   ─────────                   │\n│   [ button ]                  │\n└───────────────────────────────┘",
      "diagram": "flowchart LR\n  A[User] --> B[Extension] --> C[Browser]",
      "verification": "How to confirm this step works."
    }
  ],
  "risks": [
    "Concrete risk 1",
    "Concrete risk 2"
  ],
  "open_questions": [
    "Question we still need a human to answer"
  ]
}
```
````

Rules:

- **One fenced block only.** Prose above is fine; nothing after the closing fence.
- **Strict JSON.** No comments, no trailing commas, no unquoted keys.
- Every field outside `title`, `context`, and `steps[].title` is **optional**. Omit fields when they don't add value — empty mockup frames or generic risks dilute the plan.
- `steps[].key` should be a short, stable kebab-case slug. Used for comment anchoring across iterations; reusing keys between v1 and v2 lets the user's comments persist on the right section.

---

## Available components

The renderer composes these from your plan-json. Pick the right tool for the job; don't force every component into every plan.

### `header_chips[]` — top-of-page status pills

```json
{ "label": "owner: backend", "tone": "info" }
```

`tone`: `info | ok | warn | bad | accent` (default: neutral).

Use for: complexity, blast radius, owner, dependencies, branch name. **Max 4.**

---

### `steps[].body` — markdown prose

Standard markdown. Headings `###` and below render small uppercase eyebrows. Use lists for sub-tasks. Use tables when comparing options. Keep paragraphs short.

---

### `steps[].files[]` — touched-files chip row

```json
"files": ["src/auth/login.ts", "src/auth/login.test.ts"]
```

Renders as a row of dashed chips. List every file that will be created, edited, or deleted. **Don't** list files only being read.

---

### `steps[].callouts[]` — annotated cards inside a step

```json
{ "tone": "warn", "title": "Migration order", "body": "Run before the schema change." }
```

`tone`: `note | warn | risk | ok`. Title is optional. Use for: gotchas, ordering constraints, non-obvious requirements.

---

### `steps[].code[]` — syntax-highlighted snippets

```json
{ "lang": "ts", "label": "src/login.ts:42", "source": "..." }
```

`lang` must be a Prism-supported language (`ts, tsx, js, jsx, json, css, html, py, rs, go, sh, sql, md, yaml, …`). Use sparingly — code in a plan is for **illustration**, not the full implementation. 5–20 lines per snippet.

---

### `steps[].mockup` — realistic HTML UI mockup

An object that renders into a **sandboxed iframe wrapped in chrome** (window, browser, phone, or none). The model writes real HTML and CSS; a built-in preset stylesheet makes plain semantic markup look polished. Use this whenever you'd otherwise reach for ASCII art.

```json
{
  "frame":    "window",
  "title":    "Recording — Product Sync",
  "viewport": "auto",
  "html":     "<header><h1>Recording</h1><span class=\"spacer\"></span><button data-variant=\"primary\">Stop</button></header><main><section><label>What do you want to remember?</label><input placeholder=\"Ask about pricing roadmap…\"></section></main>",
  "css":      ".muted { color: #777 }"
}
```

**Frames** — pick the one that matches what's being designed:

- `window` *(default)* — macOS-style window with traffic lights and a centered title. Use for desktop app UIs.
- `browser` — traffic lights + a faux URL bar (uses `title` as the URL). Use for web pages.
- `phone` — phone-shaped bezel with a notch and home indicator. Use for mobile UIs.
- `none` — naked rounded card. Use for fragments / partial UIs / chrome-less surfaces.

**Viewport** is normally `"auto"` (phone → mobile width, others → fill the column). Override to `"mobile"` to force a 24rem column on a desktop frame, e.g. to mock a tablet view.

**HTML — lean on the preset.** The iframe ships with a stylesheet that already makes these elements look like a real product:

| Markup | Renders as |
|---|---|
| `<header>`, `<footer>` | padded surface bar |
| `<nav><a>…</a></nav>` | inline links with hover underline |
| `<main><section>…</section></main>` | white card on a surface bg |
| `<h1>`–`<h6>`, `<p>` | tight modern type scale |
| `<button>` | outlined pill |
| `<button data-variant="primary">` | filled accent pill |
| `<button data-variant="ghost">` | text-only button |
| `<input>`, `<textarea>`, `<select>` | bordered field with focus ring |
| `<table>` | zebra rows, padded cells |
| `<blockquote>` | left-bar quote on surface |
| `<code>`, `.kbd` | keycap-style chip |

Utility classes baked into the preset: `.row`, `.col`, `.spacer`, `.muted`, `.subtle`, `.center`, `.right`, `.mono`, `.primary`, `.danger`, `.success`, `.pill`, `.pill.primary|success|warn|danger`, `.card`, `.divider`.

**CSS is optional.** Only add a `css` field when the preset isn't enough (e.g., a custom layout you can't express with `.row` / `.col`). Don't paste Tailwind or other utility frameworks — they won't load.

**Don't include `<script>` tags.** They are stripped at render time. The mockup is a static visual.

**Examples**

`window` (desktop app):
```json
{ "frame": "window", "title": "Recording — Product Sync",
  "html": "<header><strong>● Recording</strong><span class=\"muted\">— Product Sync</span><span class=\"spacer\"></span><button data-variant=\"primary\">Stop &amp; Save</button></header><main><section><label>What do you want to remember?</label><input placeholder=\"Ask about pricing roadmap…\"></section></main>" }
```

`browser` (web page):
```json
{ "frame": "browser", "title": "app.example.com/settings",
  "html": "<nav class=\"row\"><a href=\"#\" aria-current=\"page\">Settings</a><a href=\"#\">Team</a><a href=\"#\">Billing</a><span class=\"spacer\"></span><span class=\"pill primary\">Pro</span></nav><main><section><h2>Notifications</h2><label><input type=\"checkbox\" checked>Daily digest</label><label><input type=\"checkbox\">Realtime alerts</label></section></main>" }
```

`phone` (mobile):
```json
{ "frame": "phone", "title": "Notes",
  "html": "<header><strong>Notes</strong><span class=\"spacer\"></span><button data-variant=\"ghost\">+</button></header><main><section><h3>Meeting prep</h3><p class=\"muted\">Updated 2m ago</p></section><section><h3>Standup notes</h3><p class=\"muted\">Updated yesterday</p></section></main>" }
```

`none` (a small component fragment):
```json
{ "frame": "none",
  "html": "<div class=\"card row\"><div><strong>Auto-record meetings</strong><div class=\"muted\">Capture all Zoom meetings starting tomorrow</div></div><span class=\"spacer\"></span><button data-variant=\"primary\">Turn on</button></div>" }
```

**Legacy ASCII string mockups still render** for backwards compatibility (multi-line string in a monospace paper card) — use them only when you're actually designing a text-mode UI like a CLI.

---

### `steps[].diagram` — Mermaid diagram

Any Mermaid syntax: `flowchart`, `sequenceDiagram`, `classDiagram`, `stateDiagram`, `erDiagram`, `gantt`.

Use when the change is **structural** — data flow, sequence of calls, module relationships. Don't reach for diagrams when prose is clearer.

---

### `steps[].verification`

Plain text. 1–2 sentences on how to confirm the step works. The user reads these to know how to validate after `/approve`.

---

### Top-level `risks[]` and `open_questions[]`

- **`risks[]`** — strings. Things that could go wrong even if the plan executes correctly. Concrete > generic; "the migration locks the users table for 4–7 seconds" beats "could be slow."
- **`open_questions[]`** — strings. Things the model couldn't determine from the code alone and needs a human to clarify before execution.

---

## How comments work (so you can plan for them)

- Each `step` gets an inline comment thread in the rendered page, anchored by `step.key`.
- When the user `/iterate`s, the extension feeds their comments back grouped by step title.
- **Plan with that in mind**: prefer many small steps over one big one, so feedback can target specific decisions.

---

## Theming

Tokens live in `tokens.css`. Override at `:root` to rebrand:

```css
:root {
  --pds-accent: #2e7d32;          /* swap the accent */
  --pds-font-serif: "Lora", serif; /* swap headings */
}
```

A dark theme is provided automatically via `prefers-color-scheme` (and can be forced with `data-pds-theme="dark"` on `<html>`).

---

## File layout

```
plan-design-system/
├── tokens.css        — design tokens (colors, type, spacing)
├── components.css    — every component style
├── template.html     — document shell with {{SLOTS}}
├── runtime.js        — comments, export, scroll-spy
├── vendor/
│   ├── mermaid.js    — diagrams
│   ├── prism.js      — code highlighting
│   └── prism.css     — code theme
└── README.md         — this file (also surfaced to the model)
```
