-
Write the plan to a .mdx file, starting with a single # Title heading (it becomes the plan
title; no frontmatter). Then use the components below; you never write import statements, they
are always in scope.
-
Validate before showing the user: vplan check <file>.mdx. Fix every reported file:line:col
issue (it names valid enum values and flags unknown components). check also runs a quality
lint that flags weak renders (the enforced Gotchas below); its warnings fail check, so fix
them too.
-
Present the plan with vplan <file>.mdx. An interactive review is the default way to deliver a
plan, not a special mode reserved for sign-off. It opens the plan with a feedback layer where the
user comments on whole sections or on selected text, answers any <Questions> directly (each
question becomes an inline answer field; a question with nested option bullets becomes clickable
choices, and the picked option's text (or the typed "Other" text) is the answer, printed back as
Answer to "<question>":), then clicks
Approve / Deny / Iterate. It blocks until they submit, prints the decision, comments, and
answers to stdout, and exits: approve 0, deny 1, iterate 2, timeout 3 (--timeout, default 15m;
closing the tab counts as deny). It is a long-running foreground server, so run it in the
background. (The explicit --review flag still works but is redundant now that review is the
default.) A comment may carry a severity tag: treat a [must-fix] comment as blocking (it must
be addressed before the plan can be approved) and a [suggestion] or untagged comment as
non-blocking input. Then act on the printed feedback:
- Approve -> proceed with the plan as written.
- Iterate -> revise the plan addressing each comment, then simply re-run
vplan <file>.mdx;
the review tab updates the same plan in place and the round number increments automatically
(pass -i N only to override it). Repeat until Approve or Deny. Edit
the same .mdx file in place and re-render: vplan snapshots each plan it presents (keyed by
the file path), so the next render automatically marks what changed since the last view with a
subtle git-gutter accent and a "N changed" summary, letting the user re-review only the delta.
Pass --diff <baseline.mdx> to diff against an explicit file instead of the snapshot, or
--no-diff to suppress diffing (e.g. a clean first look).
- Deny -> stop and reconsider; do not proceed.
Review is the right default for every non-trivial plan. Targeted comments and in-place
<Questions> answers drive sharper revisions than back-and-forth chat, and the loop ends in an
explicit Approve so you know it is settled. Reach for a static render (step 4) only when the user
just wants to look, not shape or decide.
-
A static page, a live-reloading preview, or a PDF/JPG export are the non-review outputs, for
when the user only wants to look or wants a shareable file rather than review one. When you need
one, load references/static-exports.md; it holds the commands and flags for these (kept out
of here so review stays the default path). Authoring the plan (everything below) is identical
whichever output you choose.
-
<Phase title="..." status="planned|active|done">: one step in a numbered vertical
timeline; wraps markdown (ordered lists, prose, nested components). The steps auto-number in
order. One per major step of the plan.
-
```mermaid fenced block, for diagrams: architecture (flowchart), sequenceDiagram,
dependency graphs, stateDiagram-v2, classDiagram, erDiagram, and xychart-beta. Reach for
this first for anything structural. (gantt and pie are not supported; use <Chart> for
quantitative data. check now validates each diagram, so an unsupported type fails check with a
file:line:col instead of rendering an error box.)
-
```math fenced block, a display formula written in LaTeX, typeset as math (complexity
bounds, probabilities, linear algebra). Example: ```math then T(n) = O(n \log n).
-
<Callout type="note|tip|risk|decision|warn">: highlight a risk, decision, tip, or note; wraps
markdown. (note is blue, tip is green, decision is purple, risk is red, warn is yellow.)
-
<FileTree>: file-change map. One bullet per file, - <change> <path>, where change is
add|modify|delete|move. A move needs both ends, - move <from> -> <to> (the file renders at
its destination with the origin shown). A path ending in / marks a whole directory (e.g.
- delete src/legacy/). A colored file-type icon is added automatically from the path's
extension. Append -- <note> to any line for a short inline comment on that change (what it does
or why); keep it to a phrase, since it shares the row with the file name.
<FileTree>
- add src/gateway/rate-limiter.ts -- sliding-window check against Redis
- modify src/gateway/middleware.ts -- mount the limiter behind the flag
- delete src/gateway/legacy/
</FileTree>
-
<Chart type="bar|line|area|scatter|radar|gauge|funnel|treemap|pie" title="...">:
estimates/metrics. Single series: one bullet per point, - <label>: <value> (a number).
Multi-series (bar/line/area/radar): a table whose header is category | series1 | series2
(cells after the first name the series and become the legend; the first column is the category
axis). Specifics: scatter is a table with exactly two value columns read as x and y
(| point | x | y |); pie/gauge/funnel/treemap are always single-series, list form only (a
table is rejected), with gauge on a 0-100 scale and funnel descending. Add stacked to a
multi-series bar/area (<Chart type="bar" stacked>) to stack rather than group.
<Chart type="bar" title="Effort (days)">
- Limiter: 2
- Dashboards: 1
</Chart>
<Chart type="line" title="Latency by stage (ms)">
| Stage | p50 | p95 |
|-------|-----|-----|
| Auth | 12 | 30 |
| DB | 40 | 120 |
</Chart>
-
<Compare>: weigh approaches side by side as pros/cons cards. Each option is a ## Name
heading (append (pick) to mark the recommended one) followed by as many - pro: / - con:
bullets as you need.
<Compare>
## Redis sliding window (pick)
- pro: accurate
- pro: shared across nodes
- con: network hop
## In-memory token bucket
- pro: fast
- con: per-node only
</Compare>
-
<Matrix>: a comparison grid (options across the columns, criteria down the rows) for scoring
several choices against several dimensions. Write a markdown table; the first column is the row
labels, and you append (pick) to one column header to highlight it. Use <Compare> for
pros/cons, <Matrix> for a scorecard.
<Matrix>
| Dimension | Postgres (pick) | ClickHouse | DynamoDB |
|-----------|-----------------|------------|----------|
| Writes | medium | high | high |
| Querying | high | medium | low |
</Matrix>
-
<Questions>: open questions you want the reader to resolve before building, one per bullet.
Use this instead of burying uncertainties in prose. The title defaults to "Open questions";
override with title="...". In a --review session each question is directly answerable, so
prefer a <Questions> block over prose when you want the reviewer to answer specific questions.
Nest bullets under a question to offer multiple-choice options: in a review they become
clickable choices (plus an "Other" free-text field), and the chosen option's text comes back as
the answer. Offer options whenever you can enumerate the likely answers; they get you a crisp,
actionable answer instead of prose. A question with no nested bullets stays free-text.
<Questions>
- Should the limiter fail open or fail closed if Redis is unreachable?
- Fail open, availability first
- Fail closed, safety first
- Is a 15-minute access-token TTL acceptable?
</Questions>
-
<Checklist title="Done when">: acceptance criteria / definition of done, as a markdown task
list: - [x] for done, - [ ] for todo.
<Checklist title="Done when">
- [x] Returns 429 over the limit
- [ ] Dashboards live
</Checklist>
-
<Stat>: headline plan metrics as a grid of cards (files changed, estimated uptime, rollout).
One card per bullet, - <label>: <value> (<intent>) -- <caption>, where intent is one of
note|good|warn|risk and both (intent) and -- caption are optional. The value is free text
(5 min, 99.9%), not a number. Use this for static facts, not time series (use <Chart> for
those). Only add a <Stat> when the plan genuinely has standout numbers worth surfacing; most
plans have none, and an invented or filler metric is worse than omitting the component entirely.
<Stat>
- Files changed: 12
- Est. uptime: 99.9% (good)
- RPO: 5 min (risk) -- worst-case data loss
</Stat>
-
Fenced code blocks are syntax-highlighted (Expressive Code): write ```ts (or js, json, bash,
python, go, rust, sql, yaml, etc.) to show a key snippet. Add a file name with
```ts title="src/path/file.ts" to render a filename header on the block.
-
Mark lines and text inside a code block with Expressive Code props in the fence meta string
(no component needed). Three marker types: mark (neutral, the default), ins (green,
inserted), del (red, removed). Each takes line numbers, ranges, quoted strings, or a
/regex/. Use this to call attention to the lines a plan changes.
- Lines/ranges (neutral):
```ts {2}, ```ts {2-4}, ```ts {1, 3, 5-6}
- Typed lines:
```ts ins={3-4} del={2} mark={6} (combine freely in one block)
- Inline text:
```ts "TokenBucket", a rename as ```ts del="oldName" ins="newName"
- Regex (and capture group):
```ts /\bTODO\b/, ```ts ins=/const (\w+) =/ (marks the group)
The whole point of a visual plan is to replace a wall of prose with something the reader grasps by
scanning. Default to a component over a sentence: if a fact has structure, show it; do not describe
it in paragraphs. Prose is the connective tissue between visuals, never the substance.
Several of these (a wall-of-prose phase, a wide LR mermaid diagram, an over-long Matrix cell, a
commented FileTree move row, a wildly-scaled Chart) are now enforced by the check quality lint
and will fail it, so they are hard rules, not just style advice.