Excalidraw whiteboards, Sean's way
Boards are generated from code so they match the masters in ~/Developer/Excalidraw
exactly — Sean films with them directly. Never hand-write .excalidraw JSON; use the
style engine, which locks the values that a from-scratch board always gets wrong.
The five things a naive board gets wrong (all fixed by the engine)
fontFamily 5 (Excalifont) — NOT 1 (legacy Virgil). Wrong font = wrong board.
roughness 1 (controlled) — NOT 2 (scratchy).
- Stroke-width hierarchy
1 detail / 2 container / 4 emphasis — not flat 2.
- Type scale = Excalidraw presets S=16 / M=20 / L=28. Majority is S/M;
L (28) reserved for big titles only. Don't inflate past the presets.
- Signature green
#b2f2bb, frames, socials block, watermark, source labels.
How to build one
from scripts.whiteboard import style as S # run from the repo root
e = []
e.append(S.text(60, 40, "Title", size=S.FS_TITLE))
e.append(S.underline(64, 120, 800, color=S.PAL["orange"][1]))
e += S.labeled_box(80, 200, 300, 140, "A box\nwith a note", color="green")
e += S.pill_header(80, 400, 600, "SECTION", color="blue")
e += S.ellipse(500, 200, 180, 120, "agent", color="pink")
e += S.diamond(700, 200, 140, 100, "gate?", color="green")
e += S.labeled_arrow(380, 270, 500, 260, "tool calls")
e += S.socials_block(1980, 44)
e.append(S.watermark(80, 1030))
e.append(S.source_label(80, 900, "per <vendor>, <date>")) # standing rule
e.append(S.red_note(80, 960, "honest red-ink opinion")) # standing rule
S.validate(e) # catches broken bound-text ids
doc = S.document(e) # wraps with appState + white bg
Where the board goes depends on who it is for:
- A board that explains Waku's own code: write it to
docs/whiteboards/<name>.excalidraw.
- A board for a video (anything a
lab/ topic films): the .excalidraw source and the
script that draws it go to ~/Developer/Excalidraw/waku-lab/<topic>/, never into the
repo. Commit only a PNG screenshot, to lab/<topic>/screenshots/<name>.png.
evals/deterministic/test_rulebook.py fails if a .excalidraw file appears in a lab topic.
See lab/kimi-k3/build_k3_tutorial.py for a full two-board example.
Palette (name → fill/stroke), meaning follows Sean's color system
green loop / hero / final reply · red harness boundary / cost / honest-ink
blue LLM-ops / observability · orange a loop step · pink LLM/agent nodes
grey neutral state/config · yellow callout · plain bare container
Always, before delivering
S.validate(elements) — every bound text must map to a container.
- Eyeball via SVG proxy → PNG (QuickLook crops wide canvases; use headless
Chrome
--screenshot --window-size=W,H for the full board). Check for
overlaps and off-canvas elements.
- Shareable boards get the socials block +
@ShenSeanChen watermark.
- Every technical/vendor claim gets a dated
source_label.
Two ratios, measured from the masters — check yours before delivering
Generated boards fail in the same two ways every time. Both are countable, so
count them instead of eyeballing:
# against ~/Developer/Excalidraw/<newest>.excalidraw
black strokes / all shapes -> masters: 96% (354 of 368)
arrows / all shapes -> masters: 0.88 (241 arrows, 165 ellipses)
COLOUR POLLUTION. The masters are ~96% BLACK strokes with transparent or
white fills. Colour lands maybe fifteen times on a whole board: green
#b2f2bb for the hero, red #ffc9c9 for the warning, and a handful of
coloured strokes on section boundaries. A first-pass generated board
typically comes out at 3% black — a coloured stroke on every shape. Ink by
default; a fill has to earn its place. Note labeled_box(fill="chip") is
documented as user-prompt/reply chips ONLY, and using it for general boxes
is the usual cause.
NO FLOW. The masters average nearly one arrow per shape, with ellipses
for agents and diamonds for decisions. A board of boxes in a grid with two
arrows is an infographic, not a whiteboard — it reads as dead. If the
subject has motion, DRAW the motion: fan-outs, back-edges, branches from a
diamond. A back-edge that visibly returns is worth more than a paragraph.
Fix both before showing it, because they are the first things Sean notices.
Layout lessons (from comparing generated vs. hand-drawn)
- Poster-tight beats sprawl for a single teaching idea — fit it in one landscape
frame (~2500×1100) so it reads without panning. Reserve the 8000×20000 sprawl
for full system maps.
- A diagonal "ladder" reads as progress; a red barrier + a green break-through
reads as problem→solution. Reuse those two shapes.
1---2name: excalidraw3description: Generate an Excalidraw whiteboard in Sean's hand-drawn video style (Excalifont, roughness 1, green signature, socials + watermark, source labels). Use whenever the user wants a whiteboard, diagram, teaching board, or "chart" for a video or the docs/whiteboards gallery or a lab/ topic — anything Sean will film with.4---56# Excalidraw whiteboards, Sean's way78Boards are generated from code so they match the masters in `~/Developer/Excalidraw`9exactly — Sean films with them directly. Never hand-write `.excalidraw` JSON; use the10style engine, which locks the values that a from-scratch board always gets wrong.1112## The five things a naive board gets wrong (all fixed by the engine)13141. `fontFamily 5` (Excalifont) — NOT `1` (legacy Virgil). Wrong font = wrong board.152. `roughness 1` (controlled) — NOT `2` (scratchy).163. Stroke-width hierarchy `1` detail / `2` container / `4` emphasis — not flat `2`.174. Type scale = Excalidraw presets S=16 / M=20 / L=28. Majority is S/M;18 L (28) reserved for big titles only. Don't inflate past the presets.195. Signature green `#b2f2bb`, frames, socials block, watermark, source labels.2021## How to build one2223```python24from scripts.whiteboard import style as S # run from the repo root2526e = []27e.append(S.text(60, 40, "Title", size=S.FS_TITLE))28e.append(S.underline(64, 120, 800, color=S.PAL["orange"][1]))29e += S.labeled_box(80, 200, 300, 140, "A box\nwith a note", color="green")30e += S.pill_header(80, 400, 600, "SECTION", color="blue")31e += S.ellipse(500, 200, 180, 120, "agent", color="pink")32e += S.diamond(700, 200, 140, 100, "gate?", color="green")33e += S.labeled_arrow(380, 270, 500, 260, "tool calls")34e += S.socials_block(1980, 44)35e.append(S.watermark(80, 1030))36e.append(S.source_label(80, 900, "per <vendor>, <date>")) # standing rule37e.append(S.red_note(80, 960, "honest red-ink opinion")) # standing rule3839S.validate(e) # catches broken bound-text ids40doc = S.document(e) # wraps with appState + white bg41```4243Where the board goes depends on who it is for:4445- **A board that explains Waku's own code:** write it to `docs/whiteboards/<name>.excalidraw`.46- **A board for a video** (anything a `lab/` topic films): the `.excalidraw` source and the47 script that draws it go to `~/Developer/Excalidraw/waku-lab/<topic>/`, **never into the48 repo**. Commit only a PNG screenshot, to `lab/<topic>/screenshots/<name>.png`.49 `evals/deterministic/test_rulebook.py` fails if a `.excalidraw` file appears in a lab topic.50See `lab/kimi-k3/build_k3_tutorial.py` for a full two-board example.5152## Palette (name → fill/stroke), meaning follows Sean's color system5354- `green` loop / hero / final reply · `red` harness boundary / cost / honest-ink55- `blue` LLM-ops / observability · `orange` a loop step · `pink` LLM/agent nodes56- `grey` neutral state/config · `yellow` callout · `plain` bare container5758## Always, before delivering59601. `S.validate(elements)` — every bound text must map to a container.612. Eyeball via SVG proxy → PNG (QuickLook crops wide canvases; use headless62 Chrome `--screenshot --window-size=W,H` for the full board). Check for63 overlaps and off-canvas elements.643. Shareable boards get the socials block + `@ShenSeanChen` watermark.654. Every technical/vendor claim gets a dated `source_label`.6667## Two ratios, measured from the masters — check yours before delivering6869Generated boards fail in the same two ways every time. Both are countable, so70count them instead of eyeballing:7172```python73# against ~/Developer/Excalidraw/<newest>.excalidraw74black strokes / all shapes -> masters: 96% (354 of 368)75arrows / all shapes -> masters: 0.88 (241 arrows, 165 ellipses)76```77781. **COLOUR POLLUTION.** The masters are ~96% BLACK strokes with transparent or79 white fills. Colour lands maybe fifteen times on a whole board: green80 `#b2f2bb` for the hero, red `#ffc9c9` for the warning, and a handful of81 coloured strokes on section boundaries. A first-pass generated board82 typically comes out at 3% black — a coloured stroke on every shape. Ink by83 default; a fill has to earn its place. Note `labeled_box(fill="chip")` is84 documented as user-prompt/reply chips ONLY, and using it for general boxes85 is the usual cause.86872. **NO FLOW.** The masters average nearly one arrow per shape, with ellipses88 for agents and diamonds for decisions. A board of boxes in a grid with two89 arrows is an infographic, not a whiteboard — it reads as dead. If the90 subject has motion, DRAW the motion: fan-outs, back-edges, branches from a91 diamond. A back-edge that visibly returns is worth more than a paragraph.9293Fix both before showing it, because they are the first things Sean notices.9495## Layout lessons (from comparing generated vs. hand-drawn)9697- Poster-tight beats sprawl for a single teaching idea — fit it in one landscape98 frame (~2500×1100) so it reads without panning. Reserve the 8000×20000 sprawl99 for full system maps.100- A diagonal "ladder" reads as progress; a red barrier + a green break-through101 reads as problem→solution. Reuse those two shapes.