Turn a described flow into a small set of deliberately rough sketch images, one per key moment, and place them on a CreatorNotes canvas as a numbered left-to-right journey. The output helps a reader feel the interaction without anyone mistaking it for finished design.
The same skill covers a visual story: people, creatures, objects and cards that must stay recognisable from frame to frame. Live product UI that already exists belongs to howto-filmstrips, because screenshots are already consistent.
The fidelity principle (do not skip)
Use fat-marker / broad-strokes fidelity (from Shape Up). Thick hand-drawn marker, rough boxes, handwritten labels, almost no detail. This is intentional:
- It makes people react to the shape of the flow, not fonts, spacing, or color.
- It signals "this is still wet clay, push back on it," which polished mockups suppress.
- It is fast and cheap to regenerate when the shape changes.
Resist the urge to make it pretty. If it looks like a real screenshot, you have failed the brief.
Reference examples (look at these first)
examples/ holds a real six-frame journey produced with this skill (the CreatorNotes "Home canvas as a multi-view lens" flow). Read a couple of them before generating to calibrate the target fidelity and the coral-accent discipline:
01-timeline-view.png: the starting state, with theTimeline · Manual · By theme · +tab chrome02-switch-manual-drag.png: a card mid-drag with a dashed "was-here" outline and a coral drag arrow03-flip-back-nothing-lost.png: back to Timeline, the card returned, coral check04-by-theme-zoom-out.png: clustered theme blobs05-ask-for-a-view.png: prompt bar plus a dashed Preview with Save/Discard06-save-as-lens.png: a new named tab added to the switcher
What to copy from them: the same window and tab row in every frame, one coral-highlighted change per frame, and a circled number. Words inside a new frame come from that frame's plan (the quoted on-screen texts plus the circled number). Copy the marker weight and the coral, and leave their drawn titles and captions as historical examples.
Step 0: Scope ONE flow and list the keyframes
A keyframe is a key moment in a single flow, not a screen inventory. Before generating:
- Pick one flow (e.g. "drag a portal, flip back, save a view"). Don't mix flows.
- List 4–7 keyframes. Each is a state where something changed from the prior frame.
- Decide the shared chrome that stays identical across every frame (same window, same tab bar, same panel). Only the changed thing differs frame to frame. Consistent chrome is what makes the set read as one continuous journey.
- Write the story first, as words, using The words below: who the person is (a name from the cast), what they want, and the beat each frame lands on. The frames come from the story, never the other way round.
- For each frame, write: a circled number, a short title, what the person just did, the one element to highlight, and a one-line caption that states what this moment costs or promises. The short title is for you. Draw it in the picture only when it is quoted UI text.
- Run the readable-cold test on the frame. If the sketch shows the moment on its own, write nothing under it. If it can't, write at most one line under it that adds what the picture cannot show: time passing, what the person knows, what is at stake. Never narrate what the person did and what they got; that is the picture's job.
Keep captions free of em dashes (house style).
The meaning test (run it on every frame before you generate)
A frame that passes the style check can still be slop: a picture with no readable cause and effect, labelled with words only the builder knows. Two frames failed this way on a real board (2026-09-26): one showed a card sliding out of a frame with a tag saying "noted", the other a pushpin saying "live" and a question bubble. The reader could not tell what the person had done or what they got. Both were drawings of a mechanism (a label written, a standing question saved), not of a moment.
Every frame must pass all four:
- One visible cause, one visible effect. The person does something you can draw (says a sentence, drags a card, points), and something on screen changes because of it. A frame that shows only the effect ("noted") or only a state ("live") fails.
- Only the person's words. Speech bubbles, labels and captions use words the person would say or read on screen. No internal vocabulary: no "label", "flag", "unsure band", "standing question", "live", no probabilities. If a concept needs a term, define it in the one-sentence line under the sketch, or redraw so it isn't needed.
- Only what the chosen shape delivers. Draw the slices, not the vision. A frame of a feature that is not in the shape's parts is a promise nobody made.
- Readable cold. Imagine the frame with no title and no caption. Can a stranger say what just happened? If not, redraw the scene so the cause is in the picture (put the sentence in the bubble, draw the card that was wrong with its title).
Cut a frame that fails rather than fixing it with a longer caption. Four honest frames beat six vague ones.
The words: a story, not a legend
A filmstrip has three places for words: the header card above it, the caption drawn inside each frame, and the optional line under a frame. All three were written as legend on a real board (2026-10-16) and the owner called it AI slop:
Four moments from a board with no quote of any kind. Read 1 to 4. Coral marks what the person is looking at. Every card is the same shape, the great line is buried in its transcript, a pasted copy loses its source, and a decision can only point at the whole call.
That paragraph describes the storyboard, instructs the reader, and summarises every frame before they look. The same board, written as a story:
Before quotes Mia ran the Acme call. One sentence in it is why the team dropped the spreadsheet import, and it sits in paragraph 23 of a forty-minute transcript.
Then under the frames, only where the picture cannot carry it: "Monday. Mia opens the Acme board to write the decision up." / "She knows the sentence is in there because she was on the call. Nobody else was." / "Three weeks later." / "He signs off without ever finding the sentence."
The rules behind that rewrite come from how storyboard and comics writers work (NN/g on UX storyboards, Kenn Adams's story spine, Scott McCloud's Making Comics, Kevin Cheng's See What I Mean, the Heaths' Made to Stick):
- One person with a name and a want, in one line. NN/g names the persona at the top of every storyboard with a sentence a stakeholder can follow before looking ("Corporate buyer, James, needs to replenish office supplies"). Give yours a name, a job and the thing they are trying to get done. "The person" and "the user" are not characters.
- Put the frames on the story spine. Kenn Adams's spine: Every day… One day… Because of that… Because of that… Until finally… Frame 1 is the ordinary state, frame 2 the thing that breaks it, each later frame a consequence of the one before, the last the outcome. A problem band stops before "until finally" on purpose: the missing resolution is the argument. If two frames sit on the same beat, cut one.
- Captions add what the picture cannot show. In comics the caption is the narrator and carries time, place, stakes and what the character knows; the picture carries the action and the face. NN/g keeps storyboard captions to the person's action, surroundings and emotional state, and short. A caption that restates the image ("the person opens the board") is dead weight. "Three weeks later" is a caption. "Monday" is a caption. "She was on the call. Nobody else was" is a caption.
- Dialogue does the telling. What the person thinks or wants goes in a speech bubble, in their words, inside the frame. McCloud's "word" choice is exactly this: the only words in a panel are the ones that could not be a drawing.
- Concrete nouns, never categories. Made to Stick: people remember "bicycle", not "personality"; the kidney-heist legend sticks because of the bathtub of ice. Write "paragraph 23 of the Acme call" and the sentence itself, never "a long transcript" and "a great line". Experts drift to the abstract because they already know the pattern; the reader does not.
- No legend, no instructions, no preview. Never write "Read 1 to 4", "coral marks…", "four moments", or a list of what the frames show. The numbers and the accent are the legend. A summary ahead of the pictures spoils them and makes the reader read everything twice.
- The spine test. Read the title, the setup line and the under-frame lines alone, in order, with no pictures. If they read as a short story a stranger could retell (who wanted what, what went wrong, what it cost), the words are done. If they read as a table of contents or a product description, rewrite until they do.
Step 1: The reusable style preamble
Prepend this verbatim to every frame's prompt so the set is visually consistent. It is the style sheet: one block, the same words on every frame.
Fat-marker product sketch in the Shape Up "broad strokes" style: thick black marker
on off-white paper, deliberately rough and low-fidelity, hand-drawn rectangles and
handwritten labels, like a quick whiteboard wireframe. NOT a polished UI: no gradients,
no realistic chrome, no photorealism, no fine detail. The same thick marker line for
people, objects and UI. Flat fill, no shading. Use ONE accent color, a warm
coral-pink, and ONLY on the single active or changed element. No border around the
picture. No names, headings or labels except the quoted texts for this frame and the
circled frame number. Ticks and badges sit where the cast list puts them. Lots of
whitespace. Aspect ratio: 16:9. It should read as one storyboard keyframe that shows
the SHAPE of an interaction, not its visual design.
Set the Aspect ratio: line to the ratio you pass as --aspect, and keep that line identical on every frame. (The coral accent matches the CreatorNotes brand. Swap the accent if the project's brand differs, but keep it to ONE color used only on the changed element.)
Then add the cast lines for this frame and the frame plan. Be concrete and literal so the model draws the right boxes and labels.
Before you generate (what the image tool accepts)
Settle this before frame 1, so you do not draw six frames and then discover the path was blocked.
cnotes files gen-image takes a text prompt only. It accepts no reference image, no input image and no seed. Pass the prompt on stdin (or --prompt). --aspect is one of 1:1, 16:9, 9:16, 4:3, 3:4 (default 16:9) and does not count toward the prompt. The server rejects a prompt longer than 4,000 characters.
Who can use the server path:
- An email on the server allowlist skips the paid gate and the credit charge.
- A Pro workspace spends 10 credits per image on the server key.
- A free workspace returns
subscription_required. A short balance returnsinsufficient_credits. The error namescnotes upgradeor--byo-key. --byo-keyreadsGOOGLE_GENERATIVE_AI_API_KEY,GEMINI_API_KEYorCN_GEMINI_KEYon this machine, generates here, and uploads only the PNG. The key is never sent to CreatorNotes. That path is text-only too.
If your agent has an image tool that takes reference images (a Gemini image edit, GPT Image edits, FLUX.2, Midjourney --oref), use the reference path in Step 2. Otherwise use the text-only path. cnotes files gen-image cannot take a reference.
If you are not sure the workspace can spend the server key, say so before the first call and offer --byo-key with a free key from https://ai.google.dev.
The cast
Write this once, before any frame plan. One line per recurring thing: a fixed name, then the visual traits a stranger would use to recognise it (hair, clothes, props, pose).
- People. Draw everyone the same way. Never mix stick figures with full figures.
- Agents and creatures. Say what they must not have ("no antennas, no torso").
- The recurring object. For example the board, its tab text, its size and shape.
- Recurring cards. Their exact look, for example "the answer card: wide white card, thick coral outline, tick on the left, two lines".
- Close the list with this sentence: "Only the characters listed for a frame appear; never draw any extra robot or person."
When your image tool takes reference images, also generate a cast sheet: one image of every cast member and the recurring object side by side. Check it, keep it, and place it later as its own richtext image above the filmstrip. With cnotes files gen-image the written cast is the cast sheet. Keep the lines short, because every text-only prompt pastes them under the style preamble and must stay within 4,000 characters.
Frame plans
Write one plan per frame after Step 0 and after the cast, and before you generate.
- Composition, left to right. Which cast members appear, and nothing else.
- State. Every card on screen with its exact text, and its marks (ticks, circles).
- Changed since last frame. The one thing that moved, appeared or was answered. Everything else is copied word for word from the previous plan.
- The only words in the picture. A closing list of every string allowed.
Then diff the plans. Card text, card count and the answer card line up from one frame to the next unless the "changed" line says otherwise.
Writing tips:
- Keep definitional names in lowercase descriptions ("the board"). A capitalised label such as "THE BOARD" gets written into the picture.
- Put buttons on a large object the person is holding ("an oversized phone in Alex's hand"), or the model floats them outside it.
- Describe a recurring character again inside the frame when they are doing something new (holding, pointing). That is when the drawing drifts.
Step 2: Generate (test one, then the rest)
Generate each frame with cnotes files gen-image: it produces the image with an image model and stores it in the workspace, returning a ready-to-use URL. No repo checkout. The prompt goes on stdin (so you don't escape quotes); pass the same --aspect you wrote in the preamble, and --json so you can read .storageId and .fullUrl.
Reference images (only when your tool accepts them): pass the cast sheet and the previous accepted frame as references, then the style preamble and this frame's plan. For GPT Image, put the most important reference first. Generate frame 1, look at it, then continue frame by frame so each frame can reference the one you just accepted.
Text only (cnotes files gen-image): send the style preamble, then the cast, then the frame plan. Trim cast lines that are not in this frame so the prompt stays within 4,000 characters. Generate frame 1, check it, then the rest.
WS=<workspaceId> # e.g. your Private workspace for internal work
mkdir -p /tmp/mockups && cd /tmp/mockups
# Frame 1: generate, capture the JSON, then LOOK at it before the rest.
cnotes files gen-image --aspect 16:9 -w "$WS" --json <<'PROMPT' > kf1.json
<style preamble, with the aspect ratio line set to 16:9>
Cast in this frame (only these):
- <one short line per cast member who appears>
This frame:
- A hand-drawn circled number "1" in the top-left corner.
- Composition, left to right: ...
- State: each card, its exact text, its marks.
- Changed since last frame: the one thing. Everything else matches the previous plan.
- The only words in the picture: "..."
PROMPT
# Download the pixels and Read them. Use a new path every time you download:
# re-downloading over an existing file can leave the old image in place.
cnotes files get "$(node -e 'console.log(JSON.parse(require("fs").readFileSync("kf1.json","utf8")).storageId)')" -o /tmp/mockups/kf1.png
Always generate frame 1 first and look at it (Read /tmp/mockups/kf1.png) to confirm the fidelity lands before spending calls on the rest. Once it's right, generate the remaining frames the same way (one call each), re-pasting the same style preamble. On the text-only path, re-paste the same cast lines that appear in that frame and that frame's plan. Collect each frame's .fullUrl, in frame order, into urls.txt:
# Repeat per frame; appends the fullUrl in order.
cnotes files gen-image --aspect 16:9 -w "$WS" --json <<'PROMPT' \
| node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).fullUrl))' >> urls.txt
<style preamble + cast lines in this frame + this frame's plan>
PROMPT
On a retry, download to a fresh name (kf1-try2.png), and read that file.
Admins and Pro workspaces generate on the server. Everyone else adds
--byo-keyafter settingGOOGLE_GENERATIVE_AI_API_KEY. See the section above, andcnotes files gen-image --helpfor the flags.
Check each frame
Open every image. Fill one row per frame in your working notes, and keep the table off the canvas. A cell is "pass" or the specific miss.
| Frame | Cast, nobody extra | Traits | Card count and exact text | Marks | Answer-card look | Inside its object | No stray label or border | Tab or title text |
|---|---|---|---|---|---|---|---|---|
| 1 |
Style check: same chrome across all, coral only on the changed thing, numbers in order, the sequence tells the story.
Cold read (the one that catches slop): for each frame, before re-reading your own prompt, write one sentence describing what a stranger sees: "the person said X and Y appeared". If that sentence names neither a cause nor an effect, or uses a word that is not in the picture, the frame fails the meaning test in Step 0. Fix it with the rules below, or cut it. The image model draws what you asked for; a meaningless frame is a meaningless prompt.
Fix only the frames that broke
- Regenerate only the failing frame. Add one targeted line naming the failure ("no antennas", "both buttons inside the phone screen") and leave every other line alone.
- With an edit-capable tool, edit just that region of that frame.
- Stop after 3 tries on a frame and record what is left. Do not loop.
- If UI state keeps drifting, render the board and cards some other way (a real screenshot, or HTML/SVG) and generate only the people.
A frame you replace on the canvas later is its own edit. See Step 3.
Step 3: Place on the canvas as a journey filmstrip
Use the cnotes CLI. Wrap the canvas writes in an operation. Step 2 already stored each image and gave you a fullUrl per frame (in urls.txt, frame order), so placing uses those URLs. The filmstrip place is one edit. Include the trust card in that same place.
WS=<workspaceId>
CANVAS=<canvasId>
cd /tmp/mockups # where urls.txt from Step 2 lives
cnotes operations begin -w "$WS" --prompt "Add fat-marker user-journey keyframes for <flow>" --json >/dev/null
# urls.txt already holds one fullUrl per line, in frame order, from Step 2.
Build a place spec where each frame is one richtext item: , plus the optional one-sentence line from Step 0 after a blank line. Keep image and line in the same node. Use a horizontal stack for up to 4 frames, a 3-column grid for 5 to 6, with a header richtext above.
The header is the title plus the one setup line from The words: who the person is and what they want. Nothing else. Section headers are plain nouns ("The problem", "Solutions"): no counts, no framing phrases. Never write reading instructions on the canvas ("Read 1 to N", "coral marks…", "in N moments") and never preview the frames; the numbering and the coral accent are the legend.
Under the filmstrip, add one richtext card of a few sentences: what the check found, and anything still broken, so the reader knows how much to trust the set. That card is the check record. Leave the header and the frames free of reading instructions. When you made a cast sheet, place it as its own richtext image above the filmstrip.
// build-journey.js: writes journey.json
const fs = require('fs');
const urls = fs.readFileSync('urls.txt','utf8').trim().split('\n');
const lines = JSON.parse(fs.readFileSync('lines.json','utf8')); // one string per frame, '' when the sketch reads cold; each adds time, stakes or knowledge, never a description
const frames = urls.map((u,i)=>({kind:'item',type:'richtext',size:'medium',key:`kf${i+1}`,
content:`` + (lines[i] ? `\n\n${lines[i]}` : '')}));
const spec = { root: { kind:'stack', axis:'vertical', gap:'medium', align:'start', items:[
{kind:'item',type:'richtext',size:'medium',content:'# <title>\n\n<one setup line: who, and what they want>'},
{kind:'grid', columns: 3, gap:'medium', items:frames},
{kind:'item',type:'richtext',size:'medium',content:'<what the check found, and anything still broken>'},
]}};
fs.writeFileSync('journey.json', JSON.stringify(spec));
node build-journey.js
cnotes canvas place $CANVAS -w $WS --spec ./journey.json --json > placed.json
Swapping one frame after a fix is a new operation. Rewrite that one richtext node and leave the others:
cnotes operations begin -w "$WS" --prompt "Replace keyframe <N> after a failed check" --json >/dev/null
cnotes canvas update-text $CANVAS -w $WS --node <richtextId> --content-file ./kfN.md
cnotes operations end -w $WS
kfN.md is the image markdown for the new file, plus the optional one-sentence line when the sketch still cannot be read cold.
Step 4: Position the band (the bulk-move offset trick)
cnotes canvas place ignores an absolute origin and appends the new block below existing content. To put the filmstrip somewhere specific (e.g. a hero band above the title), read the returned positions, compute one uniform offset, and bulk-move the whole group so the relative layout is preserved:
// move-to-top.js
const fs=require('fs'); const p=require('./placed.json'); const items=p.items||[];
const minY=Math.min(...items.map(i=>i.positionY));
const dy=-1500-minY; // move the group's top edge to y = -1500 (above content at y=100)
const moves=items.map(i=>({nodeId:i.id,nodeType:'richtext',x:i.positionX,y:Math.round(i.positionY+dy)}));
fs.writeFileSync('moves.json',JSON.stringify(moves));
node move-to-top.js
cnotes canvas bulk-move $CANVAS -w $WS --moves "$(cat moves.json)"
cnotes operations end -w $WS
The place and this bulk-move share the operation opened in Step 3. A later frame swap opens its own operation, as shown above.
Gotchas (learned the hard way)
- Carry the sequence with circled numbers and left-to-right layout. The numbered filmstrip reads as a journey at a glance. Arrows between frames add clutter. (Edges between richtext and image stickies connect if you need one, for example a labeled "then" between two moments. Reach for them sparingly. The layout is the primary device.)
placeignores absolute origin and appends below existing content. Use the bulk-move offset trick above to reposition.- richtext size presets:
large≈ 1680px wide,medium≈ 1120px,small≈ 560px. For a 6-frame filmstrip,mediumis the sweet spot;largemakes a ~11,000px strip nobody can pan. - Don't verify by grepping
![in node content. CreatorNotes converts the image markdown into a TipTap image node on save, so the raw content no longer contains![. Verify instead by counting richtext nodes / their positions (e.g. nodes with negative Y for a top band). - Server key or your own key. Pro workspaces and allowlisted admins call
cnotes files gen-imageon the server (10 credits per image; admins skip the charge). Otherwise setGOOGLE_GENERATIVE_AI_API_KEYand pass--byo-key. On that path the key stays on this machine and is never sent to CreatorNotes. - Internal/working mockups go in the Private workspace. Use
-w <privateId>and verify withcnotes workspace current. - One change per frame. If two things differ between adjacent frames, split into two frames. Ambiguity about "what changed" is what makes a storyboard unreadable.
- A tag is not a moment. "noted", "live", "saved", a pin, a checkmark on its own: these are the builder's bookkeeping, invisible to the person. Draw what the person did and what the screen did back. The mechanism belongs in the shape's parts table, not in the sketch.
- The header is where slop grows back. On 2026-10-16 a header card read "Four moments from a board with no quote of any kind. Read 1 to 4. Coral marks what the person is looking at…" and was rejected on sight. It happened because a stale installed copy of this skill still asked for an explainer paragraph. The header is a title and one setup line about a named person, see The words. Check the installed copy of this skill matches the repo before running it.
- An image may stand alone when it reads cold. Add one sentence only when it doesn't, inside the same richtext node. History: bare tiles that failed the meaning test were rejected as "visual AI slop" on 2026-09-26; the fix that followed (mandatory multi-sentence explainers) over-corrected into caption slop. Fix the sketch first, not the caption.
- Grid order follows measured y, not your item order. When frames in one row measure a few pixels apart in height, a later read-back sorts them by position and the row can come out 6, 4, 5. After placing, read the positions back and
bulk-moveany row whose x order does not match the circled numbers. - Three tries, then stop. Record the remaining miss on the trust card. Another prompt loop will not make a drifting card reliable.
When NOT to use this
- The user wants a how-to of the live product: use
howto-filmstrips. Screenshots stay consistent without a cast list. - The user wants a polished, pixel-accurate mock of UI that is not in the product yet: use a design tool.
- It's a single static screen with no flow: one sketch is fine, skip the journey scaffolding.
- You are tempted to draw a solution nobody has chosen. A problem band is fine, and often the best use: moments a named person lives through today, ending before any resolution. What must not appear is a feature outside the chosen shape.