flowfig 0.8.4 → 0.10.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
@@ -65,11 +65,14 @@ jobs:
65
65
  - uses: actions/setup-node@v4
66
66
  with:
67
67
  node-version: 22
68
- - uses: iamalvisng/flowfig@v0.8.4
68
+ - uses: iamalvisng/flowfig@v0.10.0
69
69
  with:
70
70
  figures: 'docs/**/*.svg' # default **/*.svg
71
+ # coverage: true # also fail the job if a figure is stale
71
72
  ```
72
73
 
74
+ Set `coverage: true` to run `flowfig coverage --strict` after the comment. The job then fails if a figure is stale or fails verify.
75
+
73
76
  ## Use it
74
77
 
75
78
  Set up your repo:
@@ -117,7 +120,7 @@ the run.
117
120
 
118
121
  1. **The agent writes JSON, not pictures.** The agent reads the code and writes a spec of the parts, the calls and their
119
122
  order. flowfig does the layout.
120
- 2. **flowfig checks the spec.** `flowfig check` has 23 rules. It finds ids that point nowhere, text wider than its box,
123
+ 2. **flowfig checks the spec.** `flowfig check` has 26 rules. It finds ids that point nowhere, text wider than its box,
121
124
  edges through boxes, overlapping labels and low contrast. A render runs the same check and writes nothing on an error.
122
125
  The agent reads the faults and fixes the spec.
123
126
  3. **The output is one animated SVG with no script.** The SVG plays in a GitHub README, PR or issue. It follows the light
@@ -167,7 +170,7 @@ The options are in [Open and share a figure](#open-and-share-a-figure).
167
170
  | ------------------------------ | --------------------------------- | ------------------------------------ | ---------------- |
168
171
  | Made by an agent from the code | Yes, with the `init` instructions | Yes, as Mermaid text | No |
169
172
  | Linked to the code | Yes, `verify` fails in CI | No | No |
170
- | Checked for faults | Yes, 23 rules in `flowfig check` | Syntax errors only | No |
173
+ | Checked for faults | Yes, 26 rules in `flowfig check` | Syntax errors only | No |
171
174
  | Animated | Yes, one packet per message | No | No |
172
175
  | Shows payloads and order | Yes, on the map and the rail | Order and text in a sequence diagram | No |
173
176
  | Plays in a GitHub README | Yes, as an SVG | Yes, GitHub renders it | Yes, as an image |
@@ -183,6 +186,10 @@ The options are in [Open and share a figure](#open-and-share-a-figure).
183
186
  - [Check a figure](#check-a-figure)
184
187
  - [Open and share a figure](#open-and-share-a-figure)
185
188
  - [Verify in CI](#verify-in-ci)
189
+ - [Trace the calls of a function](#trace-the-calls-of-a-function)
190
+ - [Find stale figures](#find-stale-figures)
191
+ - [One page for all figures](#one-page-for-all-figures)
192
+ - [Convert Mermaid](#convert-mermaid)
186
193
  - [MCP server](#mcp-server)
187
194
  - [Use in React](#use-in-react)
188
195
  - [Use from Node](#use-from-node)
@@ -240,7 +247,8 @@ figure: 3 boxes, 0 groups, 2 edges, 1 step, 4 messages
240
247
  browser -> api: GET /user
241
248
  api -> db: read the user
242
249
  step "Load a user": 4 hops
243
- first.svg — 9.7 kB
250
+ alt: Flow figure: Browser, API, Database. Steps: Load a user.
251
+ first.svg — 10.1 kB
244
252
  ```
245
253
 
246
254
  The render prints the check result, the counts, one line per edge and one line per step. Read these lines to check the figure
@@ -264,8 +272,12 @@ npx flowfig --spec docs/checkout.svg > checkout.json # print the spec in the S
264
272
  npx flowfig checkout.json docs/checkout-rail-only.svg
265
273
  npx flowfig verify docs/checkout.svg # check each source and each edge against the code
266
274
  npx flowfig diff old.svg docs/checkout.svg # list what changed in the spec
275
+ npx flowfig diff old.svg docs/checkout.svg --svg diff.svg # draw the change in one figure
267
276
  ```
268
277
 
278
+ The diff SVG is a still figure. Green marks an added box or edge, red and dashed marks a removed one, and orange marks a changed one.
279
+ The diff SVG has no spec. The Action and `coverage` skip it. `verify` and `--spec` report that it has no spec.
280
+
269
281
  ## The spec
270
282
 
271
283
  A spec has a `layout`, `edges`, and optional `steps`. The full types have doc comments, so your editor shows each field. For the
@@ -280,6 +292,7 @@ full reference, run `npx flowfig docs`.
280
292
  | `sub` | A smaller line under the label. | none |
281
293
  | `shape` | `"box"`, `"decision"` (a diamond) or `"store"` (a data cylinder). | `"box"` |
282
294
  | `source` | The code this draws: `path` or `path#symbol`. `flowfig verify` checks it. | none |
295
+ | `detail` | A more detailed figure: the SVG path from the repo root. `flowfig atlas` links the box to it. | none |
283
296
  | `lines` | The least number of text lines that a content card keeps. | none |
284
297
  | `width` | The width in px. This value replaces the width that layout picks. | from layout |
285
298
  | `at` | In a `lanes` figure: the time column of the box, from 0. | the order of first use in the steps |
@@ -299,18 +312,49 @@ full reference, run `npx flowfig docs`.
299
312
  | `direction` | `"row"` puts the children side by side. `"column"` stacks them. | `"row"` |
300
313
  | `gap` | The smallest space between the children in px. It grows to fit the labels of edges that cross the group. | column: 28; row: fits the widest label, at least 56 |
301
314
  | `align` | `"start"`, `"center"` or `"end"`, across the direction. | `"center"` (a column stretches) |
315
+ | `auto` | On the root layout: `true` makes flowfig place the boxes. See [Automatic layout](#automatic-layout). | off |
316
+
317
+ ### Automatic layout
318
+
319
+ Set `"auto": true` on the root layout and list the boxes. flowfig places them in ranks, in the direction of the edges.
320
+ Use it when you do not want to plan rows and columns, or when `check` reports an edge through a box.
321
+
322
+ ```json
323
+ {
324
+ "layout": {
325
+ "auto": true,
326
+ "children": [
327
+ { "id": "api", "label": "API" },
328
+ { "id": "queue", "label": "Queue", "shape": "store" },
329
+ { "label": "Workers", "children": [{ "id": "w", "label": "Worker" }] }
330
+ ]
331
+ },
332
+ "edges": [
333
+ { "from": "api", "to": "queue", "label": "enqueue" },
334
+ { "from": "queue", "to": "w", "label": "pull" }
335
+ ]
336
+ }
337
+ ```
338
+
339
+ Render the spec with `npx flowfig spec.json docs/auto-layout.svg`. This figure has 9 boxes, 1 frame, 1 decision, 1 store and 2 steps:
340
+
341
+ ![Flow figure: Shopper, Web shop, In stock?, Order, Payment, Shipping, Orders, Email, Back order. Steps: Item in stock, Item sold out.](docs/auto-layout.svg)
342
+
343
+ A group with a `label` or an `id` stays together as one frame. flowfig removes a group with neither, with its `direction`, `gap`
344
+ and `align`. flowfig ignores `around` on the edges. A `direction` on the root forces `"row"` or `"column"`. `lanes` and
345
+ `timeline` figures ignore `auto`.
302
346
 
303
347
  ### Edges
304
348
 
305
- | Field | Meaning | Default |
306
- | -------- | ------------------------------------------------------------------------------ | ---------- |
307
- | `from` | The id of the box or group where the edge starts. | required |
308
- | `to` | The id of the box or group where the edge ends. | required |
309
- | `id` | The name that beats use. | `from->to` |
310
- | `label` | The text on the edge. | none |
311
- | `around` | `"above"` or `"below"` routes the edge over or under the boxes between. | none |
312
- | `quiet` | `true` draws the edge only while a step uses it. | `false` |
313
- | `source` | The code this edge draws: `path` or `path#symbol`. `flowfig verify` checks it. | none |
349
+ | Field | Meaning | Default |
350
+ | -------- | --------------------------------------------------------------------------------------------------- | ---------- |
351
+ | `from` | The id of the box or group where the edge starts. | required |
352
+ | `to` | The id of the box or group where the edge ends. | required |
353
+ | `id` | The name that beats use. | `from->to` |
354
+ | `label` | The text on the edge. | none |
355
+ | `around` | `"above"`, `"below"`, `"left"` or `"right"` routes the edge around the boxes between, on that side. | none |
356
+ | `quiet` | `true` draws the edge only while a step uses it. | `false` |
357
+ | `source` | The code this edge draws: `path` or `path#symbol`. `flowfig verify` checks it. | none |
314
358
 
315
359
  ### Steps
316
360
 
@@ -355,6 +399,9 @@ required. The tones are `blue` (the default), `purple`, `green`, `orange` and `g
355
399
  | `autoplay` | Start to play when the figure mounts. Only the React player reads it. | `true` |
356
400
  | `check` | Run the check rules in the browser. Only the React player reads it. | `false` |
357
401
 
402
+ A figure is as wide as its boxes need, and at least 320 px. The step captions wrap at the figure
403
+ width, so a narrow figure fits a phone screen at full size. A rail-only figure is at least 560 px.
404
+
358
405
  ## Check a figure
359
406
 
360
407
  `flowfig check` reads a spec and lists the faults. The input is `-` (stdin), a `.json` file, a `.ts` module or an SVG from the
@@ -368,33 +415,36 @@ figure: 5 boxes, 3 groups, 4 edges, 2 steps, 6 messages
368
415
 
369
416
  The last line gives the counts of the parts of the figure. Compare the counts with the parts that you planned.
370
417
 
371
- `flowfig check` has 23 rules: 11 errors and 12 warnings.
372
-
373
- | Rule | Severity | What it finds |
374
- | --------------------------- | -------- | --------------------------------------------------------------------------------------------------- |
375
- | `unknown-id` | error | An edge, step or beat names a box, group or edge that does not exist. |
376
- | `duplicate-id` | error | Two boxes, two groups or two edges have the same id. |
377
- | `hidden-edge` | error | A `quiet` edge that no beat uses, so the figure never shows it. |
378
- | `text-overflow` | error | Text that needs more width than its box has. |
379
- | `edge-crosses-box` | error | An edge that goes through a box that is not one of its ends. |
380
- | `label-overlap` | error | Two edge labels overlap, or a label covers a box or an edge, or a stub label crosses a lane border. |
381
- | `low-contrast` | error | A text and background pair below 4.5:1, in the light, dark or custom theme, or on a tone tint. |
382
- | `lanes-need-column` | error | The layout of a `lanes` or `timeline` figure is not a column group of labeled groups. |
383
- | `bad-at` | error | An `at` value that is not an integer of 0 or more. |
384
- | `timeline-need-from` | error | A box in a timeline has no `from` date. |
385
- | `bad-date` | error | A `from`, `to` or `today` value is not a real YYYY-MM-DD date, or a `to` is before its `from`. |
386
- | `empty-step` | warning | A step with no beats. |
387
- | `small-text` | warning | At the page width, the smallest text is below the minimum size. |
388
- | `bad-source` | warning | A `source` that is not `path` or `path#symbol`. |
389
- | `mark-count` | warning | A lifecycle has more than one `start` mark, or a `start` mark and no `end` mark. |
390
- | `lane-column-taken` | warning | Two boxes in one lane share a time column. |
391
- | `lane-end-block` | warning | In wrapped lanes, an edge ends at a lane that no block on its side shows. |
392
- | `stub-crosses-edge` | warning | In wrapped lanes, a stub line crosses another edge. |
393
- | `timeline-dependency-order` | warning | In a timeline, an item does not start after the item that it depends on ends. |
394
- | `timeline-and-lanes` | warning | The figure sets `timeline` and `lanes`. The renderers draw the timeline and ignore `lanes`. |
395
- | `font-estimated` | warning | `theme.font` is set. The SVG check estimates text width for the system font. |
396
- | `color-not-checked` | warning | A color that the check cannot read, so its contrast is not checked. |
397
- | `plain-text` | warning | Text with a code name, a filler word, or a `say` or caption over 20 words. |
418
+ `flowfig check` has 26 rules: 11 errors and 15 warnings.
419
+
420
+ | Rule | Severity | What it finds |
421
+ | --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
422
+ | `unknown-id` | error | An edge, step or beat names a box, group or edge that does not exist. |
423
+ | `duplicate-id` | error | Two boxes, two groups or two edges have the same id. |
424
+ | `hidden-edge` | error | A `quiet` edge that no beat uses, so the figure never shows it. |
425
+ | `text-overflow` | error | Text that needs more width than its box has. |
426
+ | `edge-crosses-box` | error | An edge that goes through a box that is not one of its ends. |
427
+ | `label-overlap` | error | Two edge labels overlap, or a label covers a box or an edge, or a stub label crosses a lane border. |
428
+ | `low-contrast` | error | A text and background pair below 4.5:1, in the light, dark or custom theme, or on a tone tint. |
429
+ | `lanes-need-column` | error | The layout of a `lanes` or `timeline` figure is not a column group of labeled groups. |
430
+ | `bad-at` | error | An `at` value that is not an integer of 0 or more. |
431
+ | `timeline-need-from` | error | A box in a timeline has no `from` date. |
432
+ | `bad-date` | error | A `from`, `to` or `today` value is not a real YYYY-MM-DD date, or a `to` is before its `from`. |
433
+ | `empty-step` | warning | A step with no beats. |
434
+ | `small-text` | warning | At the page width, the smallest text is below the minimum size. |
435
+ | `bad-source` | warning | A `source` that is not `path` or `path#symbol`. |
436
+ | `bad-detail` | warning | A `detail` that is not a relative path that ends in `.svg`. |
437
+ | `mark-count` | warning | A lifecycle has more than one `start` mark, or a `start` mark and no `end` mark. |
438
+ | `lane-column-taken` | warning | Two boxes in one lane share a time column. |
439
+ | `lane-end-block` | warning | In wrapped lanes, an edge ends at a lane that no block on its side shows. |
440
+ | `stub-crosses-edge` | warning | In wrapped lanes, a stub line crosses another edge. |
441
+ | `long-edge` | warning | An edge at least 600 px long that is 1.6 times or more the straight distance between its ends. |
442
+ | `timeline-dependency-order` | warning | In a timeline, an item does not start after the item that it depends on ends. An item may start on the day of a milestone. |
443
+ | `timeline-and-lanes` | warning | The figure sets `timeline` and `lanes`. The renderers draw the timeline and ignore `lanes`. |
444
+ | `font-estimated` | warning | `theme.font` is set. The SVG check estimates text width for the system font. |
445
+ | `color-not-checked` | warning | A color that the check cannot read, so its contrast is not checked. |
446
+ | `plain-text` | warning | Text with a code name, a filler word, or a `say` or caption over 20 words. |
447
+ | `auto-ignored` | warning | `auto` with `lanes` or `timeline`, `auto` on a nested group, or `around` on an edge with `auto`. |
398
448
 
399
449
  | Option | Effect |
400
450
  | ----------------- | ---------------------------------------------------------------------------------------------------------- |
@@ -453,6 +503,8 @@ docs/login.svg: 7 of 7 boxes defined; edges: 9 found, 0 not found, 1 unsure, 1 n
453
503
  - unsure: `verify` cannot decide, for example when a receiver has no declared type. `verify` lists it with the reason. It never fails CI.
454
504
  - not checked: the edge has no `source`, or the language is not supported.
455
505
 
506
+ `verify` also checks `detail`. If the file is missing or has no figure spec, `verify` warns with `missing-detail`. `--strict` makes it an error.
507
+
456
508
  A method source is `path#Owner.name`. An edge `source` names the function that makes the call.
457
509
 
458
510
  `via` on an edge names the route, queue, topic, table, file or key that both sides use. For a `via` edge, found means that the caller code and the callee file both use that token. It does not prove that a handler serves it.
@@ -478,11 +530,292 @@ jobs:
478
530
  - uses: actions/setup-node@v4
479
531
  with:
480
532
  node-version: 22
481
- - uses: iamalvisng/flowfig@v0.8.4
533
+ - uses: iamalvisng/flowfig@v0.10.0
482
534
  with:
483
535
  figures: 'docs/**/*.svg' # default **/*.svg
484
536
  ```
485
537
 
538
+ ## Trace the calls of a function
539
+
540
+ `flowfig trace` lists the calls that one function makes, and the calls that those calls make. Ask your coding agent to run it
541
+ before it writes a figure. Then the agent reads only the lines that the trace names. Each edge passes the same check as
542
+ `verify`.
543
+
544
+ `src/login.ts`:
545
+
546
+ ```ts
547
+ import { verifyPassword } from './password.ts';
548
+ import { findUser } from './users.ts';
549
+
550
+ export function login(name: string, password: string) {
551
+ const user = findUser(name);
552
+ return verifyPassword(user, password);
553
+ }
554
+ ```
555
+
556
+ `src/password.ts`:
557
+
558
+ ```ts
559
+ export function verifyPassword(user: { hash: string }, password: string) {
560
+ return user.hash === password.split('').reverse().join('');
561
+ }
562
+ ```
563
+
564
+ `src/users.ts`:
565
+
566
+ ```ts
567
+ export function findUser(name: string) {
568
+ return { name, hash: 'drowssap' };
569
+ }
570
+ ```
571
+
572
+ ```console
573
+ $ npx flowfig trace src/login.ts#login
574
+ src/login.ts#login
575
+ 5 src/users.ts#findUser
576
+ 6 src/password.ts#verifyPassword
577
+ summary: 3 symbols, 2 found, 0 unsure, 0 open, 1 call outside the repo, 0.0 s
578
+ ```
579
+
580
+ - A header line gives a caller as `file#symbol`. Each call line under it gives the line of the call, then the callee.
581
+ The call is in the file of the caller. A callee in the same file shows only its symbol.
582
+ - `unsure`: trace cannot name the target. An example is a call of a parameter.
583
+ - `open`: the target is a name in a string, for example a route table entry. Read that line yourself.
584
+ - `stop`: the trace reached `--depth` or `--max`. The line gives the number of functions that it did not follow.
585
+ - A call into a package or the standard library gives no line. The summary line counts these calls.
586
+
587
+ A call that trace cannot see gives no line, for example a call through an interface with no known type.
588
+ trace reads code as text. In rare cases, two names that look the same can give a wrong edge. `flowfig verify` and your review of the figure still check each edge.
589
+
590
+ | Option | Meaning | Default |
591
+ | -------------- | -------------------------------------------------- | ------------------ |
592
+ | `--depth <n>` | The number of call levels to follow. | 2 |
593
+ | `--max <n>` | The largest number of edges. Then the trace stops. | 40 |
594
+ | `--root <dir>` | The repo root. The file path is relative to it. | the current folder |
595
+ | `--json` | Print one JSON object in place of the lines. | off |
596
+
597
+ The exit code is 1 if the file or the function is not found. It is also 1 if the language is not TypeScript,
598
+ JavaScript, Python, Go, Java, C# or Rust.
599
+
600
+ ## Find stale figures
601
+
602
+ `flowfig coverage` reads each figure in the repo and gives it one state. It also shows the code that has no figure. Run
603
+ it each week, or in CI.
604
+
605
+ ```console
606
+ $ npx flowfig coverage --entries 'src/geometry.ts'
607
+ docs/cached-request.svg ok 3 of 3 boxes defined
608
+ docs/checkout-rail-only.svg ok 4 of 4 boxes defined
609
+ docs/checkout.svg ok 4 of 4 boxes defined
610
+ docs/first.svg none no source links
611
+ docs/hero.svg none no source links
612
+ docs/order-status.svg none no source links
613
+ docs/refund-process.svg ok 6 of 6 boxes defined
614
+ docs/returns-process.svg ok 8 of 8 boxes defined
615
+ docs/roadmap.svg ok 8 of 8 boxes defined
616
+ uncovered src/geometry.ts: 1 of 1 files have no figure
617
+ src/geometry.ts
618
+ summary: 9 figures: 6 ok, 0 stale, 0 fail, 3 none
619
+ ```
620
+
621
+ - `fail`: `verify` finds an error, for example a symbol that is gone.
622
+ - `stale`: `verify` finds no error, but a linked file changed in a commit after the last commit of the SVG. A linked file
623
+ with changes that are not committed counts as changed now.
624
+ - `ok`: `verify` finds no error, and no linked file changed after the SVG.
625
+ - `none`: the figure has no `source` link.
626
+
627
+ `coverage` reads the commit dates from git. If the folder is not in a git repo, `coverage` does not check `stale`. It
628
+ prints one line that says so.
629
+
630
+ A figure covers a code file if one of its links names that file. With `--entries`, `coverage` lists each matched code
631
+ file that no figure covers. With no `--entries`, it prints one count line for each top folder.
632
+ `coverage` does not count test files as code: `*.test.*`, `*.spec.*`, `__tests__/`, `test_*.py`, `*_test.py`, `*_test.go`,
633
+ `*Test.java`, `*Tests.java`, `*Test.cs`, `*Tests.cs`, and files under `tests/` or `src/test/`. An entry that `--entries` names in full still counts.
634
+
635
+ | Option | Meaning | Default |
636
+ | ------------------ | ------------------------------------------------------------- | ------------------ |
637
+ | `--figures <glob>` | The SVG files to read. Only an SVG with a figure spec counts. | `**/*.svg` |
638
+ | `--entries <glob>` | The code files that must have a figure. | none |
639
+ | `--root <dir>` | The repo root. | the current folder |
640
+ | `--strict` | Exit 1 if a figure is `fail` or `stale`. | off |
641
+ | `--json` | Print one JSON object in place of the lines. | off |
642
+
643
+ `coverage` skips `node_modules`, `dist` and each folder with a name that starts with a dot.
644
+
645
+ ## One page for all figures
646
+
647
+ `flowfig atlas` writes one web page for each figure, and an index page. A box can link to a more detailed figure with
648
+ `detail`. The path starts at the repo root, as `source` does.
649
+
650
+ `docs/system.svg`:
651
+
652
+ ```json
653
+ {
654
+ "layout": {
655
+ "children": [
656
+ { "id": "up", "label": "Whole system", "detail": "docs/flows/orders.svg" },
657
+ { "id": "db", "label": "Orders table" }
658
+ ]
659
+ },
660
+ "edges": []
661
+ }
662
+ ```
663
+
664
+ `docs/flows/orders.svg`:
665
+
666
+ ```json
667
+ {
668
+ "layout": {
669
+ "children": [
670
+ { "id": "web", "label": "Web app" },
671
+ { "id": "orders", "label": "Order service", "detail": "docs/system.svg" }
672
+ ]
673
+ },
674
+ "edges": []
675
+ }
676
+ ```
677
+
678
+ `docs/system.svg` links down to the order flow. `docs/flows/orders.svg` links back up.
679
+
680
+ ```console
681
+ $ npx flowfig atlas
682
+ /path/to/repo/atlas/index.html — 2 figure pages
683
+ ```
684
+
685
+ The command prints the full path of `index.html`.
686
+
687
+ | Option | Meaning | Default |
688
+ | ------------------ | ------------------------------------------------------------- | ------------------ |
689
+ | `--figures <glob>` | The SVG files to read. Only an SVG with a figure spec counts. | `**/*.svg` |
690
+ | `--root <dir>` | The repo root. `detail` paths start here. | the current folder |
691
+ | `--out <dir>` | The folder to write. | `atlas` |
692
+ | `--open` | Open `index.html` in the default browser. | off |
693
+
694
+ The command writes `index.html`, one `.html` page for each figure, and an empty `.nojekyll` file. It deletes nothing. If a file of the same name exists and the atlas did not write it, the command writes nothing and exits with 2.
695
+ All links are relative, so the pages work from a file, a sub-path or any file host. The exit code is 0 when the site is
696
+ written, whatever the health of the figures. It is 1 when no figure matches, and 2 for a bad flag.
697
+
698
+ The index lists each figure with its health from `coverage`: `ok`, `stale`, `fail` or `none`. A figure with a bad spec
699
+ has no page. Its row shows `fail` and the error.
700
+
701
+ Each figure page has these parts, in this order:
702
+
703
+ 1. A link "All figures".
704
+ 2. The figure path and its health.
705
+ 3. The figure.
706
+ 4. A "Details" list with one link for each box that has `detail`.
707
+ 5. The transcript of the figure.
708
+
709
+ A link works in these places:
710
+
711
+ - In the README, an SVG is an image, so a box has no link. The README render does not change.
712
+ - On an atlas page, a click on a box opens the page of its `detail` figure. The mouse is the only way to use this link.
713
+ - The "Details" list has the same links with names. Use it with the keyboard and a screen reader.
714
+ - If the target figure is not in the atlas, the box has no link. The list row says "not in the atlas".
715
+
716
+ The atlas draws each spec with the installed flowfig and the default options. A page can differ from the committed SVG
717
+ if you drew that SVG with `--width` or `--min-text`. A figure that you remove keeps its old page in `--out`.
718
+
719
+ To publish the pages on GitHub Pages, run `npx flowfig atlas --out docs/atlas` and commit the result. Then set Pages to
720
+ "Deploy from a branch" with the folder `/docs`.
721
+
722
+ ## Convert Mermaid
723
+
724
+ `flowfig from-mermaid` turns a Mermaid flowchart, sequence diagram, state diagram or `gantt` into a flowfig spec. The converter uses no model.
725
+ Then `check` and `verify` can test the figure.
726
+
727
+ ```console
728
+ $ cat login.mmd
729
+ sequenceDiagram
730
+ participant W as Web app
731
+ W->>A: POST /login
732
+ A-->>W: 200 session cookie
733
+ A-)Q: user.logged_in
734
+ $ npx flowfig from-mermaid login.mmd > login.json
735
+ $ npx flowfig login.json login.svg
736
+ ```
737
+
738
+ The input is a `.mmd` file with one diagram, or a Markdown file. In a Markdown file, each fenced block with the language
739
+ `mermaid` is one diagram. `-` reads one diagram from stdin. With one diagram, the command prints the spec. With
740
+ `--out <dir>`, the command writes one JSON file for each diagram, such as `docs-1.json` and `docs-2.json`.
741
+
742
+ The command exits 1 if a diagram has a line that the converter does not read. It prints `<file>:<line>: <reason>` for each
743
+ line, and writes no spec for that diagram. The command never guesses.
744
+
745
+ This flowchart has a decision, a subgraph and a store:
746
+
747
+ ```mermaid
748
+ flowchart TD
749
+ A([Visitor]) --> B[Sign-up form]
750
+ B --> C{Email valid?}
751
+ C -->|no| X[Show error]
752
+ C -->|yes| D
753
+ subgraph auth [Account service]
754
+ D[Create account] --> E[Hash password]
755
+ end
756
+ E --> F[(Users)]
757
+ E --> G[Welcome email]
758
+ ```
759
+
760
+ `npx flowfig from-mermaid signup.mmd > signup.json` converts it. `npx flowfig signup.json docs/from-mermaid.svg` renders it:
761
+
762
+ ![Flow figure: Visitor, Sign-up form, Email valid?, Show error, Create account, Hash password, Users, Welcome email.](docs/from-mermaid.svg)
763
+
764
+ A flowchart (`flowchart` or `graph`) becomes a still map with automatic layout:
765
+
766
+ - Nodes: `A`, `A[text]`, `A(text)`, `A([text])`, `A((text))`, `A{text}` (a decision) and `A[(text)]` (a store). The text can
767
+ have quotes. `<br>` becomes a space. `#quot;`, `#amp;`, `#lt;`, `#gt;`, `#apos;`, `#nbsp;` and `#NN;` (a decimal code)
768
+ become their characters.
769
+ - Links: `-->`, `-.->`, `==>`, with a label as `-->|text|` or `-- text -->`. Chains (`A --> B --> C`) and `&`
770
+ (`A & B --> C`) are read.
771
+ - `subgraph id`, `subgraph id [title]`, `subgraph id["title"]` or `subgraph title` ... `end` becomes a group with a frame. A subgraph with no
772
+ node is dropped.
773
+ - `classDef`, `class`, `style`, `linkStyle`, `click`, `direction`, `:::class` and `%%` comments are ignored.
774
+ - In all four diagram types, `accTitle:`, `accDescr:` and `accDescr { ... }` are ignored.
775
+
776
+ A sequence diagram becomes a rail (`rail: "only"`):
777
+
778
+ - Each `participant` or `actor` is a box. A participant that first shows in a message is a box too.
779
+ - Each pair of participants gets one edge. Each message is one hop on that edge, with its text in `data`.
780
+ - A message in the other direction of the edge is a hop with `back: true`. `-)` gives `async: true`.
781
+ - With no `alt`, the figure has one step with all the messages.
782
+ - Each `alt` or `else` branch becomes one step. The step has the messages before the `alt`, the messages of that branch, and
783
+ the messages after the `end`. The step label is the branch condition.
784
+ - `loop`, `opt`, `par`, `and` and `rect` lines are ignored. Their messages stay in order in each step.
785
+ - `activate`, `deactivate`, `+` and `-`, `Note` and `autonumber` are ignored.
786
+
787
+ A state diagram (`stateDiagram` or `stateDiagram-v2`) becomes a still map with automatic layout:
788
+
789
+ - Each state is a box. `state "Long name" as A` sets the label. `A : text` puts the text under the label, as Mermaid does.
790
+ - `A --> B` and `A --> B : text` become an edge with a label.
791
+ - `[*] --> A` at the top level gives A `mark: "start"`. `A --> [*]` at the top level gives A `mark: "end"`.
792
+ - `state A { ... }` becomes a group with a frame. A `[*]` in it gives no edge and no mark.
793
+ - `state A <<choice>>` becomes a decision. Its label is its id.
794
+ - `direction`, `classDef`, `class`, `%%` comments, `note` and `hide empty description` are ignored.
795
+
796
+ A `gantt` becomes a timeline (`timeline: true`):
797
+
798
+ - Each `section` is a track. Each task is a box in its track. A task with no id gets the id `task-1`, `task-2` and so on.
799
+ - A task can have a start date or `after <id>`, and an end date or a duration in days (`10d`) or weeks (`2w`).
800
+ A task with only a duration starts when the task above it ends.
801
+ - Mermaid reads an end date as the start of that day. So the box `to` is the day before the end date.
802
+ - `after a b` starts the task when the last of `a` and `b` ends. Each id gives an edge to the task.
803
+ - `milestone` with `0d` gives a box with only `from`. `crit` gives `tone: "red"`. `done` and `active` are ignored.
804
+ - `title`, `axisFormat`, `tickInterval`, `todayMarker`, `weekday` and `click` are ignored. The today line uses the flowfig default.
805
+
806
+ Limits:
807
+
808
+ - `RL` reads as `LR`, and `BT` reads as `TB`. The automatic layout sets the order of the boxes.
809
+ - `-.->` and `==>` give a plain edge. flowfig has no dotted or thick edge.
810
+ - A link with no arrow (`---`, `-.-`, `===`) is an error. Use `-->` to give it a direction.
811
+ - Other node shapes, Markdown strings, `<-->`, `--o`, `--x`, `-x` and a message to the same participant are errors.
812
+ - A second `alt` and an `alt` inside an `alt` are errors.
813
+ - In a state diagram, `<<fork>>`, `<<join>>`, a concurrent region (`--`), `:::class` and a transition to the same state
814
+ are errors. A `[*]` on a composite state, a description on a composite state and a label on a `[*]` transition are errors too.
815
+ - A state with a start and an end transition is an error, because a box has one mark.
816
+ - In a `gantt`, a `dateFormat` other than `YYYY-MM-DD`, `excludes`, `weekend`, `inclusiveEndDates`, `until`, a duration in
817
+ hours and a milestone with a duration are errors. These change the dates, and the converter never guesses a date.
818
+
486
819
  ## MCP server
487
820
 
488
821
  `npx flowfig mcp` serves the tools `docs`, `check`, `render`, `verify` and `diff` over stdio, for an agent with no shell.
@@ -512,6 +845,7 @@ The player has these controls:
512
845
  - One tab for each step, with a progress line. A click on a tab starts that step.
513
846
  - A pause button, and a speed button that changes between 1× and 2×.
514
847
  - A full screen button. Esc closes full screen.
848
+ - The step tabs take the Left, Right, Home and End keys.
515
849
  - A hover on a box highlights its edges. With the rail, a click on a row starts that message, and a hover highlights its edge.
516
850
  - A hover on a box, an edge or a rail row with a `source` shows the `source`. The SVG shows it as a tooltip too.
517
851
 
@@ -0,0 +1,6 @@
1
+ export type AtlasOptions = {
2
+ root?: string;
3
+ figures?: string;
4
+ };
5
+ export declare function atlasFiles({ root, figures }?: AtlasOptions): Map<string, string>;
6
+ export declare function runAtlas(argv: string[]): Promise<number>;