Turn a spoken or written description of a workflow into a swimlane process diagram: a single self-contained HTML file with inline SVG, dark theme, and built-in PNG/PDF export.
Version 1.0 · Visual shell adapted from the MIT-licensed architecture-diagram skill by Cocoon AI.
Scope: processes (things that happen over time, performed by someone), not
system topology. For components and infrastructure use architecture-diagram instead.
Part 1 — Elicitation (do this first)
People describe the happy path and forget the rest. The forgotten parts are the process. Never start drawing from the first description. Ask these five questions, in one message, and wait for answers:
- Trigger — what starts the process and who launches it?
- Participants — who takes part in it, including external ones (client, contractor, bank, government agency)? Who owns the process as a whole?
- Steps — what happens in order? Where does a human work, and where does the system?
- Branches — where can the process go differently: rejection, return for revision, exception?
- Pain — where does it most often get stuck, lose time, or get redone entirely?
Skip the questions only if the user has already answered them, explicitly says "just draw what I said", or is iterating on an existing diagram.
Two follow-ups that repeatedly surface hidden work — ask them when the answers are thin:
- "And what happens if at this step there's a rejection / error / no response?"
- "Who finds out that a step is completed, and how?" (missing notifications are invisible handoffs)
If the user gives durations, costs, or frequency for steps, capture them — they go on the diagram and make it actionable. If they don't, ask once for the two or three slowest steps only. Do not block on metrics.
Naming
Steps are named verb + object, in the user's language: "Approve the estimate", "Issue the invoice". Not "Estimate" and not "Approval process". A step whose name is a noun is usually a missing step or a document, not an action.
Part 2 — Visual grammar
A process encodes on three axes at once. Keep them separate and the diagram reads itself; mix them and it becomes a coloured mess.
| Axis | Encodes | Rule |
|---|---|---|
| Horizontal position | time | strictly left → right, fixed column pitch. Same column = same moment. |
| Lane (row) | who does it | one horizontal band per actor |
| Shape | kind of node | capsule / rect / diamond / pill |
| Colour | degree of automation + pain | see palette |
Because the lane already says who, colour is free for the thing that matters most: what is manual and what is broken.
Colour palette
| Node kind | Fill (rgba) | Stroke |
|---|---|---|
| Start / End event | rgba(8, 51, 68, 0.5) |
#22d3ee (cyan-400) |
| Manual step (human by hand) | rgba(120, 53, 15, 0.35) |
#fbbf24 (amber-400) |
| Automated step (system) | rgba(6, 78, 59, 0.4) |
#34d399 (emerald-400) |
| External step (client, contractor, bank) | rgba(30, 41, 59, 0.6) |
#94a3b8 (slate-400) |
| Decision | rgba(76, 29, 149, 0.4) |
#a78bfa (violet-400) |
| Wait / delay | rgba(251, 146, 60, 0.22) |
#fb923c (orange-400) |
| Bottleneck (any kind, overrides) | rgba(136, 19, 55, 0.45) |
#fb7185 (rose-400), stroke-width="2.5" |
Bottleneck is an overlay, not a category: a slow manual step is drawn rose, not amber. Mark a step as a bottleneck only if the user said it hurts, or its own metric makes it obvious (an order of magnitude slower than its neighbours). Never more than three bottlenecks on one diagram — if everything is red, nothing is.
Typography
JetBrains Mono throughout:
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
Sizes: 11px step name, 9px lane label and sublabel, 8px metrics and arrow labels, 7px tiny.
Do not put emoji in SVG <text> — html2canvas renders them inconsistently across
platforms and they break the PNG/PDF export. Use words or plain glyphs (·, !, →).
Background
#020617 (slate-950) with a grid, same as the architecture skill:
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#1e293b" stroke-width="0.5"/>
</pattern>
Part 3 — Geometry
These numbers are the whole reason the output looks deliberate. Use them literally; change them only proportionally.
Lane label gutter : x 0 → 150
Lane area : x 150 → viewBox width
Phase band : y 20 → 58
Loop channel : y 58 → 90 (reserved for backward/rework arrows)
First lane top : y 90
Lane height : 110
Column pitch : 180
Step box : 140 × 56, rx="6"
Derived, for a lane whose vertical centre is cy:
step box top = cy - 28
capsule top = cy - 20 (capsule is 110 × 40, rx="20")
metrics text = cy + 42 (still inside the 110px band)
column i left = 300 + i * 180
Lane centres: cy(n) = 90 + 110*n + 55 → 145, 255, 365, 475, …
Lane bands
Draw bands before anything else, after the grid. Alternate the fill so rows are countable, and separate them with hairlines:
<rect x="0" y="90" width="1200" height="110" fill="rgba(15, 23, 42, 0.55)"/>
<rect x="0" y="200" width="1200" height="110" fill="rgba(15, 23, 42, 0.25)"/>
<line x1="0" y1="200" x2="1200" y2="200" stroke="#1e293b" stroke-width="1"/>
<text x="14" y="149" fill="#94a3b8" font-size="9" font-weight="600">КЛИЕНТ</text>
Order lanes by how much of the process they own, most-active lane near the top, external parties at the edges. Do not exceed 6 lanes — beyond that, split the process into two diagrams and link them by a shared start/end event.
Node patterns
Step (with opaque mask so arrows do not bleed through the translucent fill):
<rect x="480" y="227" width="140" height="56" rx="6" fill="#0f172a"/>
<rect x="480" y="227" width="140" height="56" rx="6" fill="rgba(120, 53, 15, 0.35)" stroke="#fbbf24" stroke-width="1.5"/>
<text x="550" y="250" fill="white" font-size="11" font-weight="600" text-anchor="middle">Рассчитать смету</text>
<text x="550" y="266" fill="#94a3b8" font-size="9" text-anchor="middle">вручную в Excel</text>
<text x="550" y="297" fill="#64748b" font-size="8" text-anchor="middle">4 ч · 30/мес</text>
Start / End event — capsule, cyan, one word or a short noun phrase:
<rect x="160" y="125" width="110" height="40" rx="20" fill="#0f172a"/>
<rect x="160" y="125" width="110" height="40" rx="20" fill="rgba(8, 51, 68, 0.5)" stroke="#22d3ee" stroke-width="1.5"/>
<text x="215" y="149" fill="white" font-size="10" font-weight="600" text-anchor="middle">Заявка</text>
Decision — diamond, half-diagonal 39, centred on the column centre and lane centre. The question goes inside only if it fits in ~14 characters; otherwise put it above:
<polygon points="730,106 769,145 730,184 691,145" fill="#0f172a"/>
<polygon points="730,106 769,145 730,184 691,145" fill="rgba(76, 29, 149, 0.4)" stroke="#a78bfa" stroke-width="1.5"/>
<text x="730" y="142" fill="white" font-size="9" font-weight="600" text-anchor="middle">Согласо-</text>
<text x="730" y="153" fill="white" font-size="9" font-weight="600" text-anchor="middle">вано?</text>
Every outgoing branch is labelled ("yes" / "no" / "over 100k"). An unlabelled branch out of a decision is a defect.
Wait / delay — pill, orange, named as a state not an action ("Awaiting payment"):
<rect x="1020" y="117" width="140" height="56" rx="28" fill="#0f172a"/>
<rect x="1020" y="117" width="140" height="56" rx="28" fill="rgba(251, 146, 60, 0.22)" stroke="#fb923c" stroke-width="1.5"/>
<text x="1090" y="150" fill="white" font-size="11" font-weight="600" text-anchor="middle">Ожидание оплаты</text>
<text x="1090" y="187" fill="#fb923c" font-size="8" text-anchor="middle">3 дня в среднем</text>
Waiting is where calendar time actually goes. Draw it as a node whenever the user mentions "we're waiting", "until they respond", "after approval" — do not fold it into an arrow.
External participant step — same box as any step, slate fill, and a sublabel saying where the work happens. Draw the steps your counterparties do: work you do not control is still work your process waits on, and it is where drop-off hides.
<rect x="840" y="117" width="140" height="56" rx="6" fill="#0f172a"/>
<rect x="840" y="117" width="140" height="56" rx="6" fill="rgba(30, 41, 59, 0.6)" stroke="#94a3b8" stroke-width="1.5"/>
<text x="910" y="140" fill="white" font-size="11" font-weight="600" text-anchor="middle">Выполнить тестовое</text>
<text x="910" y="156" fill="#94a3b8" font-size="9" text-anchor="middle">на своей стороне</text>
<text x="910" y="187" fill="#64748b" font-size="8" text-anchor="middle">3-5 дней · отваливается 30%</text>
Bottleneck badge — centred on the box's top-right corner, half outside it
(cx = x + width, cy = y). Inside the box it collides with the step name:
<circle cx="620" cy="227" r="8" fill="#fb7185"/>
<text x="620" y="231" fill="#0f172a" font-size="11" font-weight="700" text-anchor="middle">!</text>
Subprocess — a normal step plus a second vertical line inset 5px from the left
edge, and the sublabel подпроцесс.
Arrows
One <defs> marker per colour used; arrows are drawn after the lane bands but
before the node boxes, so they pass behind them.
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#64748b"/>
</marker>
Same lane, forward — a straight line at the lane centre, ending 6px before the next box:
<line x1="440" y1="255" x2="474" y2="255" stroke="#64748b" stroke-width="1.5" marker-end="url(#arrow)"/>
Lane change — an orthogonal path with 8px rounded corners, never a diagonal. The
vertical segment sits in the gap between columns, at column_left - 15:
<path d="M 620 255 H 640 Q 648 255 648 247 V 153 Q 648 145 656 145 H 685"
fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#arrow)"/>
Corridor check — before drawing any vertical segment, look at what it passes through. A vertical crossing an intermediate lane must run through empty space: it must not cut a box, and it must not cut the metrics line under a box (which sits 28px below the box and is easy to forget). Two steps in the same column must never be connected to each other — same column means same moment, so a dependency between them is a contradiction; give the later one its own column. A start or end event may share a column with the step it attaches to: it marks an outcome, not a unit of work.
Handoff marker — the single most useful mark on the diagram. Where an arrow crosses into the destination lane, draw a ring on the boundary:
<circle cx="648" cy="200" r="5" fill="#0f172a" stroke="#fb923c" stroke-width="1.5"/>
Every actor-to-actor transition gets one. They are where a process loses time and where responsibility silently evaporates; the count of rings is a metric in itself, and worth stating in the summary cards.
Parallel work — two arrows leaving one node with no labels mean the branches run at the same time. Exclusive choice always goes through a labelled diamond, so an unlabelled split is never ambiguous. Mark the split with a double bar drawn as two lines (not a text glyph — at this size a glyph disappears):
<line x1="1170" y1="340" x2="1170" y2="354" stroke="#94a3b8" stroke-width="1.5"/>
<line x1="1176" y1="340" x2="1176" y2="354" stroke="#94a3b8" stroke-width="1.5"/>
Give each branch its own vertical segment 14px apart inside the column gap, or they overlap. The join is simply both arrows entering the same next node — that node cannot start until the slower branch finishes, which is exactly what makes a parallel branch worth drawing: it shows which branch sets the clock. Name it in the summary cards ("security check blocks the offer").
Terminating branch — a branch that ends the process early (rejection, cancellation) gets its own end capsule, placed in the lane of whoever learns the outcome, not the lane of whoever decided. That is usually the client or the candidate, and putting it there makes visible that someone has to be told. An early exit with no capsule reads as a dead arrow.
Backward / rework loop — dashed rose, routed through the loop channel at y=70
above all lanes, never through the lane bands. It starts at the top edge of its
source node (cy - 28 for a step, cy - 39 for a diamond) — anchor it anywhere
else and the dashed line floats in a neighbouring lane, attached to nothing:
<path d="M 730 106 L 730 78 Q 730 70 722 70 L 558 70 Q 550 70 550 78 L 550 221"
fill="none" stroke="#fb7185" stroke-width="1.5" stroke-dasharray="5,5" marker-end="url(#arrow-rose)"/>
<text x="640" y="64" fill="#fb7185" font-size="8" text-anchor="middle">нет · на доработку</text>
If two rework loops would overlap, put the second at y=78 and shorten the channel
arithmetic accordingly, or move it below the lanes into a mirrored channel.
Arrow labels — 8px, #94a3b8, centred, 6px above a horizontal segment. Label an
arrow only when it carries information the node names don't: a branch condition, a
channel ("by phone", "in 1C"), or a duration.
Phase band (optional)
When the process has obvious stages, mark them above the lanes — it is cheap and makes long diagrams navigable:
<text x="550" y="40" fill="#64748b" font-size="9" font-weight="600" text-anchor="middle">ПРОДАЖА</text>
<line x1="820" y1="26" x2="820" y2="530" stroke="#1e293b" stroke-width="1" stroke-dasharray="3,5"/>
The separator runs the full height of the lane stack, dashed and faint, so it never
competes with the flow. Put it in a column gap that no arrow uses — a separator
sitting on top of a vertical arrow segment reads as part of the flow. Arrow verticals
live at column_left - 15, so column_left + 5 is always clear.
Canvas size
viewBox width = 300 + columns * 180 + 40
viewBox height = 90 + lanes * 110 + 130 (130 = legend block below the lanes)
The legend goes below the last lane band, at least 30px clear of it. Extend the viewBox rather than squeezing it in — same rule as the architecture skill.
Hard cap: 6 columns, viewBox width 1420. The SVG scales to the width of its container, so every column added shrinks the type. At 8 columns the 11px step names render smaller than the body text around them and the diagram stops being readable — it is still correct and still useless.
A longer process is split, not squeezed. Cut it at a phase boundary into two
diagrams and chain them through a shared event: the end capsule of the first
("Offer sent") is the start capsule of the second. Say so in the subtitle
(часть 1 из 2). Splitting also tends to be the honest answer — a process that
does not fit in six steps is usually two processes with one owner each.
Part 4 — Layout of the page
- Header — process name, pulsing dot, subtitle line
владелец · триггер · частота, export toolbar - Diagram — the SVG in a rounded bordered card
- Summary cards — exactly three, and always these three:
- Bottlenecks (rose dot) — the bottlenecks, each with its cost in time
- Handoffs and waits (amber dot) — handoff count, wait nodes, where the ball drops
- What to automate (emerald dot) — the manual steps ranked by payoff
- Footer —
Процесс · владелец · дата · версия
The three cards are not decoration: they are the answer to "what to do about this". Fill them from the diagram, never with generic advice. If there is nothing real to put in a card, say so in it ("no bottlenecks identified") rather than inventing one.
Export toolbar
Keep the toolbar from resources/template.html intact — a ⋯ toggle revealing
Copy / PNG / PDF, all three going through the same html2canvas capture. Specifically
preserve:
- the two pinned CDN scripts in
<head>with their Subresource Integrity hashes andcrossorigin="anonymous"(do not edit the hashes; a version bump needs a fresh hash) id="report-container"on the outer.container.toolbarmarkup, its CSS, and@media print { .toolbar { display: none !important; } }copyAsImage(),downloadPNG(),downloadPDF()
Caveats: the clipboard API needs a user gesture and a secure context (https/file/localhost);
SVG <foreignObject> renders unreliably in html2canvas, so stick to plain shapes and
<text>; raise scale: 2 to 3 for print-resolution output.
Part 5 — Output
Produce a single self-contained .html file: embedded CSS, inline SVG, no external
images, no dependencies beyond Google Fonts and the two pinned CDN scripts. It must
render correctly opened straight from disk.
Language: labels, cards, and footer follow the language the user described the process in. Default to Russian. Keep the SVG element ids and CSS class names in English.
Start from resources/template.html (the pattern library — one of every node type,
arrow, and marker) and use resources/example.html as the quality bar for a finished
diagram.
Before returning the file, verify
- every step name is verb + object
- every decision branch is labelled
- every actor-to-actor arrow has a handoff ring on the lane boundary
- no box overlaps another; no arrow crosses a box or a metrics line
- no arrow connects two steps that share a column (events may)
- rework loops are in the loop channel, not across the lanes
- at most three bottlenecks, and each one is something the user actually reported
- at most 6 columns; a longer process is split, not squeezed
- every rework loop starts at the top edge of its source node
- every early exit ends in its own capsule, in the lane of whoever is told
- the legend is below the last lane band, inside an extended viewBox
- the three summary cards say something specific about this process
- no emoji inside the SVG