Turn a real product flow into a left-to-right filmstrip of screenshots on a CreatorNotes canvas. Each frame shows the full UI with a clear click (or type) target: crimson highlight on the real element box + soft radial spotlight dimming the rest.
The durable output is one walkthrough note holding every step and its screenshot, stamped with the product version and date it shows. The filmstrip on the canvas is a projection of that note: people scan the strip, while search, citations and agents read the note.
This is the pixel-accurate sibling of fat-marker-flows. Use fat-marker-flows for a proposed Shape Up sketch and for a visual story whose characters stay the same. Use this skill when the question is how to do it in the live product.
Design lock (do not invent a new look)
| Element | Spec |
|---|---|
| Highlight colour | Classy crimson #C43036 (outline, soft wash, CLICK/TYPE pill) |
| Spotlight | Soft radial fade, max opacity ~40% (up to ~55% on dark forms), long fade (fade_mult ≈ 5.5) |
| Done frame | Green #2F9E6A outline + DONE pill; lighter spotlight (~30%) |
| Steps | One walkthrough note per job: ## N · <action> headings, screenshot embedded under each |
| Captions | Canvas richtext under each image, projected from the note (short, no mentions) |
| Titles | Middle dot · as separator; no em dashes |
| Layout | One path = one horizontal row; multiple paths = stacked sections |
| Language | Match the product UI; prefer UK English in captions unless the product is US-only |
Do not use orange bullseyes, yellow highlighters, cyan brackets, or hard rectangular box-shadow: 0 0 0 9999px spotlights. The hard box-shadow gets clipped by parent overflow and often never appears in the PNG.
When to use / not use
Use when: documenting a live click path, comparing entry points (Path A/B/C), workshop how-tos, JTBD storyboards from a real app.
Do not use when: the UI does not exist yet, or the frames are imagined people and objects that must stay the same. Both are fat-marker-flows. A polished Figma mock belongs in a design tool.
Prerequisites
- A browser tool that can run JavaScript in the page and save a screenshot to a file: CDP
Runtime.evaluate, Cursor's browser MCP, Playwright, oragent-browser. A tool that only shows the screenshot to the model cannot feedcnotes files upload. - A sandbox or demo account with made-up data. Never capture a production account that holds customer records.
cnotesauthenticated with a writable workspace (cnotes auth status,cnotes workspace current).- This skill folder installed (
cnotes skills install howto-filmstrips) so scripts resolve under the agent skills dir.
Step 0 — Scope the paths
- Name the job (e.g. “Create a new contact”).
- List each entry path separately (Path A, Path B…). One path = one row.
- For each path, list 4–7 frames: each frame is one action (click or type) or a final Done state.
- Plan one line per frame (
N · Click …/N · Type …/N · Done · …). The final caption text comes from capture (Step 2). - Write the capture stamp before the first shot: product name, the release or version shown (from the app's About page, footer or release ticket), plan or region if it changes the UI, the account used, and today's date. It becomes the first line of the note so a reader knows which build the screenshots show.
Step 1 — Inject highlight helpers (before any click)
Read scripts/inject-highlight.js and evaluate its entire file contents in the page via CDP Runtime.evaluate (or equivalent). That installs:
window.__cnHowto.highlight(el, { label: 'CLICK'|'TYPE'|'DONE' })window.__cnHowto.clear()window.__cnHowto.findExact(text, opts)— visible, sized matches; prefer menu-row / control sizewindow.__cnHowto.redact(selectors)/unredact()— blur sensitive regions for every later frame
The injector uses a fixed full-viewport radial overlay (not element box-shadow), so the spotlight survives screenshot and parent clipping.
Calibrate visually: after first inject, highlight one control and screenshot. Check that the crimson outline hugs the element box (not an inner span), the CLICK pill sits just above it and is legible, and the spotlight is a soft radial fade centred on the control — never a hard-edged rectangle. Adjust opacity only if the product chrome is unusually light or dark.
Redact before the first shot
Look at the first screen for names, emails, phone numbers, amounts, matter or account numbers and anything else that identifies a real person or customer. Blur them:
__cnHowto.redact(['.client-name', '[data-field="email"]', 'table.invoices td.amount'])
Redactions survive clear() and hold for every later frame; re-run redact after each navigation, because a new page has new elements. __cnHowto.unredact() removes them. Redaction is a backstop for a sandbox account, never a licence to capture production data. Check every PNG by eye before upload; if anything sensitive shows, re-shoot.
Step 2 — Capture loop (per frame)
For each click/type frame:
- Find the target with
__cnHowto.findExact(exact label text) or a tight selector. Prefer the largest visible control that matches (menu row over inner text span). - Highlight with the right label (
CLICK/TYPE). Keep the return value:stepDraftis a first-draft caption built from the control's own label (Click **Save contact**,Type in **Email address**,Choose from **Matter type**). - Screenshot the viewport → save as
A1.png,A2.png, … - Clear highlight.
- Perform the action (click / type), wait for UI settle.
- Finish the caption from
stepDraftplus what changed on screen:Click **Save contact**. The contact opens with its matters tab empty.One or two sentences, in the product's words. - Repeat.
After the last frame, tidy the sequence before upload: drop frames whose action changed nothing visible, merge consecutive typing into one field into one frame, and renumber.
For the Done frame: land on the success state, highlight the result (e.g. contact name) with { label: 'DONE' }, screenshot, clear.
Accuracy rules
- Capture highlights live from element
getBoundingClientRect. Do not guess pixel rings on old PNGs. - If a frame teaches picking an option that is already selected, switch to another option first, then highlight the target and shoot — so the frame shows the switch, not a no-op.
- Retina / DPR: screenshots are often 2× CSS pixels. The injector uses CSS pixels for the overlay; if you fall back to
soft-spotlight.py, pass--dpr(usually2).
Fallback if the in-page spotlight is weak
On very dark UIs the radial overlay can look faint. After capture, run:
python3 "<skillDir>/scripts/soft-spotlight.py" \
--in A3.png --out A3.png \
--cx 276 --cy 219 --dpr 2 \
--opacity 0.55
--cx/--cy are CSS-pixel centres from getBoundingClientRect (the script multiplies by --dpr).
Step 3 — Upload, write the note, place the filmstrip
WS=<workspaceId>
CANVAS=<canvasId>
OUT=/tmp/howto-filmstrip
mkdir -p "$OUT"
cnotes operations begin -w "$WS" --prompt "How-to filmstrip: <job>" --json >/dev/null
# Upload in frame order; collect storageIds
> "$OUT/ids.txt"
for f in A1 A2 A3 A4 A5 A6; do
sid=$(cnotes files upload "$OUT/$f.png" -w "$WS" --json \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('storageId') or d.get('id') or '')")
echo "$sid" >> "$OUT/ids.txt"
done
Write the walkthrough note first. Pick its type with cnotes types list: Workflow if the workspace has it, else Guide, else Note. One note per job:
# Create a new contact · the walkthrough
Captured 16 Oct 2026 in Acme Practice v26.10 (APAC, Boutique plan), sandbox account, from the Contacts page.
What the job is and who does it, in one or two sentences.
## 1 · Click Create new
Click **Create new +**. A menu opens with Contact, Matter and Task.

## 2 · …
- Numbered
##headings, never a markdown ordered list: images inside list items convert unreliably. - Several entry paths: one note,
## Path A · …headings with### A1 · …steps under each. - Create it inside the open run with
cnotes notes create --notes '[{"key":"W","type":"Workflow","markdownFile":"./walkthrough.md"}]'. - Refresh after the product changes: re-shoot, then
cnotes versions create <displayId> --markdown-file …with a new capture stamp. Never a second note for the same job.
Then build a place spec: header + the walkthrough note + one section per path, each with a grid of richtext cells (image markdown + short caption copied from the note). Prefer columns: N matching frame count (e.g. 6). Use size: "medium" for frames. Tiles are projections: no mention syntax inside them (it blanks the card).
Image URL form inside richtext:

1 · Click Create new +
Clear existing storyboard nodes if rebuilding the same canvas:
cnotes canvas get "$CANVAS" -w "$WS" --json | python3 -c "
import sys,json
d=json.load(sys.stdin)
ids=[n['id'] for n in (d.get('allNodes') or [])]
for s in d.get('sectionNodes') or []:
if s['id'] not in ids: ids.append(s['id'])
open('/tmp/howto-remove.json','w').write(json.dumps(ids))
"
cnotes canvas agent-run wrap --canvas "$CANVAS" --prompt "Clear before how-to rebuild" -- \
cnotes canvas bulk-remove "$CANVAS" --nodes "$(cat /tmp/howto-remove.json)" -w "$WS" --json
Place:
cnotes canvas agent-run wrap --canvas "$CANVAS" --prompt "Place how-to filmstrip" -- \
cnotes canvas place "$CANVAS" --spec "$OUT/unified.json" -w "$WS" --json
cnotes operations end -w "$WS" --json
Multi-path layout sketch
{
"root": {
"kind": "stack",
"axis": "vertical",
"gap": "spacious",
"align": "start",
"items": [
{ "kind": "item", "type": "richtext", "size": "large",
"content": "# How to …\n\nSame job, several entry points. Read each row left to right." },
{ "kind": "item", "type": "note", "noteId": "<the walkthrough note's displayId>" },
{
"kind": "section",
"name": "Path A · …",
"child": {
"kind": "stack",
"axis": "vertical",
"gap": "tight",
"align": "start",
"items": [
{ "kind": "item", "type": "richtext", "size": "small",
"content": "**Path A** · …" },
{ "kind": "grid", "columns": 6, "gap": "medium", "items": [/* frames */] }
]
}
}
]
}
}
Gotchas (learned the hard way)
box-shadow: 9999pxspotlights get clipped and vanish from screenshots. Always use the fixed radial overlay frominject-highlight.js.- Wrong element: loose regexes match sibling labels (
/save/ialso matches “Save and close”). Prefer exact text + min width/height + prefer larger hit targets. - CLICK pill vs outline mismatch: place the pill from the highlighted element’s box after layout; re-highlight after fixing helpers if the first shot drifted.
- Dark forms: bump spotlight opacity toward 0.55 so the fade reads.
- DPR: post-process centres must be CSS × devicePixelRatio.
placeappends below existing content unless you bulk-remove first when rebuilding.- Verify images by node count / positions, not by grepping
![in stored content (TipTap converts markdown images). - Do not commit secrets from the product under test; use throwaway trial data in screenshots and
redactanything that still looks real. - No stamp, no trust: a filmstrip without the version and date it shows reads as current long after the UI has moved on.
Pairing with fat-marker-flows
| Skill | Fidelity | Source |
|---|---|---|
fat-marker-flows | Rough Shape Up sketches, and a visual story with recurring people | Image model |
howto-filmstrips | Real product UI | Browser capture + CSS |
Same canvas can hold a fat-marker "proposed" row and a how-to "as shipped" row. Keep the visual language of each skill intact so a reader can tell a sketch from a screenshot.