Diagrams
WorkflowFigure
A process in lanes and phases, with the main path drawn heavier than its branches.
Usage
When to reach for it
Ships from@misoto22/design/diagrams
import { WorkflowFigure } from '@misoto22/design/diagrams'Notes
A process: who does what, in what order, and where it can go wrong.
Three structures stacked on one grid, and they are three because they answer three different questions a reader brings to a runbook.
Lanes are rows and answer WHO. A lane marked exception is the one band that gets a wash rather than a rule, and it earns the exception to the frames-are-rules law because nothing routes across it — it IS the ground for the failure path, not a frame drawn over the happy one.
Phases are columns across every lane and answer WHEN. Printed as captions on one header rule, because a phase is an axis label. Groups are frames inside a single lane and answer WHAT BELONGS TOGETHER — a planning loop, an evidence path.
THE MAIN PATH IS HEAVIER, and that is the whole hierarchy. mainPath lists the node ids a reader should be able to follow without thinking. Every edge between two consecutive ids on it is drawn at the emphasis weight whatever its own variant says, which is the one thing that turns fourteen boxes and nineteen arrows into a diagram with a subject. An error role goes the other way — dashed and soft — so the exception path recedes without disappearing, and so does async. branch adds no stroke of its own: a branch is an edge the main path does not list, and that is already the difference on the page.
Anatomy
| Element | Description |
|---|---|
| Figure shellrequired | The same frame every figure sits in. What a process hands it is a key of LINE variants rather than of node kinds — Call, Primary path, Crosses a trust boundary, Asynchronous — because a runbook is read for its arrows and not for its boxes. |
| Lanerequired | A 92-unit band per lane with its name set in the 118-unit gutter to its left. The band spans only what the lane actually holds, and a lane marked exception is washed rather than ruled — the one wash in the package’s diagrams, because nothing routes across it. |
| Columnrequired | What col indexes: a 168-unit column with a 58-unit gutter, the same across every lane. A node sits centred in its lane’s depth, moved off that centre line only by yOffset. |
| Phase header | A mono caption at the x of its fromCol, over a rule drawn across the columns from fromCol to toCol — so a phase’s extent is something a reader can see rather than a field only its author knows. variant dashes that rule for security and thickens it for emphasis. A phase is an axis label, so it is set like one rather than framed. |
| Group frame | A labelled rule around a run of columns inside ONE lane — a planning loop, an evidence path — dashed when its variant is security. |
| Main path | mainPath, drawn as weight rather than as a mark of its own: every edge between two consecutive ids on it goes heavier whatever its own variant says. |
Best practices
Do
- Give every node a lane that spec.lanes declares. An unknown lane id still resolves to the first lane — there is nowhere else to put the box — so a step written for the exception band is drawn in the right column, at the right size, in the wrong row, and development prints DIAGRAM_LANE_UNKNOWN naming the node.
- Write mainPath as the path in order. The heavier weight is applied to CONSECUTIVE pairs, so [intake, review, ship] emphasises intake→review and review→ship and nothing else; an id whose neighbour on the list is not the far end of a real edge changes nothing at all.
- Say what an edge MEANS with role and let the drawing follow it. async and error take the quiet dashed line, return takes an open arrowhead, and main and branch add no stroke of their own because mainPath already draws that distinction as weight. An explicit variant overrides all of it, which is what it is for.
Don’t
- Phases partition the columns; they do not stack. Each is drawn as a rule across its own span, so two phases claiming the same columns put two rules on one line and one caption over the other — and a reader cannot tell that from one phase drawn twice.
- An empty lane is not a spacer. The band is sized from what the lane holds, so a lane with no nodes collapses to about one column beside the gutter with its name still set — and an exception lane in that state washes a strip of ground next to the figure rather than under the failure path.
Examples
default
A release pipeline with the failure path in a lane of its own. mainPath is what gives the figure a subject: every edge between two consecutive ids on it is drawn at the emphasis weight whatever its own variant says, so a reader following the heavy run gets the green path without reading a word. The exception lane is the one washed band in these diagrams, and the rollback edge is dashed and soft on purpose — a failure path drawn at the weight of the happy one is a runbook nobody can skim.
Release
4 elements and 3 relationships. Green tests ship; a red deploy rolls back.
- Lane: CI
- Lint (backend) — eslint
- Tests (backend) — vitest
- Deploy (cloud) — Cloudflare
- Exception lane: Recovery
- Roll back (security) — previous build
- Lint → Tests
- Tests → Deploy: green
- Deploy → Roll back: health check failed
- Phase Verify covers columns 0 to 1: Lint, Tests.
- Phase Ship covers columns 2 to 2: Deploy, Roll back.
a branch and both outcomes
One check with two outcomes, which is the shape most of a workflow is. The heavy run through Checks to Merge is mainPath, and the branch that leaves it is dashed — but the key names a dashed line Asynchronous by default, so this spec renames that entry rather than let the key contradict the picture. The edge back from the author carries role return, the one role that changes how a line is drawn: an open arrowhead, so a reader sees the loop close without having to trace it.
A pull request
4 elements and 4 relationships. One gate, two ways out, and the way back in.
- Lane: Automation
- PR opened (frontend) — branch pushed
- Checks (backend) — lint and tests
- Merge (cloud) — squash
- Lane: Author
- Changes requested (external) — review comment
- PR opened → Checks
- Checks → Merge: all green
- Checks → Changes requested: a check failed
- Changes requested → Checks: pushed a fix
- Group Merge gate covers columns 1 to 2 in lane Automation: Checks, Merge.
three structures on one grid
Lanes answer who, the phase captions on the header rule answer when, and the group frame answers what is in scope — three structures on one grid, because a runbook gets read for all three questions and by three different people. The frame is dashed because that group carries variant security, which is the one claim on the figure a reader can check without tracing an arrow. The receipt leaves the main path and is drawn dashed rather than solid, which is the whole difference between an email that may be late and a shipment that may not.
Order fulfilment
5 elements and 4 relationships. Three teams, three stages, one hop nobody waits for.
- Lane: Storefront
- Checkout (frontend) — web
- Receipt email (messagebus) — queued
- Lane: Fulfilment
- Pick and pack (backend) — warehouse
- Lane: Finance
- Authorise (security) — card network
- Capture (backend) — settled
- Checkout → Authorise: card token
- Authorise → Capture: approved
- Capture → Pick and pack: paid
- Capture → Receipt email: confirmation
- Phase Take covers columns 0 to 0: Checkout.
- Phase Settle covers columns 1 to 2: Receipt email, Authorise, Capture.
- Phase Ship covers columns 3 to 3: Pick and pack.
- Group PCI scope covers columns 1 to 2 in lane Finance: Authorise, Capture.
- Only the two boxes inside the dashed frame see a card number.
- The receipt is queued, so a slow mailer cannot hold up a shipment.
what a role draws
Four edge roles on one deploy, each paired with the variant that draws it. role says what a line MEANS and the renderer reads exactly one of its values — return, for the open arrowhead — so branch, async and error change nothing about the stroke on their own. An error edge left at the default variant is drawn at the weight and the solidity of the happy path, which is the one thing a runbook must not do. A lane is sized to what it holds, too, so an empty one does not act as a spacer — it collapses to about a column beside the gutter with its name still set.
Canary deploy
5 elements and 5 relationships. One path forward, three ways off it.
- Lane: Pipeline
- Build (backend) — container image
- Canary (cloud) — 5% of traffic
- Full rollout (cloud) — all regions
- Lane: Out of band
- Announce (messagebus) — #deploys
- Exception lane: On call
- Page the on-call (security) — PagerDuty
- Build → Canary
- Canary → Full rollout: healthy for 10m
- Canary → Announce: posts the diff
- Canary → Page the on-call: errors above 2%
- Page the on-call → Build: previous image restored
Accessibility
- Same contract as every figure: a named picture, with its nodes and relationships published as text beside it.
- The exception lane is the one washed band in the system’s diagrams, and it is still labelled — the wash is not carrying the meaning on its own.