Open Feature Lab and there is already a first pass on the surface — eight to twelve feature candidates Mira generated from this project’s Foundation, Spark Inbox, blueprints, and the things Sarah built in her other projects. Each carries a name and one line of what it does. She dispositions them with a single gesture that has six readings, most of them fast rejections. The second pass is shaped by the pattern of what she killed, not only by what she kept. No pickers, no transcript, no scoreboard, no blank page.
The problem
The current Feature Lab is two pickers stacked in front of a chat box.
FeatureLabLandingView asks which of three modes you are in at the moment you have the
least information about that. FeatureLabDeepDiveView puts a second picker behind the
first, and past it sits a transcript with a “Reply to Mira…” field while the feature
material is pushed into a side panel.
Underneath the layout is a worse problem: the surface requires Sarah to produce first. That is the hardest part of the work and the part that is hardest when she is already stuck — and features are precisely what re-engages her with a stalled project. A surface that demands production from a blank page fails at the one job it exists to do.
Her actual dynamic, in her words: “I go first, she sculpts, we iterate, she decides. First draft, first research pass, first organizational structure from me. She reacts — cutting, sharpening, redirecting. I refine based on what she kept and what she cut. She makes the final call.”
Approach
Mira goes first, every time
There is no empty state because the surface is never empty. The generator runs before Sarah arrives, so the surface never opens by asking her to produce.
Sculpting, not reviewing
A reviewer grades and approves; a sculptor acts and the material changes. So there is no text reply field anywhere. Every input is a disposition applied directly to a candidate.
A candidate is a name plus one line of what it does
A bare label cannot be compared to another bare label. “Spark Inbox” next to “Universal Quick Capture” is two words looking at each other; the same two with their descriptions are obviously the same feature wearing different words. The description is load-bearing.
Variety is what makes silent rejection informative
The pass varies along declared axes. Killing every expensive candidate and both wild ones says something precise with no typing. Without the axes, twelve candidates are twelve flavours of one idea and cutting them teaches nothing.
Six dispositions from one gesture
Three bands with an intensity rather than six flat cases. Direction chooses the band, distance sets the intensity, and the label appears during the gesture so nothing has to be memorised. Every reading also has a key and a tappable label.
Editorial register, not craft costume
The torn-paper texture from the earlier bench mockup is out. Provenance rides on weight, rule colour, and a byline, in Code Raven’s own language.
What makes a candidate worth an opinion
Gesture design is worthless against weak material. “Add tagging” is a candidate nobody
wants to disposition, and no amount of good drag physics rescues it. The contract goes into the
generator prompt verbatim, and FirstPassValidator enforces the mechanical half of it
before a card reaches the surface.
1. It names a behaviour, not a capability. “Tagging for notes” fails.
“Type a tag straight into the capture field and it becomes a sidebar filter chip” passes.
2. It carries a claim about the project. Rejecting it rejects the claim rather than a noun.
3. It is decidable in one read. One sentence, present tense, eight to twenty words,
no “and/or” menu of alternatives.
4. It is refusable. The generator commits to a predicted disposition per candidate,
never shown on the card, and at least two per pass must be ones it predicts will be killed.
The prediction turns “is the generator’s model of this project any good?” into something with an answer. Kills that land on the predicted candidates mean the model is close. Kills that scatter mean it is not.
A seventh reading reports on the writing, not the idea
I don’t get it is not a disposition and does not belong in the ring. It has no direction in the gesture and never counts as a kill, because it says the candidate could not be read rather than that it was refused.
That completes a three-way the six were missing. Way off base means the generator’s model of the project is wrong. Not interested means the candidate is wrong for this developer. I don’t get it means it was wrongly written.
It is also the only instrument the contract has. Rule three asks that a candidate be decidable in one read, and nothing measured that until now — if the reading lands on the long candidates the word limit is right, and if it lands on the short ones compression is the problem.
The spread, and how it gets settled
The axes are the load-bearing guess, so they are declared as data and Phase 0 picks between two of them against real output.
Spread A — properties of the feature. Ambition
(steady / stretch / wild) × audience × cost × source.
Audience and cost already exist as FeatureAudience and FeatureComplexity,
so a candidate accepted under A carries two of its four axis values into the Features layer for free.
Spread B — properties of the offer. Pressure (annoys her now / would make the project legible to someone else / would make it more fun to work on) × grain (one control / one surface / the shape of the app) × conviction (Mira thinks it is right / arguable / probably wrong but worth saying) × source.
The fork is real: A varies candidates by what they are, B by why they are being offered. The test is four real passes — CodeRaven and VentureRaven under both spreads — loaded into the prototype with the labels hidden. A spread wins when candidates can be rated without hesitation and the kills cluster on one axis value.
Decisions made for you
All three modes collapse into one surface
Discovery, Auto-Suggest and Deep Dive were three doors onto the same activity. The generator subsumes Auto-Suggest; the other two lose their pickers and their reason to exist separately. FeatureLabMode stops being written but stays decodable, so sessions already on disk still parse.
Stored sessions from the old modes become read-only
FeatureLabSessionStore holds real Discovery transcripts and Deep Dive drafts. Deleting the three views leaves them with no renderer. They keep loading, they list in Sessions with their captured candidates, and they cannot be resumed. Silent data loss is the worse option.
The no band is never re-proposed
Pass two re-offering something already killed is the fastest way to make the surface feel like it is not listening. Hold-band candidates come back in a later pass, never the next one.
Way off base triggers a premise check
“Not interested” is a preference. “Way off base” says the generator’s model of the project is wrong. The second one asks what it got wrong before generating anything.
The prototype lives in docs/mockups/
That puts it in Mockup Lab as a reference session and syncs it to the mobile workbench, so it is reachable from the phone during the review.
This supersedes the bench design
The 2026-09-06 room record settled tear / pile / cut / press / pin and first-class relations. That is a different surface. Its deletions are carried over; its verbs and fusion mechanics are out of scope here, deferred rather than rejected.
The work
Phase 0 — See it and play with it
7 tasks Prototype built and rebuilt twice against hands-on use. Four passes loaded. The spread is still unchosen.- Build an interactive HTML prototype of the first pass and the disposition gesture
- Include all six dispositions, the in-gesture labels, and the keyboard path
- Generate four real first passes — CodeRaven and VentureRaven, each under both spreads — and load them into the prototype as data
- Add a spread inspector that reveals each candidate's axis values and Mira's predicted disposition, hidden by default
- Convene Game Feel Expert, Product Philosopher and Experience Curator against the prototype and the four passesyou
- Review the prototype and settle the axes before any Swift is written
- Write the chosen spread and the final candidate contract back into this plan before Phase 2 starts
Phase 1 — Strip the old surface
8 tasks Built 2026-09-06. Compiles clean; runtime unverified.- Delete the landing picker and the route picker
- Remove the transcript column and the reply-to-Mira field
- Remove the Outcome Panel and every count, timer and running total
- Collapse FeatureLabMode so Discovery, Auto-Suggest and Deep Dive share one surface
- Keep FeatureLabMode decodable so sessions already on disk still parse after the modes are gone
- Make legacy Discovery and Deep Dive sessions read-only in the Sessions tab rather than resumable
- Delete the mode-specific views once the single surface renders
- Reduce FeatureLabRuntime's three per-session coordinator caches to the one the new surface needs
As built — three notes. The 60-minute idle
timer was removed rather than kept hidden: with one live session reused on every open,
pausing it while you are looking at it would blank the surface, and nothing else depended
on it. FeatureLabMiraEngine.swift is now unreferenced and was kept rather than
deleted, because Phase 3’s generator is what will use it. The generator is still
FeatureLabAutoSuggestService.swift, retargeted to write firstPassData
and marked INTERIM in its header; its prompt is untouched and Phase 3 replaces it.
Phase 2 — The candidate and the spread
6 tasks Depends on Phase 0 — the chosen spread decides the axis fields.- Add a FeatureCandidate model with name, one-line description, axis tags and provenance
- Encode the chosen spread as a FirstPassSpread declaration rather than constants scattered through the generator
- Map the cost and audience axes onto FeatureComplexity and FeatureAudience so an accepted candidate carries them into the layer
- Add FirstPassValidator with the mechanical candidate checks and a one-shot refill for failed slots
- Add a Mira-predicted disposition to each candidate, stored but never rendered on the card
- Verify FirstPassValidator against a standalone swiftc harness in the scratchpad
Phase 3 — The first pass
8 tasks Depends on Phase 2.- Build the first-pass generator that emits eight to twelve candidates before Sarah arrives
- Write the candidate contract into the generator prompt as hard rules with worked pass and fail examples
- Vary candidates deliberately along ambition, audience, cost and source
- Draw source material from the Features layer, Spark Inbox, blueprints and other projects
- Carry each candidate's description through every surface, never the name alone
- Mark candidates adapted from another project with that project's colour and origin
- Run a fresh pass on entering Feature Lab when the stored pass is stale, and reuse the stored one otherwise
- Route the generation through FeatureLabRuntime so it survives navigating away
Phase 4 — Disposition
10 tasks Buildable against stub candidates in parallel with Phase 3.- Add a Disposition value carrying three bands and an intensity rather than six flat cases
- Map the six readings: Excited, Promising, Interesting not now, Leaning no, Not interested, Way off base
- Build the drag where direction chooses the band and distance sets the intensity
- Show the band label during the drag and clear it on release
- Make the in-gesture labels tappable so the gesture is never the only path
- Leave a quiet persistent mark on each rated card
- Add the keyboard path: up, up-up, left, right, down, down-down
- Make a second press promote immediately rather than waiting on a timing window
- Add space to advance without rating
- Add undo that reverses the last disposition without breaking rhythm
- Add the I-don't-get-it reading outside the ring, with no drag direction and no place in the no band
Phase 5 — The second pass
7 tasks Depends on Phases 3 and 4.- Persist every disposition alongside the candidate that earned it
- Condition the second pass on the pattern of dispositions, not only on what was kept
- Never re-propose a candidate that landed in the no band
- Resurface hold-band candidates in a later pass rather than the next one
- Make a Way-off-base rating ask what the generator got wrong before it regenerates
- Make an I-don't-get-it rating rewrite that one candidate and offer it again rather than regenerating the pass
- Show how far Mira's predicted dispositions landed from the real ones after each pass
- Promote Excited and Promising candidates into real Features layer entries
Phase 6 — The Deep Dive, after the pass
12 tasks Depends on Phases 3, 4 and 5. The spread decides what a candidate carries in.The first pass answers which of these. It cannot answer what is this really, and the end-of-pass offers have nothing behind them until something does. Five named moves, modelled on Ideation’s Deep Dive, which frames each as a human situation rather than a form field.
The mockup is a shape, not a skin.
docs/mockups/feature-lab-deep-dive.html autoplays the whole dive and is the reference for the
interaction — the material pinned in view, the drafted position, the four verbs, the settled
positions accumulating into the entry. Its typography is a serif approximation and is wrong. The real surface
uses Typography (Plus Jakarta Sans: Heading.small for a move name,
Body.large for a drafted position, Caption.regularUppercase for section labels) on
the editorial cmInk* palette. Take the layout from the mockup and none of the type.
The gate is a drafted position, not a question. Asking the developer how a feature serves its personas is asking her to produce, one level down, which is the thing this rebuild exists to stop. Mira drafts the position — wrong in interesting ways — and the developer cuts, sharpens and redirects. Checked boxes become homework; a claim you disagree with never does.
- Add a FeatureDeepDive with five moves: The Moment, Whose Hands, The Neighbours, The Cut, What Breaks
- Open every move with Mira's drafted position rather than a question to answer
- Give each move the same direct actions the cards have, with no open reply field as the primary input
- Keep the candidate in front of the conversation rather than beside it
- Close a move when the developer is satisfied with the position, not when she has supplied it
- Route Excited candidates into a dive and park the hold band rather than diving it
- Add the one-pass expert: one outside seat, one response, against a single move
- Make Devil's Advocate available as a one-pass seat, which needs its meetingOnly mode in AgentModeMap relaxed
- Let the Devil's Advocate fire only after Mira has drafted, since it refuses to speak before a position exists
- Write a finished dive into its Features layer entry through FeatureLabCommitService
- Persist a dive so it survives navigating away and an app restart
- Build the surface on Typography and the cmInk editorial tokens, not on the mockup's serif approximation
Manual Testing
8 tasks Yours. The gesture and the material can only be judged by hand.- Confirm a first pass reads as eight to twelve genuinely different ideas, not one idea restatedyou
- Confirm every candidate can be rated without needing to ask what it meansyou
- Confirm I-don't-get-it lands on the long candidates rather than the short onesyou
- Confirm a Deep Dive move opens with something worth disagreeing with rather than a question to answeryou
- Confirm dispositioning twelve candidates feels decisive rather than clericalyou
- Confirm the spread inspector shows kills clustering on an axis rather than scattering across the passyou
- Confirm the second pass is visibly shaped by what was killedyou
- Confirm Way off base produces a different second pass from Not interestedyou
- Confirm a Feature Lab session saved before this rebuild still opens read-only rather than failingyou
- Confirm the surface leaves you more interested in building than when you opened ityou
Risks & edge cases
The axes are still a judgement call, now with evidence
Phase 0 tests two spreads against four real passes instead of trusting one, but the verdict is still Sarah’s on a single sitting, and both spreads could be wrong together. The salvage is the spread being data: a third spread is a new declaration, not a rewrite of the generator.
The prediction mechanism can be gamed
If the generator learns that predicting kills is rewarded, it can pad the pass with candidates it knows are bad — filler under another name. The two must-be-killable candidates are a floor, not a target, and the validator applies to them identically.
The validator cannot catch boring
The contract and the validator catch vagueness, generic openers and category nouns. They cannot catch a well-formed sentence describing a dull feature. That residue is real and only the prototype review shows how large it is.
Six dispositions may still feel clerical at twelve cards
The force-and-direction gesture is meant to keep judgement fast, but twelve small decisions is still twelve decisions. If the prototype feels like triage, the answer is fewer candidates rather than fewer dispositions.
Deleting three modes strands stored sessions
Read-only legacy sessions are the chosen answer, and that is still work: the Sessions tab needs a renderer for a session whose view no longer exists. Getting it wrong loses real Discovery transcripts.
Cross-project source material is unproven
Drawing on other projects means reading a foreign FoundationManager, and BlueprintStore’s caches are known not to be project-keyed — the exact shape that caused the earlier cross-project blueprint leak. Read features through FoundationManager.shared(for:) and keep blueprint reads to the current project.
The register is unsettled
Feature Lab is a drafting surface, which in this codebase means the light working palette. Immersive argues for the editorial register. Those pull against each other and the prototype should try the editorial one first.
Out of scope
- Relations, press, and the fusion mechanics from the 2026-09-06 bench record — deferred, not rejected
- Any aging, wear, or resolution-fade system
- Mobile
- Feature Lab in the Personal workspace
- Fixing
BlueprintStore’s non-project-keyed caches; this plan works around them instead
Final review
What it does well. The inversion is the right diagnosis, and the plan now has two things it was missing. The axes stopped being an assertion and became a fork with a test attached, and candidate quality stopped being a hope and became a contract with a validator behind it. Phase 1 also stands alone: the deletions can land while the spread is still being argued about, so the gate blocks nothing that does not genuinely depend on it.
Where it is weakest. Everything downstream of Phase 0 is written against a spread that does not exist yet, so Phases 2 and 3 name fields whose types are still open — that is honest, but it means those phases cannot be estimated until the gate closes. The disposition gesture itself is unvalidated beyond the prototype, and a gesture that feels good against twelve fake cards can feel like triage against twelve real ones. The read-only legacy session renderer is real work sized at one bullet.
What I would adjust if pushed. Cut the pass to eight candidates rather than twelve for the first build. Twelve is where the clerical risk lives, and the prototype can test both without changing any Swift. I would also run the four Phase 0 passes before building the prototype rather than after — real material makes the prototype an honest test, and generating it costs one CLI run per pass.
Up from 74. The two soft spots now have mechanisms rather than intentions — a two-spread fork tested against real passes, and a candidate contract with a validator and a predicted-disposition check behind it. What holds it back is that the gate is still one person’s judgement on one sitting, the disposition gesture is unproven past the prototype, and Phases 2 and 3 are written against a spread that has not been chosen yet.