72 tasks · 57 done · 10 phases · 2026-09-23

Storyboards

A living design registry for an app already in development: the screens it has and needs, their variants and version history, flows that order them, and boards that render them side by side. Phases 1–9 are built; Phase 10 turns data entry into conversation.

57Done
72Tasks
10Phases
68Phase 10 confidence
The gist

The problem

One at a time

MockupLabGridView is a flat LazyVGrid of cached PNG thumbnails, and the only live render in the whole subsystem is the single MockupWebView in the detail pane. Cross-game drift — two games claiming the same interaction verb, three different stamp treatments — stays invisible until the app is wired and rebuilt.

A frame is not a file

The mockups that work best — prime-suspect-case-view.html, lineup-round-view-v6.html — are single-file prototypes with a harness bar of named states: First entry · Played before · Working screen · Mid-case · Case closed. The comparison worth making is first entry across every game, which no file-level grid can express.

The rules exist and nothing reads them

docs/mockups/_principles.md, _kit.html, and the spec’s binding sections (§5.2 visual grammar, §7.6 interaction-verb ownership) govern every mockup. No generation prompt and no review consults any of them.

Half the work happens in a browser

Mockups get generated in claude.ai on the desktop and in the Claude app on the phone. Those artifacts land in ad-hoc folders such as Game-Content-and-Ideas/…, never reach docs/mockups/, and carry no canon out or back.

Approach

A frame is (file, state), not a file

The board’s unit is a frame: a prototype file plus the name of a state inside it. Files declare their states through a small contract — a cr-frames JSON block listing {id, label, beat}, and a message listener that sets state on request.

beat is a shared vocabulary (entry · brief · play · commit · reveal · closed). It is what makes the comparison matrix work: rows are games, columns are beats, and a column shows the same moment across every game. A file without the contract contributes one frame at its default state, labelled single state, so nothing needs retrofitting before the board is useful.

The board is one HTML document in one web view

The board renders as a generated page holding one <iframe> per frame, lazily mounted with IntersectionObserver and driven into its state by postMessage after load. A single WKWebView hosts it.

Rejected: a SwiftUI canvas with a web view per card. That is 49 live WKWebViews and two rendering paths — screen and export — that drift apart.

Frames are inlined, not linked — measured, not assumed

The first design pointed each frame at the real file with src="../mockups/x.html", which keeps the board tiny and always current. WebKit will not do it at scale. Measured on Peregrine’s own files, loaded through loadFileURL(allowingReadAccessTo:):

8 frames → 8 rendered. 16 → 16. 30 → 27. 45 → 12.

The ceiling is cumulative rather than concurrent. Feeding the 45-frame board through a queue capped at four concurrent loads still stalled at 27, and only reached 43 after about a minute of timeouts and retries. Frames that lose never fire load at all.

The same 45 frames inlined as srcdoc all rendered in under three seconds, because a srcdoc frame shares the parent document’s origin and escapes that ceiling. So each frame’s HTML is read off disk and embedded.

postMessage is kept for state even though an inlined frame is same-origin and could be called directly, so one frame contract covers both cases. Verified: three cells of one file, driven into three different states, rendering side by side.

Export writes that same document to docs/storyboards/{slug}.html. Inlining makes it self-contained, which is what lets it work on the phone where relative paths to sibling files may not resolve. The copy is a snapshot, and it stays current because an edited mockup changes the rendered bytes and the exporter’s content comparison rewrites the file.

Frames are files; sessions are the editing layer

Slots reference a path under the project, not a MockupSession.id. Artifacts arrive from three places — the Lab, claude.ai on the desktop, the Claude app on the phone — and the file is the only thing all three produce. MockupDocScanner and MockupFolderImporter already join files to sessions, so “open this frame in the Lab” still resolves when a session exists, and offers Iterate on this when the file is an external reference.

The canon is a repo file both sides can edit

docs/mockups/_principles.md is the default canon, resolved per board and overridable. It feeds the generation prompt, the cohesion review, and the clipboard packet that goes to the browser. The review proposes canon amendments as well as fixes, because the rules are expected to move as the experience gets evaluated. The canon stays a markdown file in git; CodeRaven reads and writes it and never owns it.

The browser round-trip is clipboard-shaped, and that is a real limit

Copy Canon Packet puts canon, kit, frame contract, and the selected frames’ HTML on the clipboard, ending with a return-fence contract. Claude’s reply can carry a coderaven_storyboard JSON fence with frame, canon, and board kinds, which the Lab’s import sheet applies after an explicit commit — the same shape as Press Raven’s working pressraven_return intake.

Nothing reaches the repo from claude.ai automatically. Pasting happens in both directions. A watched docs/mockups/_inbox/ plus the existing mobile iCloud drop covers files that arrive as downloads rather than as text.

Phase 10 — the registry is a database with a form over it

Testing produced one verdict that outweighs its pass rate: “the setup is robust but the UI feels more like a database that requires a lot of manual steps / manual entry.” Every noun in Phases 2 and 3 is a table, and almost every field is typed by hand. The seeder proposes once, at import; purposes, states, beats, flows and loops are manual from then on.

The three AI paths do not fix this, because they are pointed one layer too low. They operate on mockups. None of them operates on the registry, so the structure stays hand-maintained however good they turn out to be.

Read the files before opening a conversation. Four *-flow-spec.md files already sit in docs/mockups/, the spec covers 21 games, and 8 of 56 mockups carry a visible state harness. A screen’s purpose is legible from its own markup. That evidence is unread today, and reading it is cheaper than asking.

Recommendation

A contextual pane, backed by a persistent thread

Rejected: a Mira-style session over the whole registry. The thing worth changing is usually the thing on screen, and a separate session re-establishes that context every time. A pane that carries the current selection and keeps one thread per project gets the immediacy of context and the memory of a session.

Cost: a pane is a poor place for long multi-step work, and cross-cutting questions — “which games have no commit beat” — will feel cramped in it.

Recommendation

Per-unit, on demand — not project-wide, not ambient

A project-wide fill would propose roughly 150 fields across 45 screens at once, replacing manual entry with manual review of a wall of guesses: the same chore wearing a different hat. A unit is one to five screens and reviews in a minute. Ambient nudges were rejected because they turn a tool into a nag, and because the honest trigger for a fill is “I am working on this unit now”, which only Sarah knows.

Cost: the registry fills unit by unit rather than arriving complete.

What Phase 10 re-points

/cr-frames becomes largely redundant. Its job — read a prototype’s harness, work out its states, write them down — is what the first item does automatically, without a CLI ritual to remember. The testing note “not entirely sure what we’re doing here / when to use this” is the symptom of asking a person to run something the app should offer.

What the file-reading pass actually yielded

Measured on Peregrine rather than estimated: 37 of 56 mockups carry a screen-reader description that reads as the screen’s purpose, 5 declare a usable set of states through their harness, and 3 are named by a spec that says which file it accompanies. All deterministic — no model call, so a proposal can be wrong about wording but cannot be invented.

Three defects surfaced on the first real run and were fixed before any UI existed. Pressed-state buttons inside a mockup — Prime Suspect’s POSSIBLE / UNLIKELY / PRIME / CLEARED suspect pins — were read as screen states, yielding five real states and four impostors. Buttons written inside JavaScript template strings produced states named esc-a-t. And a spec stem like peregrine-spec-v2 mangled into a claim on peregrine-v2.html, a file that does not exist.

Revised

The real lesson from Design Studio is not navigation

Design Studio never shows an empty field. Every card arrives full — a screen registry entry carries a name, a description, its flows and its emotional beat, all generated — and the only thing asked of a person is what should change about this. The registry built here shows a name taken from a filename and leaves the rest blank. That difference, not the layout, is why one reads as a conversation and the other as data entry.

So the order matters more than the pane does. Generate first, correct second. The file-reading pass is the cheap half; a model pass over the same ground fills what no file answers. Both should land before a conversation surface is built, or the pane becomes a slower way to type into blanks.

Revised recommendation

One pane whose scope follows the selection, and can be widened

The pane is primary and contextual — on a screen it revises that screen, on a unit that unit — and carries a visible scope chip that can be changed to the whole registry when the question is cross-cutting. That answers “contextual primary, full-registry also” without two surfaces, and it removes the cost previously accepted: a cramped place for “which games have no commit beat” becomes a scope change rather than a different tool.

Say what can be asked, rather than showing a blank box. Design Studio’s revision bar is a question, not a field: “Revise the registry: Missing a screen, or should two be merged?” The prompt teaches the move.

What to borrow, and what not to

Borrow: the detail card with labelled sections, the inline revise bar phrased as a question, the prev/next stepper that moves through items without navigating, select-on-left with detail-on-right, and Regenerate beside Confirm.

Do not borrow the linear wizard spine. Design Studio walks one product through one pass, so First Contact → Core Loop → Task Flows → Screen Registry can be a progress bar. This registry holds 21 units, is re-entrant, and is never finished. The card grammar scales per unit; the wizard does not. The stakes differ too — Design Studio is conceptual, while this describes a built app, so its cards need provenance the originals do not carry: which file said this, whether the screen is built, which take is current.

Decisions made for you

Judgment call

Storyboard is a mode inside the Mockup Lab tab, not a new project tab

It reads the same files and the same sessions; a seventh ProjectTab would split one subsystem across two surfaces. Reversible by adding a case to ProjectTab.

Judgment call

Board state lives in a per-project sidecar, not on MockupSession

storyboards.json sits beside target-zone-presets.json. A slot can be empty, can point at a file with no session, and can appear on several boards — none of which fits a field on a session.

Judgment call

Phase 1 ships a board with no state-switching and no review

Forty-nine files on one wall is most of the value and depends on none of the rest. The cost is that the first board shows each prototype only in its default state.

Judgment call

The board document carries a copy of every frame rather than a pointer

Peregrine’s full wall comes to about 1.5 MB. Linking is prettier and does not work; the measurements are in Approach. Reversible only by accepting that large boards render partially.

The work

Phase 1 — A wall you can look at

11 tasks Built. Document layer verified against Peregrine’s real files.
  • Add Storyboard, StoryboardGroup, StoryboardSlot, StoryboardSlotSource to a new Models/Storyboard.swift with defensive Codable
  • Add StoryboardStore persisting storyboards.json per project, mirroring the target-zone-presets sidecar pattern
  • Add StoryboardDocumentBuilder that renders a board to a self-contained HTML page with lazily mounted <iframe> frames
  • Measure how many frames a WKWebView will actually render, and pick the frame-embedding strategy from the result
  • Add StoryboardBoardView hosting the generated document in one WKWebView with zoom and frame-width controls
  • Add a .storyboard case to MockupStudioContainerView's navigation enum and a Storyboards entry point in MockupLabHeader
  • Add board CRUD UI — create, rename, delete, and a board picker
  • Seed a new board from MockupDocScanner entries so an existing project starts populated
  • Add slot arrangement — drag to reorder within a group, move between groups, add and remove groups
  • Add StoryboardExporter writing the board to docs/storyboards/{slug}.html
  • Run the board in the app and confirm the SwiftUI chrome behaves — Sarah

Phase 2 — The registry spine

9 tasks Built. Seeder verified on all 54 mockups.
  • Add Models/DesignRegistry.swift with DesignUnit, Screen, ScreenVariant, ScreenVersion, ScreenStatus and defensive Codable
  • Give ScreenVersion a source of lab, browser, external or build so a build-derived version has somewhere to live later
  • Add DesignRegistryStore persisting design-registry.json per project beside storyboards.json
  • Add ScreenRegistrySeeder that proposes units, screens, variants and versions from filenames, carrying its reasoning per grouping
  • Treat a trailing -v{n} as a version of one screen, and a same-stem suffix such as -lean or -live as a variant
  • Treat a v2_ prefix as an earlier version rather than a later one, and say so in the proposal
  • Add a seeding review sheet where a proposed tree can be merged, split, renamed or rejected before it is written
  • Add a registry browser — units in a rail, screens in the middle, the selected screen's variants and version stack on the right
  • Add screen editing — name, purpose, status, states, and promoting an older version back to current

Phase 3 — Flows, and boards rendered from them

8 tasks Built. 52 of 52 frames through the refactored renderer.
  • Add Flow and FlowStep referencing a screen by id, with an optional pinned state per step
  • Add CoreLoop per unit, reusing Design Studio's trigger/action/reward/investment shape
  • Extend StoryboardSlot with a source of screen reference, file path, or empty placeholder
  • Resolve a screen-backed slot to its current version at render time in StoryboardExporter
  • Add flow boards — a board that renders a flow's steps in order and follows the flow as it changes
  • Add registry boards — a board that renders a unit's screens, or the whole registry, crossed with beats
  • Add a flow editor that orders a handful of steps, replacing drag-ordering of a 54-item list
  • Keep ad-hoc file boards working, and add multi-select plus filtering to the arrange sheet for them

Phase 4 — Intake: new mockups reach the registry

5 tasks Built.
  • Surface unfiled mockups — files on disk that no screen claims — as a count and a list
  • Add an intake step that asks whether a file is a new screen, a new variant, or a new version of an existing screen
  • Suggest the likely screen for an unfiled file by filename proximity, ranked, never auto-applied
  • Fold a re-seed into existing units and screens by name so a later mockup joins rather than twins
  • Add "add missing screens" to a registry board so a board picks up screens added after it was made

Phase 5 — States and the comparison matrix

5 tasks Built. Matrix verified as aligned beat columns.
  • Write the frame contract as docs/mockups/_frame-contract.md and add it to the canon set
  • Parse the cr-frames JSON block in MockupDocScanner and expose declared states per file
  • Drive each frame into its slot's state by postMessage after iframe load, with a single state label when the file declares none
  • Add the matrix layout — rows are groups, columns are beats — and the flow and room layouts
  • Add a /cr-frames command that retrofits the contract onto an existing prototype file

Phase 6 — Canon and cohesion review

7 tasks Built. No live model call yet.
  • Add StoryboardCanon resolving the canon markdown, the kit file, and the frame contract for a board
  • Add a canon pane with plain-text editing and a Refine with Claude turn that presents a diff before writing
  • Add StoryboardCohesionService.runReview(board:scope:) carrying the canon and the scoped frames' HTML
  • Add a per-frame HTML budget with comment stripping and repeated-data collapsing so a wide board still fits one turn
  • Model the report as findings that cite a canon rule plus proposed canon amendments
  • Render the report in a tray modelled on DesignCohesionReportTrayView, with Send to chat and Apply amendment rows
  • Inject the canon into MockupAIService.buildPrompt as a section between Additional Context and Conversation History

Phase 7 — Generating from a slot

3 tasks Built. No live model call yet.
  • Add empty slots carrying an intent string and a beat
  • Generate from a slot with the group's sibling frames and the canon attached as context automatically
  • Write the generated file to docs/mockups/ and bind it to the slot on completion

Phase 8 — The browser round-trip

5 tasks Built, less the phone leg.
  • Add Copy Canon Packet for a board, a group, or a selection, ending with the return-fence contract
  • Parse the coderaven_storyboard return fence in the Lab import sheet with frame, canon, and board kinds
  • Make the scanner see docs/mockups/_inbox/ one level deep and surface inbox items as unfiled
  • Add an Adopt action that files an inbox item into docs/mockups/ with a slug and optionally onto a slot
  • Route the mobile iCloud file drop into docs/mockups/_inbox/

Phase 9 — Built status from the codebase

3 tasks Built. 21 of 45 screens matched on the first run.
  • Scan the project's source for view types and propose matches against registry screens
  • Mark a screen built and link the source file, leaving the match editable
  • Show a built screen's current implementation beside the mockup proposing to replace it

Phase 10 — From data entry to conversation

16 tasks File-reading half built. Shape revised after looking at Design Studio.
  • Add RegistryFillService proposing a screen's purpose, states and beats by reading its own mockup
  • Link a screen to the spec written about it, from the spec's own "Accompanies" line
  • Propose flows from *-flow-spec.md files already in the project, matching steps to registry screens by name
  • Propose a unit's loop from the project spec, leaving it blank rather than inventing one when the spec is silent
  • Review fills in one sheet per unit, with confidence per field and only high-confidence pre-ticked
  • Add RegistryEdit — a typed vocabulary of create, rename, move, split, merge, setPurpose, setStates, setFlow, setLoop
  • Apply a RegistryEdit batch through a diff review, reusing the accept-or-reject shape the seeder and the cohesion tray already use
  • Add a revise bar to the screen, unit and registry views, phrased as a question rather than a blank field
  • Give the pane a visible scope chip — this screen, this unit, the whole registry — defaulting to the selection and changeable
  • Add a prev/next stepper so screens in a unit can be worked through without returning to the list
  • Persist one conversation thread per project so the pane accumulates memory instead of re-establishing context each time
  • Offer openers derived from what is blank — screens with no purpose, units with no loop, games with no commit beat
  • Add a model pass filling the fields no file answers, so a card arrives full and the only question left is what to change
  • Extend the cohesion review to propose registry edits alongside canon amendments, so "these are the same screen" becomes a merge
  • Make screen generation callable from the conversation, so a missing screen can be asked for rather than navigated to
  • Write a glossary of board, registry, unit, screen, variant, version, state, beat, flow and step into the canon, and draw both the UI tooltips and the prompts from it

Risks & edge cases

Trade-off

A fill pass can be confidently wrong at scale

Proposing a purpose for 45 screens produces 45 plausible sentences, and plausible-but-wrong is harder to catch than blank. Per-unit scope and per-field confidence are the mitigations; neither is a guarantee, and a fill that is usually right is exactly the kind of thing that stops being read.

Trade-off

A conversation can become the same manual work with extra steps

If every request needs careful phrasing, the pane is a worse form than the fields it replaces. Openers derived from blank fields are the hedge, and they only help if they name the work actually wanted.

Edge case

Conversation will not fix the vocabulary, and may worsen it

A model using board, unit, screen and view loosely teaches the loose usage. The glossary has to land before the pane, and both the UI and the prompts have to draw from it.

Trade-off

Board documents are large and get rewritten wholesale

Peregrine’s full wall is about 1.5 MB of generated HTML under docs/storyboards/, and any change to any frame rewrites it. Boards of four to twelve frames — the intended size — come to a few hundred KB. A 24 MB inline budget caps the worst case; frames past it render as “too large to include”. Whether these belong in git is a call worth making before the first one is committed.

Unverified

The in-app surface is compile-verified only

The generated document is verified against Peregrine’s real files through a headless WKWebView harness — 45 of 45 frames, the state contract, the missing-file cell. The SwiftUI chrome around it (navigation, the arrange sheet, the zoom slider) has never been run, because this environment cannot drive a native macOS GUI.

Edge case

Review context volume

Forty-nine frames of full HTML will not fit one turn. Boards are expected to hold four to twelve frames and the review is scope-limited, but a wide matrix over long prototypes can still overflow. The per-frame budget task is a mitigation, not a guarantee.

Edge case

A board is a snapshot taken when it was last rendered

Opening the board re-reads its frames and rewrites the document, so it is current whenever it is looked at in the app. The copy sitting in docs/storyboards/ between openings can lag the mockups it shows.

Edge case

Fonts

All 47 Peregrine frames @import Google Fonts, so a full board issues that many font requests. It rendered in under three seconds on a warm connection; offline it degrades to a fallback face.

Limit

Thumbnails stay cold

MockupWebView captures a snapshot only once the detail pane has been opened, so grid cards for the 49 reference files remain placeholders. The storyboard renders live rather than fixing that.

Out of scope

Four things deliberately left out

Annotation on the board. Annotating keeps going through the existing per-session preview pane.

Writing back into externally authored files. Fork remains the sanctioned iteration path.

Any change to Design Studio’s Mockups phase. The cohesion review here is a second implementation against a different model, and the two are not merged yet.

Board version snapshots.

Final review

The plan’s strength is that it reuses what already works and inverts one assumption. The 49 Peregrine mockups are already imported, MockupDocScanner already joins files to sessions, MockupProjectExporter already owns docs/mockups/, and Design Studio’s cohesion review is a working template. The inversion — treating a frame as (file, state) rather than as a file — is what makes the comparison matrix possible, and it comes straight from the harness bar already present in the prototypes that work best.

It is weakest at the boundary between CodeRaven and claude.ai. Phase 5 is a clipboard protocol, and a clipboard protocol is only as good as the discipline applied to it. It will work; it will not feel like a door opening by itself.

The second weakness is that the cohesion review is a second implementation of something Design Studio already has. That defers a merge, and it means a bug fixed in one will not be fixed in the other.

The rendering question turned out to be the load-bearing one, and settling it early was worth more than any amount of further design. The linked-frame approach read better on paper and fails at 45 frames; only a measurement would have caught that.

One adjustment worth considering: Phases 1 and 2 could merge. A board without state-switching undersells the idea for exactly the files that matter most, the contract parsing is small, and the state mechanism is already verified working.

Phase 10 exists because testing said something the pass rate could not. Fifty-three of fifty-four implementation tasks were complete and the thing still felt like data entry — a design failure no amount of green ticks would have surfaced. The lesson repeats Phase 1’s: the load-bearing questions here get answered by using the thing, not by planning it.

68/100

Phase 10 only, up from 63. The scope question resolved cleanly — one pane whose scope follows the selection answers both needs without two surfaces — and Design Studio supplies a working precedent for the card grammar rather than a guess. The file-reading half is built and measured. What still holds it down is the generate-first half: cards must arrive full for the pane to feel like conversation, and a model writing 45 purposes is the part most likely to produce plausible wrongness. The plan as a whole stands at 72.