Skip to content
Dave Klein

Flow diagram

Turns a boxes-and-arrows sketch of a system into an animated canvas diagram whose pulses run the loop in the order the real code does, then records and verifies it. Flow diagram is one of the Claude Code skills I wrote for building this site, with the site-specific values turned into placeholders and examples so it works on any project. The skill is the SKILL.md below, exactly as it ships in the zip.

Install

unzip flow-diagram.zip -d ~/.claude/skills

That installs it for every project. For one project, unzip into that project’s .claude/skills/ instead. Either way, Claude Code reads the folder on its next session and applies the skill when a request matches its description:

Build an animated architecture, pipeline, request-flow or data-flow diagram for any React or Next.js site, driven by a small spec and a bundled canvas engine, with hairline boxes, quiet wires and accent-coloured pulses that run the loop in the order the real system does. Use this whenever someone asks for a diagram of how a system, server, device, pipeline, sync, deploy, webhook or request loop works, wants "red dots" or pulses moving between boxes, wants an animated version of a boxes-and-arrows sketch for a post, docs page or case study, or asks to change, re-time, verify or record an existing FlowMotion diagram, even if they only say "diagram" or "show how it works".

Inside the zip

  • —flow-diagram/SKILL.md · 8 KB
  • —flow-diagram/assets/flow-motion.tsx · 27 KB
  • —flow-diagram/references/language.md · 4 KB
  • —flow-diagram/references/spec.md · 10 KB
  • —flow-diagram/scripts/record-loop.sh · 3 KB
  • —flow-diagram/scripts/timeline.py · 3 KB
  • —flow-diagram/scripts/zoom.py · 1 KB

The skill

Animated diagrams built with this skill are specs for one engine, FlowMotion, so every diagram on a site shares the same drawing and the same rules: every node the same hairline box, wires with no arrowheads, the accent spent only on what is happening now. Your job is to write a truthful spec, wire it into a page, and verify it the way a careful reviewer would: zoomed in, frame by frame, and as a recorded loop they can watch.

0. Install the engine once

The engine is assets/flow-motion.tsx in this skill: one client component, about 500 lines, React 18 or later, no other dependencies and no utility framework (its few styles are inlined). Copy it into the project’s components folder, for example src/components/motion/flow-motion.tsx, and leave it unmodified apart from the two tables described below.

Colours and the mono font are read live from five CSS custom properties on the wrapper: --flow-fg, --flow-border, --flow-bg, --flow-accent, --flow-mono. Map them to the site’s own tokens once in global CSS, so dark mode and theme switches come free:

:root {
  --flow-fg: var(--color-fg);
  --flow-border: var(--color-border);
  --flow-bg: var(--color-bg);
  --flow-accent: var(--color-accent);
  --flow-mono: var(--font-mono);
}

Or pass them per diagram through the tokens prop, as literals or var() references. Anything unset falls back to the DEFAULTS in the file (near-black, light grey, white, a red). Find the site’s real token names first; do not guess them.

1. Learn the real sequence before drawing anything

The dots are not decoration. A reader takes the loop as a claim about how the system works, and the first thing a reviewer who knows the system will reject is a loop that runs in the wrong order (a diagram that opened with a side widget’s push was sent back because the widget does not start the process). So before writing beats, read the code that runs the thing: the request handler, the cron, the firmware’s call order, the CI config. Write the sequence down as numbered steps with who initiates each one, and decide what to fold away. Folding is allowed when the honest version misleads (two responses in a row read as the image arriving twice), but say so in a comment in the spec.

Distinguish pushes from pulls. A pull is a round trip: the puller’s pulse goes out and comes back, and the far node flashes as it is read. A push is a one-way pulse that lands. Things that happen on their own clock (a widget posting every 15 minutes) go in the loop’s rest period, after the main sequence, so they never read as the trigger.

2. Write the spec

Read references/spec.md for the FlowSpec type and two worked examples (a dashboard’s request loop and a deploy pipeline). In short: nodes with a lowercase mono label, a mark and a column; wires between node ids (direction does not matter, pulses can run either way); beats with loop-fraction windows.

Marks. Two ship with the engine: vercel (a triangle drawn as geometry) and device (a 12-pixel bitmap of a small e-paper frame). Everything else you add:

  • A brand goes into the engine’s BRAND table as a filled path on a 24-grid plus an optical size ({ path: 'M12 ...', size: 11 }). simple-icons (simpleicons.org) is the place to find the path; check the brand’s guidelines before shipping it. Optical size, not bounding box: a chunky square mark sits at 9 in the 12 slot, a round disk at 11, a sparse starburst at 12. Judge against the other marks in a zoomed crop.
  • A piece of hardware is a 12-row bitmap of # and . passed through bitmapMark(), measured off a product photo so the details (screen, grille, button, lid) sit where they really are. Pass it as the node’s mark straight from the spec file, or add it to the engine’s DRAWN table to give it a name.
  • Never use an outline icon next to the filled marks: one family, all filled.
  • Actors that are neither a brand nor a device you have a bitmap for (a person pushing, a build step) are fine as label-only boxes; leave mark off rather than inventing an icon.

Timing: pulses should cross wires at roughly the same speed everywhere, so give longer wires longer windows. Leave a rest at the end of the loop. The e-paper refresh is about 7.4 seconds long; a loop that ends with it needs a period around 18 seconds, and a guard like the one in the examples (LAND + refreshSeconds() / PERIOD must stay below the next beat).

Put the spec in a 'use client' file next to the project’s other media components and export a wrapper component that renders <FlowMotion spec={...} />. For an MDX post, register the wrapper in the MDX component map inside the layout’s wide-media container and place the tag in the MDX; for a page component, import the wrapper directly.

The loop starts at its first beat whenever the canvas first runs and whenever a flow-restart event is dispatched on it, so a reader scrolling to the diagram meets the story from the top. Order the beats so the first one is the true trigger. Choose still (the frame shown under reduced motion) at a moment with one pulse mid-wire.

3. Verify like a reviewer

Run the project’s typecheck first (npx tsc --noEmit). Then, with the page served (a dev server is fine), use the scripts in scripts/. They need agent-browser, ffmpeg and Python 3 with Pillow; each prints its usage with --help.

  1. scripts/record-loop.sh <page url> "<start of aria-label>" <out.mp4> <period> records one loop with agent-browser, rewinds the canvas as it scrolls into view, crops the video to the drawing and writes the MP4 plus a frames/ folder of 1x PNGs.
  2. scripts/timeline.py frames/ --accent '#c22' prints one character per tenth of a second for where the accent appears (left, middle, right third of the canvas) and the colour state of a probe point, so you can read the beat order off the recording instead of trusting the spec. Pass your accent colour; the default probe sits just inside the rightmost box, which suits a single device in the last column, and --probe x,y moves it.
  3. scripts/zoom.py <screenshot.png> <x,y,w,h> zoom.png makes a 3x nearest-neighbour crop for checking marks and box edges. Look at it. Box edges must be one pixel wide, marks must read as the same weight, the bitmap marks must be on whole pixels. agent-browser screenshots are 1x, which is the harder case for edges, so a clean 1x crop is enough.

In the timeline, a held flash on a middle box and a pulse crossing that third both print as p; tell them apart by the frames or by the probe, or move the probe to the box you care about.

Send the MP4 along with a one-line caption naming the beats in order. Expect notes on mark size, pixel alignment and sequence truth; those are the three things a reviewer always checks.

4. The language, briefly

Why the engine draws what it draws, so a spec does not fight it: every node is the same hairline box, because size and fill carrying meaning was the first thing stripped out. Wires are quiet border-colour curves with no arrowheads, because pulses run both ways on the same wire and arrowheads then lie. The accent is spent only on pulses and the box a pulse reads or lands in, so it always means “happening now”. A frame landing on an e-paper device is the real multi-second refresh, not a polite flash. Details and their reasons are in references/language.md; read it before changing the engine.