CLAUDE.md · diff

git:20260915.64e23d6 to git:20260917.17d01cc

50 added, 12 removed. Audit A to A.

# flowpane
A Claude Code plugin that replaces the default Workflow progress list with a
live drawing of the run: a phase-by-phase graph, a stack, or a timeline in the
pane beside the transcript, with each agent's state, timing, token spend, tool
calls and prompt.
It draws into a terminal grid. Everything it knows comes from files a running
workflow writes, and everything it says has to fit in cells.
## Commands
```bash
- claude plugin test . # the test suite — 99 tests across 11 files
+ claude plugin test . # the test suite — 192 tests across 15 files
bunx tsc --noEmit # typecheck (hooks/ and types/ only; see tsconfig.json)
```
Use `claude plugin test .`, not `bun test`. The tests import
`claude-code/testing`, which the plugin test runner supplies and a bare `bun
test` does not; bare `bun test` fails to resolve it.
The developer tools under `dev/` all take a run and draw it without a session:
```bash
bun dev/preview.ts [--animate|--journal <dir>|--settings|--menu theme|--about|--plain]
bun dev/shot.ts [--journal <dir>] [--port 8731] # the pane in a browser, live, at any size
bun dev/stress.ts # wide fan-outs and a narrow body
bun dev/lines.ts # every line of every run at every size
bun dev/audit.ts # every run, layout and size: nothing throws, every cell legible
bun dev/checkpic.ts <dir> # the bands cover the canvas, every node has a Button
bun dev/contrast.ts # every theme's roles against its ground, its edges against its states
bun dev/edges.ts <dir> # the graph derived for a run, and how it was derived
bun dev/recover.ts <sessionId> [home] # what a session's runs rebuild to
bun dev/dryrun.ts <workflow> # a workflow's control flow, stubbed, no agents spent
bun dev/checkmeta.ts # the version the pane shows is the one the manifest ships
+ sh dev/reload.sh # install this tree as flowpane-dev, beside the released build
```
- `dev/audit.workflow.js` is the demo run: a workflow shaped to exercise every
- case the pane draws. It lives under `dev/` because it is a development tool, not
- part of the installed plugin. `bun dev/dryrun.ts` defaults to it.
+ `dev/reload.sh` installs this tree *beside* the released plugin rather than in
+ place of it: `~/claude-dev-marketplaces/flowpane-dev` holds a copy of the repo
+ with the three names the engine keys off moved aside — the manifest's `name`,
+ `COMMAND` and `PANE_ID`. Renamed, both can be enabled at once: `/flowpane` stays
+ the released build and `/flowpane-dev` draws this tree. Unrenamed they collide on
+ every one of them — one command, one pane id, one `tool.call` hook opening one
+ pane. They are patched in the copy, never in the repo, so what ships is unchanged.
- `dev/load.ts` is not a command — it is the run-building library the others share.
- `dev/drive.py` gives Claude Code a pty and captures a real session (`python3`,
- not `python`).
+ The title the pane draws is left alone. A build is told apart by the version it
+ already prints, and a product name bent out of shape to serve a dev install is a
+ dev install leaking into the picture.
- Real runs to draw live under
- `~/.claude/projects/<project-slug>/<sessionId>/subagents/workflows/wf_*`.
+ A *copy*, not a symlink. A symlinked plugin source installs files that look
+ right — the engine's cache comes out byte-identical to the tree — and then never
+ loads, with no error printed anywhere: the command comes back `Unknown command`
+ while `claude plugin details` still reports the plugin as installed. So the
+ reload mirrors the tree in with `rsync`, then drops the engine's cached copy,
+ since an install over a version already installed is a no-op. Restart the session
+ afterwards: hooks are read once, when it starts.
- Before calling any of this done: `bunx tsc --noEmit`, `claude plugin test .`,
- `bun dev/lines.ts`, `bun dev/audit.ts`.
+ Both builds watch the same journals and both open a pane on a Workflow launch,
+ so with the two enabled a live run draws twice. `/flowpane` toggles the released
+ one shut.
## Layout of the repo
| Path | What it holds |
| --- | --- |
| `hooks/register.ts` | The plugin itself: every hook, the journal tail, the run state |
| `hooks/journal.ts` | Reading a run off disk — journal, run file, agent transcripts |
| `hooks/shape.ts` | The run's shape: phases, passes, what fed what |
- | `hooks/tree.ts` | Rows and segments for the timeline |
+ | `hooks/tree.ts` | The canvas as elements: coloured runs per row, and a Button over every hotspot |
| `hooks/layout.ts` | Where every band, card and column goes, at a given size |
| `hooks/paint.ts` | Everything drawn: cards, wires, header, dialogs, settings |
| `hooks/canvas.ts` | The grid: cells, colour mixing, line drawing, joints |
| `hooks/theme.ts` | Twelve themes, each ten colours, and the thirteen roles derived |
| `hooks/press.ts` | Keys and clicks |
| `hooks/about.ts` | What the pane says about itself: the dialog and `/flowpane about` |
| `tests/` | What the pane draws, read back off the canvas |
| `dev/` | The tools above |
| `docs/` | The design record: every decision, and what it replaced |
| `README.md` | What the plugin is, installing it, and the way in to `docs/` |
## Constraints the platform imposes
These are not preferences. Breaking one produces a pane that looks right in a
test and wrong in a terminal.
- **A `Button` carries no colour and no background.** `ButtonProps` has `key`,
`label`, `hotkey`, `action`, `plain` and `hover` — no colour among them. A
state mark, a rule or a fill drawn inside a Button's cells comes out in the
label's own plain text. So every coloured cell stays outside every hotspot: the mark sits beside the
name with a cell of air between them, and a card is framed in code points
rather than filled.
- **Every dialog is modal.** There is no layering below one. Anything that has
to be readable while a dialog is open is drawn into the pane, not under it.
- **A `Raster` is blitted, an element tree is re-rendered.** `ui.blit` repaints
a mounted Raster at 120 ms a frame; a redraw through `ui.render` costs a
render and runs at 320. Animation belongs in the Raster.
- **The pane's size is given, not asked for.** Every drawing decision takes the
width it is handed. Nothing may assume a minimum beyond what `layout.ts`
already guards.
## Rules the drawing keeps
- **Colour says one of two things: what state a node is in, or that a line is a
wire.** Structure is grey. The one border allowed to carry a state is a
card's own frame, which encloses exactly one agent — see `frameOf`.
- **No line crosses a word.** `canvas.line()` refuses to overwrite a port, and
`dev/lines.ts` checks the whole corpus: every line lands on a blank cell,
every arrowhead has a stem, every arm reaches something.
- **A measurement keeps its unit where the unit fits.** Token counts run down a
ladder — `∑ 15.1k tkns`, `∑ 15.1k`, `15.1k` — and a lane picks one spelling
for all of its cards, so two cards are never compared in two units.
- **The header gives way in a fixed order.** Each group has a `drop` rank and
its own `short` ladder; the highest rank gives way first.
- **A finished run stops moving.** A run that is not `running` is painted at
`tick = -1`, which freezes every animation: no spinner, no sliding label, no
travelling light.
+ - **The arc is for two wires, and nothing else.** `◠` says *there is a second
+ line in this cell, go and find it*. A phase's border and a band's rule are
+ boundaries rather than lines anything travels along, so a wire meeting one
+ breaks the boundary and runs through whole. A card's exit point is marked
+ whatever else wants the cell.
+ - **A phase is entered where it started and left where it finished.** Its
+ agents group into waves by their clocks — the ones that overlapped, in the
+ order the waves went — and the barrier above it lands on the first wave, the
+ phase below is fed from the last. One wave is a fan and keeps every wire;
+ waves of one are a chain. Feeding every agent both ways draws two gutters of
+ wire that say the phase started all of them at once; see `wavesOf`.
+ - **The dashed stroke means *not this run's own work*.** The phases it skipped,
+ the strip naming the ones still ahead, and a nested run's rule and gutter —
+ three things, one register, in every layout.
+ - **Time is drawn to one scale, and everything on it reads that scale from one
+ place.** The timeline's axis has a width of its own — as many cells as the
+ shortest bar in the run needs, up to four panes' — and the pane is a window on
+ it. Bars, ticks, grid columns and the now-marker all take their column from the
+ single millisecond-to-cell function, which subtracts the sideways scroll, and
+ all clip to the axis columns rather than to the pane.
+ - **A section is never narrower than a name, and a node never shorter than its
+ frame.** The pane divides itself only among the phases — and, down the pane,
+ the agents — it can give a column wide enough to name. Past that the columns
+ take the width they need, the drawing runs past the pane's edge and the body
+ scrolls to the rest. The height keeps the same bargain: a column with more
+ nodes than the pane can stand as cards keeps the cards and scrolls, rather
+ than flattening every node in the drawing to a row. See `docs/header.md`.
## Conventions
- **Comments say why, not what.** Most of what is hard here is a decision that
replaced something worse, and the comment is where the replaced thing is
recorded. `paint.ts` is long because of this and that is the intent.
- **Prose written to a file is normal English** — comments, `docs/`, commit
messages. No abbreviations invented for the file, no notes to the reader
about the session that produced it.
- **`docs/` is the design record, and it is kept current.** A change that
alters what a reader sees changes the page under `docs/` that covers it, in
the same pass — `docs/README.md` says which page that is. The root `README.md`
is the introduction: what the plugin is, how to install it, and links in.
- **Tests read the canvas.** They paint a run and assert on the cells, so a
test says what a person would see. Keep the fixture minimal and name the test
as a sentence about the drawing.