Your coding agent draws the diagram from your code. flowfig checks it, and CI fails when the linked code is gone.
The problem
A diagram in a repo drifts from the code. Someone renames a function, and the diagram still shows the old name. No check fails, so no one sees the drift.
flowfig links each part of a diagram to the code that it draws. flowfig verify fails when a linked file or symbol is
gone, and warns when the code does not make an edge. Run verify in CI, and a stale diagram fails the pull request.
Fail CI when the diagram drifts
A box, an edge or a message can name the code that it draws:
{ "id": "verify", "label": "verifyPassword", "source": "src/auth/login.ts#verifyPassword" }Rename verifyPassword to checkPassword. Then verify fails with exit code 1:
$ npx flowfig verify login.svg
error missing-symbol login.svg: box "verify" -> src/auth/login.ts#verifyPassword: symbol not defined
1 error, 0 warnings
login.svg: 0 of 1 boxes definedverify checks that each linked file exists and that it defines the symbol. It also checks each edge against the code
(see Verify in CI). A link to a Markdown file can name a heading, so a process diagram can follow its SOP
document.
The GitHub Action runs verify on every figure in a pull request. For each SVG that the PR changes, it comments the old
image, the new image and the spec changes. It also names each figure whose linked code the PR changes.
# .github/workflows/figures.yml
name: figures
on: pull_request
jobs:
figures:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the Action reads the old figure from the base commit
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: iamalvisng/flowfig@v0.9.0
with:
figures: 'docs/**/*.svg' # default **/*.svgUse it
Set up your repo:
npx flowfig init/figure how does login work
With any agent, ask in plain words:
draw a diagram of how login works in this repo
use flowfig to draw the checkout flow as a sequence
draw the refund process with a lane for each team
The agent writes an SVG and ends its reply with npx flowfig open <path>. Run that command to see the animation in your
browser.
One command
npx flowfig draw "how does login work"How it works
- The agent writes JSON, not pictures. The agent reads the code and writes a spec of the parts, the calls and their order. flowfig does the layout.
- flowfig checks the spec.
flowfig checkhas 26 rules. It finds ids that point nowhere, text wider than its box, edges through boxes, overlapping labels and low contrast. A render runs the same check and writes nothing on an error. The agent reads the faults and fixes the spec. - 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 mode of the reader. The SVG carries its spec, so
npx flowfig --spec figure.svgprints it back.
Forms
The map (the default) shows the parts and the messages between them:
The map and the rail (rail: true) adds one row for each message, with its payload:
The rail only (rail: "only") shows the order of the messages without the map:
Swimlanes (lanes: true) give each role a lane and put the steps left to right in time order:
A long swimlane process wraps into blocks at the page width. An edge between two blocks becomes two labeled stubs:
The timeline (timeline: true) draws a roadmap with one track per team, dated bars, milestones and a today line:
A state lifecycle marks the first state with mark: "start" and each final state with mark: "end":
Open and share
npx flowfig open figure.svg shows the figure in your default browser.
npx flowfig gif figure.svg writes an animated GIF for Slack, Notion, X and slides, where SVG animation does not play.
The options are in Open and share a figure.
flowfig, Mermaid and a hand-drawn image
| flowfig | Mermaid | Hand-drawn image | |
|---|---|---|---|
| Made by an agent from the code | Yes, with the init instructions |
Yes, as Mermaid text | No |
| Linked to the code | Yes, verify fails in CI |
No | No |
| Checked for faults | Yes, 26 rules in flowfig check |
Syntax errors only | No |
| Animated | Yes, one packet per message | No | No |
| Shows payloads and order | Yes, on the map and the rail | Order and text in a sequence diagram | No |
| Plays in a GitHub README | Yes, as an SVG | Yes, GitHub renders it | Yes, as an image |
| Readable back as source | Yes, with --spec |
Yes, the source is text | No |
| Class and ER diagrams | No | Yes. Mermaid wins this row. | Yes |
Contents
- Install
- Your first figure
- Read and change a figure
- The spec
- Check a figure
- Open and share a figure
- Verify in CI
- Trace the calls of a function
- Find stale figures
- One page for all figures
- MCP server
- Use in React
- Use from Node
- Use with your coding agent
- What flowfig does not draw
- License
- Development
Install
npm i flowfigReact (>=18) is a peer dependency. Only the React player needs React. The CLI and flowfig/svg do not need React. flowfig needs
Node 18 or later.
Your first figure
Save this spec as first.json. The spec has 3 boxes, 2 edges and 1 step.
{
"layout": {
"children": [
{ "id": "browser", "label": "Browser" },
{ "id": "api", "label": "API" },
{ "id": "db", "label": "Database", "shape": "store" }
]
},
"edges": [
{ "from": "browser", "to": "api", "label": "GET /user" },
{ "from": "api", "to": "db", "label": "read the user" }
],
"steps": [
{
"label": "Load a user",
"flow": [
{ "edges": "browser->api", "say": "The browser asks the API for the user." },
{ "edges": "api->db", "say": "The API reads the row." },
{ "edges": { "edge": "api->db", "back": true }, "say": "The row comes back." },
{ "edges": { "edge": "browser->api", "back": true }, "say": "The API sends the user as JSON." }
]
}
]
}Render the spec:
$ npx flowfig first.json
0 errors, 0 warnings
figure: 3 boxes, 0 groups, 2 edges, 1 step, 4 messages
browser -> api: GET /user
api -> db: read the user
step "Load a user": 4 hops
alt: Flow figure: Browser, API, Database. Steps: Load a user.
first.svg — 10.1 kBThe render prints the check result, the counts, one line per edge and one line per step. Read these lines to check the figure
without a second command. If a box or an edge has a source or a via, the render also prints the verify counts.
--no-verify skips them.
The command writes first.svg:
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 color scheme of the reader.
Read and change a figure
Each SVG from the CLI carries its own spec. These commands made the rail-only figure from docs/checkout.svg:
npx flowfig --spec docs/checkout.svg > checkout.json # print the spec in the SVG
# edit checkout.json: set "props.rail" to "only"
npx flowfig checkout.json docs/checkout-rail-only.svg
npx flowfig verify docs/checkout.svg # check each source and each edge against the code
npx flowfig diff old.svg docs/checkout.svg # list what changed in the spec
npx flowfig diff old.svg docs/checkout.svg --svg diff.svg # draw the change in one figureThe 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.
The diff SVG has no spec. The Action and coverage skip it. verify and --spec report that it has no spec.
The spec
A spec has a layout, edges, and optional steps. The full types have doc comments, so your editor shows each field. For the
full reference, run npx flowfig docs.
Boxes
| Field | Meaning | Default |
|---|---|---|
id |
The name that edges and steps use. It must be unique. | required |
label |
The title in the box. | required |
sub |
A smaller line under the label. | none |
shape |
"box", "decision" (a diamond) or "store" (a data cylinder). |
"box" |
source |
The code this draws: path or path#symbol. flowfig verify checks it. |
none |
detail |
A more detailed figure: the SVG path from the repo root. flowfig atlas links the box to it. |
none |
lines |
The least number of text lines that a content card keeps. | none |
width |
The width in px. This value replaces the width that layout picks. | from layout |
at |
In a lanes figure: the time column of the box, from 0. |
the order of first use in the steps |
from |
In a timeline figure: the start date of the item, as YYYY-MM-DD. A box with only from is a milestone. |
none |
to |
In a timeline figure: the last day of the item, as YYYY-MM-DD. |
none |
mark |
"start" draws a dot before the box. "end" draws a ring after the box. Use it on a state lifecycle. |
none |
Groups
layout is a group. A group holds boxes and other groups.
| Field | Meaning | Default |
|---|---|---|
children |
The boxes and groups in the group, in order. | required |
id |
The name that an edge can use to reach the whole group. | none |
label |
The title of the frame. Only a group with a label has a frame. | none |
direction |
"row" puts the children side by side. "column" stacks them. |
"row" |
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 |
align |
"start", "center" or "end", across the direction. |
"center" (a column stretches) |
auto |
On the root layout: true makes flowfig place the boxes. See Automatic layout. |
off |
Automatic layout
Set "auto": true on the root layout and list the boxes. flowfig places them in ranks, in the direction of the edges.
Use it when you do not want to plan rows and columns, or when check reports an edge through a box.
{
"layout": {
"auto": true,
"children": [
{ "id": "api", "label": "API" },
{ "id": "queue", "label": "Queue", "shape": "store" },
{ "label": "Workers", "children": [{ "id": "w", "label": "Worker" }] }
]
},
"edges": [
{ "from": "api", "to": "queue", "label": "enqueue" },
{ "from": "queue", "to": "w", "label": "pull" }
]
}A group with a label or an id stays together as one frame. flowfig removes a group with neither, with its direction, gap
and align. flowfig ignores around on the edges. A direction on the root forces "row" or "column". lanes and
timeline figures ignore auto.
Edges
| Field | Meaning | Default |
|---|---|---|
from |
The id of the box or group where the edge starts. | required |
to |
The id of the box or group where the edge ends. | required |
id |
The name that beats use. | from->to |
label |
The text on the edge. | none |
around |
"above", "below", "left" or "right" routes the edge around the boxes between, on that side. |
none |
quiet |
true draws the edge only while a step uses it. |
false |
source |
The code this edge draws: path or path#symbol. flowfig verify checks it. |
none |
Steps
Each step is one story. The player shows one tab for each step. With no steps, the figure is a still map.
| Field | Meaning | Default |
|---|---|---|
label |
The tab title. | required |
flow |
The beats, in play order. | required |
caption |
The line under the figure while no beat has a say. |
none |
nodes |
The ids of the boxes to highlight for the whole step. | none |
Beats
A beat is an edge id, an array of edge ids that run at the same time, or an object:
| Field | Meaning | Default |
|---|---|---|
edges |
One hop or an array of hops. A hop is an edge id or { edge, back, data, async, source }. |
none (a pause) |
say |
The line of narration for the beat. | none |
show |
{ boxId: content } fills the content card of a box until the step ends. |
none |
light |
The ids of the boxes to highlight for this beat only. | none |
ms |
The length of the beat in ms. | speed (900) |
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
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
share a parallel band on the rail.
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
required. The tones are blue (the default), purple, green, orange and gray.
Figure options
| Field | Meaning | Default |
|---|---|---|
rail |
true draws the rail under the map. "only" draws the rail alone. |
false |
lanes |
true draws swimlanes: a column group of labeled groups, one lane per role. |
off |
timeline |
true draws a timeline: one labeled group per track, with the boxes at their dates. |
off |
today |
In a timeline figure: the date of the today line, as YYYY-MM-DD. |
none |
speed |
The time in ms for a packet to cross one edge. | 900 |
theme |
{ accent, fg, muted, bg, surface, border, font }. Each key is optional. |
built-in |
autoplay |
Start to play when the figure mounts. Only the React player reads it. | true |
check |
Run the check rules in the browser. Only the React player reads it. | false |
Check a figure
flowfig check reads a spec and lists the faults. The input is - (stdin), a .json file, a .ts module or an SVG from the
CLI. A .ts module needs a Node version that strips types, such as Node 22.18 or later.
$ npx flowfig check docs/checkout.svg
0 errors, 0 warnings
figure: 5 boxes, 3 groups, 4 edges, 2 steps, 6 messagesThe last line gives the counts of the parts of the figure. Compare the counts with the parts that you planned.
flowfig check has 26 rules: 11 errors and 15 warnings.
| Rule | Severity | What it finds |
|---|---|---|
unknown-id |
error | An edge, step or beat names a box, group or edge that does not exist. |
duplicate-id |
error | Two boxes, two groups or two edges have the same id. |
hidden-edge |
error | A quiet edge that no beat uses, so the figure never shows it. |
text-overflow |
error | Text that needs more width than its box has. |
edge-crosses-box |
error | An edge that goes through a box that is not one of its ends. |
label-overlap |
error | Two edge labels overlap, or a label covers a box or an edge, or a stub label crosses a lane border. |
low-contrast |
error | A text and background pair below 4.5:1, in the light, dark or custom theme, or on a tone tint. |
lanes-need-column |
error | The layout of a lanes or timeline figure is not a column group of labeled groups. |
bad-at |
error | An at value that is not an integer of 0 or more. |
timeline-need-from |
error | A box in a timeline has no from date. |
bad-date |
error | A from, to or today value is not a real YYYY-MM-DD date, or a to is before its from. |
empty-step |
warning | A step with no beats. |
small-text |
warning | At the page width, the smallest text is below the minimum size. |
bad-source |
warning | A source that is not path or path#symbol. |
bad-detail |
warning | A detail that is not a relative path that ends in .svg. |
mark-count |
warning | A lifecycle has more than one start mark, or a start mark and no end mark. |
lane-column-taken |
warning | Two boxes in one lane share a time column. |
lane-end-block |
warning | In wrapped lanes, an edge ends at a lane that no block on its side shows. |
stub-crosses-edge |
warning | In wrapped lanes, a stub line crosses another edge. |
long-edge |
warning | An edge at least 600 px long that is 1.6 times or more the straight distance between its ends. |
timeline-dependency-order |
warning | In a timeline, an item does not start after the item that it depends on ends. |
timeline-and-lanes |
warning | The figure sets timeline and lanes. The renderers draw the timeline and ignore lanes. |
font-estimated |
warning | theme.font is set. The SVG check estimates text width for the system font. |
color-not-checked |
warning | A color that the check cannot read, so its contrast is not checked. |
plain-text |
warning | Text with a code name, a filler word, or a say or caption over 20 words. |
auto-ignored |
warning | auto with lanes or timeline, auto on a nested group, or around on an edge with auto. |
| Option | Effect |
|---|---|
--strict |
Every warning becomes an error. |
--json |
Print the findings as a JSON array, for scripts. Each finding has rule, severity, ids and message. |
--width <px> |
The page width for small-text and for the lanes wrap. Default: 830. |
--min-text <px> |
The smallest text size the reader must get. Default: 10. |
The exit code is 0 with no errors, 1 with one or more errors, and 2 for bad use, such as a missing input, an unknown flag, or a --width value that is not a number.
A render runs the same check first. If the check finds an error, the render writes nothing. --no-check skips the check.
In React, <Flow check /> runs the same rules on the layout that the browser drew. The player prints each fault with
console.warn.
Open and share a figure
flowfig open shows a figure in your default browser. On macOS, an .svg file often opens in a text editor or in Xcode. Thus
open writes an HTML page that holds the SVG, and opens that page.
npx flowfig open docs/checkout.svg
npx flowfig open docs/checkout.svg --html checkout.html # also write the page to checkout.html
npx flowfig docs/checkout.json docs/checkout.svg --open # render, then openflowfig gif writes an animated GIF of the whole loop. A GIF plays in Slack, Notion, X and slide decks, where an SVG
animation does not play.
npx flowfig gif docs/checkout.svg # writes docs/checkout.gif
npx flowfig gif docs/checkout.svg --step 2 # only step 2: writes docs/checkout-step2.gif
npx flowfig gif docs/checkout.svg --dark --mp4 # the dark theme, and also docs/checkout.mp4gif needs Chrome, Edge, Chromium or Brave in the standard install path. Set CHROME_PATH to use another browser program.
--fps sets the frames per second (1 to 50, default 20). --scale sets the pixel scale (default 2). --mp4 needs ffmpeg
on the PATH. If a GIF is over 10 MB, gif prints a warning. Use --step, a lower --fps or a lower --scale to make the
file smaller.
Verify in CI
A box, an edge or a hop can name the code it draws: "source": "src/auth/login.ts#verifyPassword". npx flowfig verify docs/login.svg
fails when the file or the symbol is gone. The action runs verify on every figure in a pull request. It comments the old and
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
changes.
verify prints one count line for each figure, and gives each edge one result:
docs/login.svg: 7 of 7 boxes defined; edges: 9 found, 0 not found, 1 unsure, 1 not checked- found: the caller code calls or references the callee through an import, the same file or a typed receiver.
- not found: the caller code does not.
verifywarns, and--strictmakes it an error. - unsure:
verifycannot decide, for example when a receiver has no declared type.verifylists it with the reason. It never fails CI. - not checked: the edge has no
source, or the language is not supported.
verify also checks detail. If the file is missing or has no figure spec, verify warns with missing-detail. --strict makes it an error.
A method source is path#Owner.name. An edge source names the function that makes the call.
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.
Edge checks support TypeScript/JavaScript, Python, Go, Java, C# and Rust. Other files keep the name check for boxes.
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.
# .github/workflows/figures.yml
name: figures
on: pull_request
jobs:
figures:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the Action reads the old figure from the base commit
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: iamalvisng/flowfig@v0.9.0
with:
figures: 'docs/**/*.svg' # default **/*.svgTrace the calls of a function
flowfig trace lists the calls that one function makes, and the calls that those calls make. Ask your coding agent to run it
before it writes a figure. Then the agent reads only the lines that the trace names. Each edge passes the same check as
verify.
$ npx flowfig trace src/verify.ts#verify
src/verify.ts#verify
104 verifyReport
src/verify.ts#verifyReport
48 emptyCoverage
49 src/source.ts#links
58 src/code.ts#outside
65 src/code.ts#codeFile
67 src/code.ts#isDefined
69 escape
70 hasHeading
79 src/model.ts#nodes
81 src/edges.ts#edgeResult
91 src/model.ts#edgeId
95 src/model.ts#toBeat
stop depth 2: 11 symbols not followed
summary: 13 symbols, 12 found, 0 unsure, 0 open, 4 calls outside the repo, 0.1 s- A header line gives a caller as
file#symbol. Each call line under it gives the line of the call, then the callee. The call is in the file of the caller. A callee in the same file shows only its symbol. unsure: trace cannot name the target. An example is a call of a parameter.open: the target is a name in a string, for example a route table entry. Read that line yourself.stop: the trace reached--depthor--max. The line gives the number of functions that it did not follow.- A call into a package or the standard library gives no line. The summary line counts these calls.
A call that trace cannot see gives no line, for example a call through an interface with no known type.
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.
| Option | Meaning | Default |
|---|---|---|
--depth <n> |
The number of call levels to follow. | 2 |
--max <n> |
The largest number of edges. Then the trace stops. | 40 |
--root <dir> |
The repo root. The file path is relative to it. | the current folder |
--json |
Print one JSON object in place of the lines. | off |
The exit code is 1 if the file or the function is not found. It is also 1 if the language is not TypeScript, JavaScript, Python, Go, Java, C# or Rust.
Find stale figures
flowfig coverage reads each figure in the repo and gives it one state. It also shows the code that has no figure. Run
it each week, or in CI.
$ npx flowfig coverage --entries 'src/geometry.ts'
docs/cached-request.svg ok 3 of 3 boxes defined
docs/checkout-rail-only.svg ok 4 of 4 boxes defined
docs/checkout.svg ok 4 of 4 boxes defined
docs/first.svg none no source links
docs/hero.svg none no source links
docs/order-status.svg none no source links
docs/refund-process.svg ok 6 of 6 boxes defined
docs/returns-process.svg ok 8 of 8 boxes defined
docs/roadmap.svg ok 8 of 8 boxes defined
uncovered src/geometry.ts: 1 of 1 files have no figure
src/geometry.ts
summary: 9 figures: 6 ok, 0 stale, 0 fail, 3 nonefail:verifyfinds an error, for example a symbol that is gone.stale:verifyfinds no error, but a linked file changed in a commit after the last commit of the SVG. A linked file with changes that are not committed counts as changed now.ok:verifyfinds no error, and no linked file changed after the SVG.none: the figure has nosourcelink.
coverage reads the commit dates from git. If the folder is not in a git repo, coverage does not check stale. It
prints one line that says so.
A figure covers a code file if one of its links names that file. With --entries, coverage lists each matched code
file that no figure covers. With no --entries, it prints one count line for each top folder.
| Option | Meaning | Default |
|---|---|---|
--figures <glob> |
The SVG files to read. Only an SVG with a figure spec counts. | **/*.svg |
--entries <glob> |
The code files that must have a figure. | none |
--root <dir> |
The repo root. | the current folder |
--strict |
Exit 1 if a figure is fail or stale. |
off |
--json |
Print one JSON object in place of the lines. | off |
coverage skips node_modules, dist and each folder with a name that starts with a dot.
One page for all figures
flowfig atlas writes one web page for each figure, and an index page. A box can link to a more detailed figure with
detail. The path starts at the repo root, as source does.
docs/system.svg:
{
"layout": {
"children": [
{ "id": "up", "label": "Whole system", "detail": "docs/flows/orders.svg" },
{ "id": "db", "label": "Orders table" }
]
},
"edges": []
}docs/flows/orders.svg:
{
"layout": {
"children": [
{ "id": "web", "label": "Web app" },
{ "id": "orders", "label": "Order service", "detail": "docs/system.svg" }
]
},
"edges": []
}docs/system.svg links down to the order flow. docs/flows/orders.svg links back up.
$ npx flowfig atlas
atlas/index.html — 2 figure pages| Option | Meaning | Default |
|---|---|---|
--figures <glob> |
The SVG files to read. Only an SVG with a figure spec counts. | **/*.svg |
--root <dir> |
The repo root. detail paths start here. |
the current folder |
--out <dir> |
The folder to write. | atlas |
--open |
Open index.html in the default browser. |
off |
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.
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
written, whatever the health of the figures. It is 1 when no figure matches, and 2 for a bad flag.
The index lists each figure with its health from coverage: ok, stale, fail or none. A figure with a bad spec
has no page. Its row shows fail and the error.
Each figure page has these parts, in this order:
- A link "All figures".
- The figure path and its health.
- The figure.
- A "Details" list with one link for each box that has
detail. - The transcript of the figure.
A link works in these places:
- In the README, an SVG is an image, so a box has no link. The README render does not change.
- On an atlas page, a click on a box opens the page of its
detailfigure. The mouse is the only way to use this link. - The "Details" list has the same links with names. Use it with the keyboard and a screen reader.
- If the target figure is not in the atlas, the box has no link. The list row says "not in the atlas".
The atlas draws each spec with the installed flowfig and the default options. A page can differ from the committed SVG
if you drew that SVG with --width or --min-text. A figure that you remove keeps its old page in --out.
To publish the pages on GitHub Pages, run npx flowfig atlas --out docs/atlas and commit the result. Then set Pages to
"Deploy from a branch" with the folder /docs.
MCP server
npx flowfig mcp serves the tools docs, check, render, verify and diff over stdio, for an agent with no shell.
render writes the SVG file and returns the check lines and the path, so no SVG text goes through the model. docs takes an
optional topic.
{ "mcpServers": { "flowfig": { "command": "npx", "args": ["flowfig", "mcp"] } } }Use in React
import { Flow, type FlowProps } from 'flowfig';
import spec from './first.json';
export const LoadUser = () => <Flow {...(spec as FlowProps)} />;The player has these controls:
- One tab for each step, with a progress line. A click on a tab starts that step.
- A pause button, and a speed button that changes between 1× and 2×.
- A full screen button. Esc closes full screen.
- 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.
- A hover on a box, an edge or a rail row with a
sourceshows thesource. The SVG shows it as a tooltip too.
The player props are the spec fields. theme sets the colors, speed sets the packet time, and autoplay={false} stops the
auto start. check runs the check rules. If a figure is wider than its container, the player shrinks the figure to half size at
most, then scrolls. If the reader asks for reduced motion, the player shows the last beat and does not move packets.
The player has no dark theme of its own. For dark mode, set the --fig-* CSS variables on .flowfig:
@media (prefers-color-scheme: dark) {
.flowfig {
--fig-accent: #1f78c8;
--fig-fg: #e3e3e3;
--fig-muted: #9aa0a6;
--fig-bg: #1b1b1d;
--fig-surface: #242526;
--fig-border: #3a3b3c;
}
}These are the built-in dark colors. flowfig exports them as DARK, and the light colors as LIGHT.
Use from Node
flowfig/svg needs no React and no browser.
import { readFileSync, writeFileSync } from 'node:fs';
import { check, toSvg } from 'flowfig/svg';
const spec = JSON.parse(readFileSync('first.json', 'utf8'));
for (const f of check(spec)) console.log(f.severity, f.rule, f.message);
writeFileSync('first.svg', toSvg(spec));toSvg(spec, options)returns the SVG string. The options arespeed,padding(default 24) andtheme.check(spec, options)returns the findings. It also takeswidthandminText.render(spec, options)returns{ svg, scene }. The scene is the layout that the check reads. Its shape can change.
flowfig/verify is for Node only. It exports verify, verifyReport (the findings and the counts), links, owners and parseSource, the same checks
as flowfig verify.
toSvg does not put the spec in the SVG. Only the CLI adds the spec, which --spec reads back.
Use with your coding agent
npx flowfig init writes flowfig instructions for the coding agents of a repo. The instructions tell the agent how to write a
spec, check the spec, and render the SVG.
npx flowfig init # the current directory
npx flowfig init path/to/repoOn a terminal, init shows a picker. The picker selects the agents that the repo already uses. If it finds none, it selects
AGENTS.md. Type the numbers to toggle agents, press Enter to write, or type q to quit.
| Flag | Effect |
|---|---|
--agents <ids> |
Write for these agents, for example --agents claude,cursor. |
--all-agents |
Write for all 7 agents. |
-y, --yes |
Write for the agents that the repo uses, with no picker. |
--dry-run |
Print what init would write, and write nothing. |
--no-mcp |
Do not register the MCP server. |
--list-agents |
Print the agent ids. |
With no terminal and no -y, --agents or --all-agents, init prints the agent ids and exits with code 2.
A section sits between <!-- flowfig:start --> and <!-- flowfig:end -->. A second run replaces that section and keeps the rest
of the file. A second run also replaces a whole file that init wrote. If a whole file exists and did not come from init,
init skips the file. init also skips a section file that has a start marker and no end marker.
npx flowfig docs prints the core guide as Markdown. npx flowfig docs <topic> prints one topic: lanes, timeline, rail,
marks or verify.
What flowfig does not draw
flowfig draws parts, the messages between the parts, and their order. flowfig draws a roadmap or a timeline with dates (timeline: true),
and converts a Mermaid gantt to it. flowfig does not draw class diagrams, ER diagrams, charts of numbers or mind maps. Use Mermaid
or a chart library for those.
License
MIT. See LICENSE.
Development
npm install
npm run dev # the gallery: each file in figures/ is one figure
npm test # build, then run the unit tests
npm run typecheck
npm run format:checkA change to the layout, the routing or the SVG output needs a unit test in src/*.test.ts.