flowfig 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <h1><img src="docs/logo.svg" alt="flowfig" height="80"/></h1>
4
4
 
5
- ### Claude Code, Cursor, Copilot, Codex, Gemini CLI, Windsurf and Kiro draw the diagram from your code. flowfig checks it before it goes in your README.
5
+ ### Your coding agent draws the diagram from your code. flowfig checks it, and CI fails when the linked code is gone.
6
6
 
7
7
  <p>
8
8
  <a href="https://www.npmjs.com/package/flowfig"><img src="https://img.shields.io/npm/v/flowfig?style=for-the-badge&logo=npm&logoColor=white&label=npm" alt="npm version"/></a>
@@ -16,6 +16,58 @@
16
16
 
17
17
  </div>
18
18
 
19
+ ## The problem
20
+
21
+ A diagram in a repo drifts from the code. Someone renames a function, and the diagram still shows the old name. No
22
+ check fails, so no one sees the drift.
23
+
24
+ flowfig links each part of a diagram to the code that it draws. `flowfig verify` fails when a linked file or symbol is
25
+ gone. Run `verify` in CI, and a stale diagram fails the pull request.
26
+
27
+ ## Fail CI when the diagram drifts
28
+
29
+ A box, an edge or a message can name the code that it draws:
30
+
31
+ ```json
32
+ { "id": "verify", "label": "verifyPassword", "source": "src/auth/login.ts#verifyPassword" }
33
+ ```
34
+
35
+ Rename `verifyPassword` to `checkPassword`. Then `verify` fails with exit code 1:
36
+
37
+ ```console
38
+ $ npx flowfig verify login.svg
39
+ error missing-symbol login.svg: box "verify" -> src/auth/login.ts#verifyPassword: symbol not found
40
+ 1 error, 0 warnings
41
+ ```
42
+
43
+ `verify` checks that each linked file exists and that the symbol name still occurs in it. A link to a Markdown file can
44
+ name a heading, so a process diagram can follow its SOP document.
45
+
46
+ The GitHub Action runs `verify` on every figure in a pull request. For each SVG that the PR changes, it comments the old
47
+ image, the new image and the spec changes. It also names each figure whose linked code the PR changes.
48
+
49
+ ```yaml
50
+ # .github/workflows/figures.yml
51
+ name: figures
52
+ on: pull_request
53
+ jobs:
54
+ figures:
55
+ runs-on: ubuntu-latest
56
+ permissions:
57
+ contents: read
58
+ pull-requests: write
59
+ steps:
60
+ - uses: actions/checkout@v4
61
+ with:
62
+ fetch-depth: 0 # the Action reads the old figure from the base commit
63
+ - uses: actions/setup-node@v4
64
+ with:
65
+ node-version: 22
66
+ - uses: iamalvisng/flowfig@v0.7.0
67
+ with:
68
+ figures: 'docs/**/*.svg' # default **/*.svg
69
+ ```
70
+
19
71
  ## Use it
20
72
 
21
73
  Set up your repo:
@@ -41,49 +93,79 @@ draw the refund process with a lane for each team
41
93
  The agent writes an SVG and ends its reply with `npx flowfig open <path>`. Run that command to see the animation in your
42
94
  browser.
43
95
 
96
+ `init` sets up 7 agents: Claude Code, Cursor, GitHub Copilot, Codex and other `AGENTS.md` agents, Gemini CLI, Windsurf
97
+ and Kiro.
98
+
44
99
  ## One command
45
100
 
46
101
  ```bash
47
102
  npx flowfig draw "how does login work"
48
103
  ```
49
104
 
50
- `draw` runs Claude Code on the repo. It gives Claude Code the flowfig guide, `npx flowfig` and the tools Read, Glob and Grep.
51
- The permission mode is `acceptEdits`, so Claude Code can write files. `draw` disallows the tools Edit, MultiEdit and
52
- NotebookEdit. Claude Code writes the spec `how-does-login-work.json`, then renders `how-does-login-work.svg`. Then `draw`
53
- checks the figure itself with `check --strict` and `verify`, and removes the spec file. If the agent writes no new figure,
54
- `draw` exits 1 and shows the tool calls that the permission rules denied. `draw` prints the result, the reply of the agent
55
- and the cost of the run. `draw` needs Claude Code on the machine and a login. `draw` does not run on Windows yet.
56
- `--out` sets the path (it must end in `.svg`), `--model` sets the model, `--max-turns` sets the turn cap (default 40) and
57
- `--json` prints `{ out, reply, cost, session, findings }` for scripts. `--open` opens the figure in the default browser after the check.
105
+ `draw` runs Claude Code on the repo with the flowfig guide. It gives Claude Code the tools Read, Glob and Grep, and
106
+ `npx flowfig`. `draw` disallows the tools Edit, MultiEdit and NotebookEdit. Claude Code writes the figure. Then `draw`
107
+ checks the figure with `check --strict` and `verify`. `draw` prints the result, the reply of the agent and the cost of
108
+ the run.
58
109
 
59
- ## The problem
110
+ `draw` needs Claude Code on the machine and a login. `draw` does not run on Windows yet. `--out` sets the path,
111
+ `--model` sets the model, `--max-turns` sets the turn cap (default 40), `--json` prints the result for scripts, and
112
+ `--open` opens the figure.
113
+
114
+ ## How it works
115
+
116
+ 1. **The agent writes JSON, not pictures.** The agent reads the code and writes a spec of the parts, the calls and their
117
+ order. flowfig does the layout.
118
+ 2. **flowfig checks the spec.** `flowfig check` has 22 rules. It finds ids that point nowhere, text wider than its box,
119
+ edges through boxes, overlapping labels and low contrast. A render runs the same check and writes nothing on an error.
120
+ The agent reads the faults and fixes the spec.
121
+ 3. **The output is one animated SVG with no script.** The SVG plays in a GitHub README, PR or issue. It follows the light
122
+ or dark mode of the reader. The SVG carries its spec, so `npx flowfig --spec figure.svg` prints it back.
123
+
124
+ ## Forms
125
+
126
+ The map (the default) shows the parts and the messages between them:
127
+
128
+ ![A request that misses the cache, then hits the cache](docs/cached-request.svg)
60
129
 
61
- - A diagram in a repo drifts from the code. No one sees the drift.
62
- - An agent that you ask for a diagram invents parts that the code does not have.
63
- - A static picture cannot show the order of the calls or their payloads.
64
-
65
- ## What flowfig does
66
-
67
- - **One command sets up 7 agents.** `npx flowfig init` writes instructions for Claude Code, Cursor, GitHub Copilot, Codex and
68
- others through `AGENTS.md`, Gemini CLI, Windsurf and Kiro.
69
- - **The agent writes JSON, not pictures.** The agent reads the code and writes a JSON spec of the parts, the calls and their
70
- order. flowfig does the layout.
71
- - **`flowfig check` finds the faults a machine can read.** It finds ids that point nowhere, text wider than its box, edges
72
- through boxes, overlapping labels, hidden edges, text too small at the README width, and low contrast. The agent reads the
73
- faults and fixes the spec.
74
- - **The output is one animated SVG with no script.** The SVG plays in a GitHub README, PR or issue. It follows the light or dark
75
- mode of the reader.
76
- - **Every SVG carries its own spec.** `npx flowfig --spec figure.svg` prints the spec. Any agent can read a diagram back, change
77
- it, and render it again.
78
- - **Three forms and a React player.** The forms are the map, the map with a lifeline rail for sequences, and the rail alone. The
79
- React player adds tabs, pause and hover. The package has zero runtime dependencies. Only the React player needs React.
130
+ The map and the rail (`rail: true`) adds one row for each message, with its payload:
131
+
132
+ ![An order checkout: the map of the parts, and a rail of the messages under it](docs/checkout.svg)
133
+
134
+ The rail only (`rail: "only"`) shows the order of the messages without the map:
135
+
136
+ ![The checkout messages as a rail, without the map](docs/checkout-rail-only.svg)
137
+
138
+ Swimlanes (`lanes: true`) give each role a lane and put the steps left to right in time order:
139
+
140
+ ![The refund process across Customer, Support and Finance](docs/refund-process.svg)
141
+
142
+ A long swimlane process wraps into blocks at the page width. An edge between two blocks becomes two labeled stubs:
143
+
144
+ ![A returns process in blocks of lanes, with labeled stubs between the blocks](docs/returns-process.svg)
145
+
146
+ The timeline (`timeline: true`) draws a roadmap with one track per team, dated bars, milestones and a today line:
147
+
148
+ ![A Q4 roadmap with three tracks, milestones and a today line](docs/roadmap.svg)
149
+
150
+ A state lifecycle marks the first state with `mark: "start"` and each final state with `mark: "end"`:
151
+
152
+ ![An order status lifecycle with a start dot and two end rings](docs/order-status.svg)
153
+
154
+ ## Open and share
155
+
156
+ `npx flowfig open figure.svg` shows the figure in your default browser.
157
+
158
+ `npx flowfig gif figure.svg` writes an animated GIF for Slack, Notion, X and slides, where SVG animation does not play.
159
+
160
+ The options are in [Open and share a figure](#open-and-share-a-figure).
80
161
 
81
162
  ## flowfig, Mermaid and a hand-drawn image
82
163
 
83
164
  | | flowfig | Mermaid | Hand-drawn image |
84
165
  | ------------------------------ | --------------------------------- | ------------------------------------ | ---------------- |
85
166
  | Made by an agent from the code | Yes, with the `init` instructions | Yes, as Mermaid text | No |
86
- | Checked for faults | Yes, 11 rules in `flowfig check` | Syntax errors only | No |
167
+ | Linked to the code | Yes, `verify` fails in CI | No | No |
168
+ | Checked for faults | Yes, 22 rules in `flowfig check` | Syntax errors only | No |
87
169
  | Animated | Yes, one packet per message | No | No |
88
170
  | Shows payloads and order | Yes, on the map and the rail | Order and text in a sequence diagram | No |
89
171
  | Plays in a GitHub README | Yes, as an SVG | Yes, GitHub renders it | Yes, as an image |
@@ -94,12 +176,11 @@ and the cost of the run. `draw` needs Claude Code on the machine and a login. `d
94
176
 
95
177
  - [Install](#install)
96
178
  - [Your first figure](#your-first-figure)
97
- - [Three forms](#three-forms)
179
+ - [Read and change a figure](#read-and-change-a-figure)
98
180
  - [The spec](#the-spec)
99
181
  - [Check a figure](#check-a-figure)
100
182
  - [Open and share a figure](#open-and-share-a-figure)
101
183
  - [Verify in CI](#verify-in-ci)
102
- - [For teams](#for-teams)
103
184
  - [MCP server](#mcp-server)
104
185
  - [Use in React](#use-in-react)
105
186
  - [Use from Node](#use-from-node)
@@ -163,28 +244,7 @@ The command writes `first.svg`:
163
244
  The SVG has no script and fetches no font. GitHub shows the SVG in a README, a PR or an issue. The SVG follows the light or dark
164
245
  color scheme of the reader.
165
246
 
166
- ## Three forms
167
-
168
- | Form | Spec | Use it when |
169
- | ------------- | -------------- | ----------------------------------------------------------------------- |
170
- | The map | (default) | The reader must see where each part runs and which parts talk. |
171
- | Map and rail | `rail: true` | The reader must see the parts, and also the order and payload of calls. |
172
- | The rail only | `rail: "only"` | The order of the messages is the point, and the map adds nothing to it. |
173
-
174
- The map:
175
-
176
- ![A request that misses the cache, then hits the cache](docs/cached-request.svg)
177
-
178
- The map with the rail:
179
-
180
- ![An order checkout: the map of the parts, and a rail of the messages under it](docs/checkout.svg)
181
-
182
- The rail draws one row for each message, with its payload.
183
- Hops in one beat share a parallel band. An `async` hop has a dashed arrow and an `async` tag.
184
-
185
- The rail only, from the same checkout spec:
186
-
187
- ![The checkout messages as a rail, without the map](docs/checkout-rail-only.svg)
247
+ ## Read and change a figure
188
248
 
189
249
  Each SVG from the CLI carries its own spec. These commands made the rail-only figure from `docs/checkout.svg`:
190
250
 
@@ -203,15 +263,19 @@ full reference, run `npx flowfig docs`.
203
263
 
204
264
  ### Boxes
205
265
 
206
- | Field | Meaning | Default |
207
- | -------- | ------------------------------------------------------------------------- | ----------- |
208
- | `id` | The name that edges and steps use. It must be unique. | required |
209
- | `label` | The title in the box. | required |
210
- | `sub` | A smaller line under the label. | none |
211
- | `shape` | `"box"`, `"decision"` (a diamond) or `"store"` (a data cylinder). | `"box"` |
212
- | `source` | The code this draws: `path` or `path#symbol`. `flowfig verify` checks it. | none |
213
- | `lines` | The least number of text lines that a content card keeps. | none |
214
- | `width` | The width in px. This value replaces the width that layout picks. | from layout |
266
+ | Field | Meaning | Default |
267
+ | -------- | --------------------------------------------------------------------------------------------------------- | ----------------------------------- |
268
+ | `id` | The name that edges and steps use. It must be unique. | required |
269
+ | `label` | The title in the box. | required |
270
+ | `sub` | A smaller line under the label. | none |
271
+ | `shape` | `"box"`, `"decision"` (a diamond) or `"store"` (a data cylinder). | `"box"` |
272
+ | `source` | The code this draws: `path` or `path#symbol`. `flowfig verify` checks it. | none |
273
+ | `lines` | The least number of text lines that a content card keeps. | none |
274
+ | `width` | The width in px. This value replaces the width that layout picks. | from layout |
275
+ | `at` | In a `lanes` figure: the time column of the box, from 0. | the order of first use in the steps |
276
+ | `from` | In a `timeline` figure: the start date of the item, as YYYY-MM-DD. A box with only `from` is a milestone. | none |
277
+ | `to` | In a `timeline` figure: the last day of the item, as YYYY-MM-DD. | none |
278
+ | `mark` | `"start"` draws a dot before the box. `"end"` draws a ring after the box. Use it on a state lifecycle. | none |
215
279
 
216
280
  ### Groups
217
281
 
@@ -262,20 +326,24 @@ A beat is an edge id, an array of edge ids that run at the same time, or an obje
262
326
  | `ms` | The length of the beat in ms. | `speed` (900) |
263
327
 
264
328
  In a hop, `back: true` runs the packet from `to` to `from`. `data` is a small card on the packet. `async: true` marks a message
265
- that does not wait for an answer.
329
+ that does not wait for an answer. On the rail, an `async` hop has a dashed arrow and an `async` tag. The hops of one beat
330
+ share a parallel band on the rail.
266
331
 
267
332
  Content is an array of rows, or a React node in the player. A row is `{ text, tag, tone, meta, mark, mono }`. Only `text` is
268
333
  required. The tones are `blue` (the default), `purple`, `green`, `orange` and `gray`.
269
334
 
270
335
  ### Figure options
271
336
 
272
- | Field | Meaning | Default |
273
- | ---------- | ------------------------------------------------------------------------- | -------- |
274
- | `rail` | `true` draws the rail under the map. `"only"` draws the rail alone. | `false` |
275
- | `speed` | The time in ms for a packet to cross one edge. | 900 |
276
- | `theme` | `{ accent, fg, muted, bg, surface, border, font }`. Each key is optional. | built-in |
277
- | `autoplay` | Start to play when the figure mounts. Only the React player reads it. | `true` |
278
- | `check` | Run the check rules in the browser. Only the React player reads it. | `false` |
337
+ | Field | Meaning | Default |
338
+ | ---------- | ------------------------------------------------------------------------------------ | -------- |
339
+ | `rail` | `true` draws the rail under the map. `"only"` draws the rail alone. | `false` |
340
+ | `lanes` | `true` draws swimlanes: a `column` group of labeled groups, one lane per role. | off |
341
+ | `timeline` | `true` draws a timeline: one labeled group per track, with the boxes at their dates. | off |
342
+ | `today` | In a `timeline` figure: the date of the today line, as YYYY-MM-DD. | none |
343
+ | `speed` | The time in ms for a packet to cross one edge. | 900 |
344
+ | `theme` | `{ accent, fg, muted, bg, surface, border, font }`. Each key is optional. | built-in |
345
+ | `autoplay` | Start to play when the figure mounts. Only the React player reads it. | `true` |
346
+ | `check` | Run the check rules in the browser. Only the React player reads it. | `false` |
279
347
 
280
348
  ## Check a figure
281
349
 
@@ -290,20 +358,32 @@ figure: 5 boxes, 3 groups, 4 edges, 2 steps, 6 messages
290
358
 
291
359
  The last line gives the counts of the parts of the figure. Compare the counts with the parts that you planned.
292
360
 
293
- | Rule | Severity | What it finds |
294
- | ------------------- | -------- | ---------------------------------------------------------------------------- |
295
- | `unknown-id` | error | An edge, step or beat names a box or edge that does not exist. |
296
- | `duplicate-id` | error | Two boxes, groups or edges have the same id. |
297
- | `hidden-edge` | error | A `quiet` edge that no beat uses, so the figure never shows it. |
298
- | `text-overflow` | error | Text that needs more width than its box has. |
299
- | `edge-crosses-box` | error | An edge that goes through a box that is not one of its ends. |
300
- | `label-overlap` | error | Two edge labels overlap, or an edge label covers a box. |
301
- | `low-contrast` | error | A text and background pair below 4.5:1, in the light, dark or custom theme. |
302
- | `empty-step` | warning | A step with no beats. |
303
- | `small-text` | warning | At the page width, the smallest text is below the minimum size. |
304
- | `lane-end-block` | warning | In wrapped lanes, an edge ends at a lane that no block on its side shows. |
305
- | `font-estimated` | warning | `theme.font` is set. The SVG check estimates text width for the system font. |
306
- | `color-not-checked` | warning | A color that the check cannot read, so its contrast is not checked. |
361
+ `flowfig check` has 22 rules: 11 errors and 11 warnings.
362
+
363
+ | Rule | Severity | What it finds |
364
+ | --------------------------- | -------- | --------------------------------------------------------------------------------------------------- |
365
+ | `unknown-id` | error | An edge, step or beat names a box, group or edge that does not exist. |
366
+ | `duplicate-id` | error | Two boxes, two groups or two edges have the same id. |
367
+ | `hidden-edge` | error | A `quiet` edge that no beat uses, so the figure never shows it. |
368
+ | `text-overflow` | error | Text that needs more width than its box has. |
369
+ | `edge-crosses-box` | error | An edge that goes through a box that is not one of its ends. |
370
+ | `label-overlap` | error | Two edge labels overlap, or a label covers a box or an edge, or a stub label crosses a lane border. |
371
+ | `low-contrast` | error | A text and background pair below 4.5:1, in the light, dark or custom theme, or on a tone tint. |
372
+ | `lanes-need-column` | error | The layout of a `lanes` or `timeline` figure is not a column group of labeled groups. |
373
+ | `bad-at` | error | An `at` value that is not an integer of 0 or more. |
374
+ | `timeline-need-from` | error | A box in a timeline has no `from` date. |
375
+ | `bad-date` | error | A `from`, `to` or `today` value is not a real YYYY-MM-DD date, or a `to` is before its `from`. |
376
+ | `empty-step` | warning | A step with no beats. |
377
+ | `small-text` | warning | At the page width, the smallest text is below the minimum size. |
378
+ | `bad-source` | warning | A `source` that is not `path` or `path#symbol`. |
379
+ | `mark-count` | warning | A lifecycle has more than one `start` mark, or a `start` mark and no `end` mark. |
380
+ | `lane-column-taken` | warning | Two boxes in one lane share a time column. |
381
+ | `lane-end-block` | warning | In wrapped lanes, an edge ends at a lane that no block on its side shows. |
382
+ | `stub-crosses-edge` | warning | In wrapped lanes, a stub line crosses another edge. |
383
+ | `timeline-dependency-order` | warning | In a timeline, an item does not start after the item that it depends on ends. |
384
+ | `timeline-and-lanes` | warning | The figure sets `timeline` and `lanes`. The renderers draw the timeline and ignore `lanes`. |
385
+ | `font-estimated` | warning | `theme.font` is set. The SVG check estimates text width for the system font. |
386
+ | `color-not-checked` | warning | A color that the check cannot read, so its contrast is not checked. |
307
387
 
308
388
  | Option | Effect |
309
389
  | ----------------- | ---------------------------------------------------------------------------------------------------------- |
@@ -351,39 +431,30 @@ fails when the file or the symbol is gone. The action runs `verify` on every fig
351
431
  the new image for each SVG the PR changes, with the spec changes as a list, and it names each figure whose linked code the PR
352
432
  changes.
353
433
 
354
- A process figure for a team links its boxes to the SOP document, not to code: `"source": "docs/sop/refunds.md#step-3-approve-the-refund"`. The symbol is the heading as a GitHub anchor. If the heading is gone, `verify` fails.
434
+ A process figure for a team links its boxes to the SOP document, not to code: `"source": "docs/sop/refunds.md#step-3-approve-or-reject"`. The symbol is the heading as a GitHub anchor. If the heading is gone, `verify` fails.
355
435
 
356
436
  ```yaml
357
- permissions:
358
- contents: read
359
- pull-requests: write
360
- steps:
361
- - uses: actions/checkout@v4
362
- with:
363
- fetch-depth: 0
364
- - uses: actions/setup-node@v4
365
- with:
366
- node-version: 22
367
- - uses: iamalvisng/flowfig@v0.2.0
368
- with:
369
- figures: 'docs/**/*.svg' # default **/*.svg
437
+ # .github/workflows/figures.yml
438
+ name: figures
439
+ on: pull_request
440
+ jobs:
441
+ figures:
442
+ runs-on: ubuntu-latest
443
+ permissions:
444
+ contents: read
445
+ pull-requests: write
446
+ steps:
447
+ - uses: actions/checkout@v4
448
+ with:
449
+ fetch-depth: 0 # the Action reads the old figure from the base commit
450
+ - uses: actions/setup-node@v4
451
+ with:
452
+ node-version: 22
453
+ - uses: iamalvisng/flowfig@v0.7.0
454
+ with:
455
+ figures: 'docs/**/*.svg' # default **/*.svg
370
456
  ```
371
457
 
372
- A state lifecycle marks the first state with `mark: "start"` and each final state with `mark: "end"`.
373
-
374
- ![An order status lifecycle with a start dot and two end rings](docs/order-status.svg)
375
-
376
- ## For teams
377
-
378
- A process is a diagram too. `lanes: true` draws one lane per role and the steps left to right in time order. Each step
379
- links to a heading in the SOP, so `verify` fails when the SOP changes under the diagram.
380
-
381
- ![The refund process across Customer, Support and Finance](docs/refund-process.svg)
382
-
383
- A plan is a figure too. `timeline: true` draws one track per team, with each item as a bar from its `from` date to its `to` date. A milestone has only `from`, and `today` draws the line for now.
384
-
385
- ![A Q4 roadmap with three tracks, milestones and a today line](docs/roadmap.svg)
386
-
387
458
  ## MCP server
388
459
 
389
460
  `npx flowfig mcp` serves the tools `docs`, `check`, `render`, `verify` and `diff` over stdio, for an agent with no shell.
package/dist/browser.d.ts CHANGED
@@ -2,7 +2,7 @@ export type Found = {
2
2
  path: string | null;
3
3
  checked: string[];
4
4
  };
5
- /** The first capture browser that exists. `CHROME_PATH` wins. Pure apart from `exists`. */
5
+ /** The first capture browser that exists. `CHROME_PATH` wins. */
6
6
  export declare function findBrowser(o: {
7
7
  platform: NodeJS.Platform;
8
8
  env: Record<string, string | undefined>;
@@ -12,7 +12,6 @@ export type Cdp = {
12
12
  send(method: string, params?: object, sessionId?: string, timeoutMs?: number): Promise<any>;
13
13
  once(event: string, sessionId?: string, timeoutMs?: number): Promise<any>;
14
14
  };
15
- /** Start the capture browser in headless mode with a CDP pipe. `close` stops it and never throws. */
16
15
  export declare function launch(path: string, profile: string, timeoutMs?: number): Promise<{
17
16
  cdp: Cdp;
18
17
  close: () => Promise<void>;
@@ -23,7 +22,6 @@ export type Capture = {
23
22
  height: number;
24
23
  loopMs: number;
25
24
  };
26
- /** Set the page, pause the animations and take one PNG per frame time. */
27
25
  export declare function captureFrames(cdp: Cdp, html: string, o: {
28
26
  width: number;
29
27
  height: number;