42 tasks · 5 phases · ~12 files · 2026-09-09

Three Doors to a Dive

Feature Lab can only dive on what Mira just proposed. This adds two more ways in — features already sitting in the Foundation, and ideas you brought yourself — without putting a chooser in front of the door.

42Tasks
5Phases
~12Files touched
2New sheets
The gist

The Deep Dive has become the valuable half of Feature Lab, and the only thing that can reach it is a candidate Mira invented ninety seconds ago. Two other kinds of idea deserve the same treatment: features already accepted into the Foundation and never argued through, and ideas you arrived with. This adds both routes, reusing the review queue rather than building a second one, and fixes the commit path that would otherwise duplicate every Foundation feature it dove on.

The problem

Opening Feature Lab starts a pass. That is the right front door and it is not changing. But it means a feature sitting in the Foundation at status Planned — already accepted as worth having, never given a moment, a persona, a cut, or a case against it — can only be worked if Mira independently re-invents it. Same for an idea you brought with you.

There is also a defect waiting underneath. FeatureDeepDiveService.commit() builds a fresh candidate and hands it to acceptCandidates, which only appends. A dive that started from an existing Foundation feature would write a second copy of it.

Approach

The pass stays the door. A three-option menu on open is the blank page in a different costume — it asks you to decide something before anything has been produced for you. Opening Feature Lab keeps starting a pass immediately. The other two routes live on a header control available at all times, and appear inline on the waiting screen, which is where you are standing anyway while a pass generates.

Both new routes reuse the review queue. Each writes candidates into a session with a disposition already set, which empties undecided, which lands you on the review surface that already orders a queue and offers Dig in per item. That covers “one or more” without a line of new queue code. The price is that the review surface shows pass-specific chrome a hand-picked set has no use for, so a session-level origin marker suppresses it.

A Foundation dive updates rather than appends. Phase 1 fixes that before either route can reach it.

Decisions made for you

Decision

No chooser at the front door

The alternative — three cards on open, pick one — is cleaner to build and reverses the premise the whole first-pass rebuild rests on. Rejected on that basis. If you would rather have the explicit chooser, this is the one decision worth overturning before Phase 4 starts.

Decision

Candidates, not a parallel dive queue

Routes 2 and 3 could each open a dive directly. They would then need their own ordering, their own now-or-later handling, and their own storage. Writing candidates with a pre-set disposition reaches the existing queue instead.

Decision

A second dive on the same feature is a question

The dive carries the feature id, so the commit can see that another committed dive already wrote there. It puts the choice up — overwrite the existing rationale, or write this dive as a new feature with its own id. Two dives can legitimately land somewhere different, and choosing for the developer would discard one of them without saying so.

Decision

A generating pass is never cancelled

Choosing another route while Mira is mid-pass leaves her running. The runtime already owns generation, so the pass lands and waits under Sessions rather than being thrown away.

The work

Phase 1 — The dive stops duplicating

10 tasks Lands first and alone. Fixes a live defect the new routes would otherwise multiply, and settles what a second dive on the same feature does.
  • Add `foundationFeatureId: String?` to `FeatureCandidate`, defaulted nil, with `decodeIfPresent` in the custom decoder so existing sessions still open
  • Carry `foundationFeatureId` onto `FeatureDeepDive` so a dive knows which Features-layer record it belongs to, decoding defensively
  • Add `FeatureLabCommitService.applyDive(_:for:)` that updates the existing `Feature` in place when `foundationFeatureId` resolves, and appends when it does not
  • Snapshot the Features layer before either path, with a label that names which one ran
  • Update the in-place path to write the dive's rationale and leave name, status, priority and persona links alone unless the dive settled something about them
  • Point `FeatureDeepDiveService.commit()` at `applyDive` and delete its local candidate construction
  • Handle the case where `foundationFeatureId` is set but the feature has since been deleted from the layer — append rather than silently doing nothing, and say so in the log
  • Detect at commit time that another committed dive already wrote to this `foundationFeatureId`, by scanning dives across sessions rather than marking the Feature record
  • Put the collision to the developer instead of resolving it silently: overwrite the existing rationale, or write this dive as a new feature with its own id
  • Make the new-feature choice clear `foundationFeatureId` on this dive, so a third dive collides with neither of the first two

Phase 2 — From the Foundation

6 tasks Depends on Phase 1. Nothing here is safe until commit stops appending blindly.
  • Add `FeatureLabOrigin` (`pass` / `foundation` / `ownIdeas`) to `FeatureLabFirstPassData`, defaulting to `pass` so every existing session decodes unchanged
  • Build `FoundationFeaturePickerSheet` — the Features layer filtered to Planned, Potential, Deferred and Suggested, grouped by status, multi-select
  • Exclude features that already have an uncommitted dive in any live session, and say why they are absent rather than hiding them silently
  • Show each feature's existing rationale in the picker row, because a feature that already has one is a different proposition from one that does not
  • Add `FeatureLabSessionStore.startFoundationSession(project:features:)` which builds one candidate per picked feature with `disposition = .excited`, `foundationFeatureId` set, and origin `.foundation`
  • Carry the feature's existing rationale onto the candidate so Mira's first move opens with what was already claimed rather than from nothing

Phase 3 — From an idea of mine

5 tasks Independent of Phase 2 — either can land first.
  • Build `OwnIdeaSheet` — a name field and a description field, with an add-another control so several ideas go in at once
  • Require a description before the idea can be added, for the same reason the first-pass contract requires one: a bare name cannot be argued with
  • Add `FeatureLabSessionStore.startOwnIdeasSession(project:ideas:)` mirroring the Foundation route with origin `.ownIdeas` and no `foundationFeatureId`
  • Offer the Spark Inbox as source material in the sheet — unarchived sparks classified to this app, one tap to fill the fields
  • Mark candidates from this route so Mira's prompt knows the idea is the developer's rather than hers, and does not congratulate her on it

Phase 4 — The entrances

6 tasks Needs 2 and 3 to exist before there is anything to point at.
  • Add a "Start a dive" menu to the Feature Lab header with two items, From the Foundation and From an idea of mine
  • Show the same two entrances inline on the `waiting` screen, below the generating indicator
  • Make picking a route while a pass is generating leave the pass running, and say in one line that it will be waiting in Sessions
  • Suppress the scorecard, the axis reading and the second-pass action on `FirstPassReviewView` when the session origin is not `.pass`
  • Give the non-pass review header its own line naming where the set came from, so a Foundation set never reads as something Mira proposed
  • Show the origin on session rows in `FeatureLabSessionsSheet` so the three kinds are distinguishable in history

Phase 5 — Breathing room on the triage surface

4 tasks Done. Landed ahead of the rest — it fixes what shipped, not what is being added.
  • Make the verdict cross claim every point the footer does not and centre itself in what is left
  • Pin the footer to the bottom of the stage and give it its own separation from the cross
  • Declare the fill at every level of the chain — container, first-pass view, stage — so no single content-sized link collapses it
  • Raise the floor to 40pt above and below the cross and 48pt on the horizontal margins

Manual Testing

11 tasks Yours. The first two clear the layout fix that is compiled but not yet seen running.
  • Relaunch and confirm the triage surface breathes like the mockup — the cross centred in a field that grows with the window, the footer down at the bottomyou
  • Resize the window short and confirm the cross keeps its 40pt floor and never collides with the footeryou
  • Relaunch the app and confirm the restyled triage card renders as the mockup did — white card on warm grey, spine leaning on hover, distance-encoded verdict crossyou
  • Open Feature Lab cold and confirm a pass still starts generating with no chooser in the wayyou
  • Pick two Planned features from the Foundation and confirm both arrive in the review queue with Dig in available on eachyou
  • Complete a dive on a Foundation feature and confirm the Features layer entry was updated rather than duplicatedyou
  • Confirm the Features layer snapshot taken before that write can be restoredyou
  • Dive the same Foundation feature twice and confirm the second commit offers overwrite versus a new feature rather than deciding for youyou
  • Enter two of your own ideas and confirm they reach the queue and dive normallyyou
  • Start a Foundation route while a pass is generating and confirm the pass still lands and appears under Sessionsyou
  • Confirm the scorecard and second-pass action are absent on a Foundation session and present on a pass sessionyou

Risks & edge cases

Risk

Route 3 asks you to produce

“From an idea of mine” is a blank name field and a blank description field — precisely the thing the first-pass rebuild exists to remove. Ruled on: the option stays, and it is not expected to carry much traffic. The signal to watch for is it becoming the route reached for by default, which would mean the pass had stopped being worth opening.

Edge case

The Foundation feature is deleted mid-dive

A dive holds a foundationFeatureId that can stop resolving. Phase 1 appends rather than failing silently, which is the recoverable wrong answer rather than the invisible one.

Edge case

The picker still hides an in-flight dive

Features with an uncommitted dive elsewhere are excluded from the picker rather than offering to resume that dive. The row says why it is absent, and getting back to the dive in progress means finding its session by hand.

Risk

The in-place update is lossy by omission

Writing only the rationale leaves status, priority and persona links as they were, even when the dive settled something that contradicts them. The dive knows whose hands the feature is for; the Features entry will not learn it. This is deliberate for now and it is the first thing to revisit.

Out of scope

Final review

The strong part is Phase 1 and the queue reuse. Phase 1 is a real defect found by reading the commit path rather than a hypothetical, and it lands independently of everything else — worth doing even if the rest of this plan is rejected. Reaching the existing review queue by writing pre-dispositioned candidates means routes 2 and 3 inherit ordering, the now-or-later round and the dive storage without duplicating any of it, and it is the reason this is a small plan rather than a large one.

The weakest part is the entrance placement in Phase 4. Keeping the pass as the unconditional front door is right for the Lab’s premise and wrong for the specific case where you opened it knowing exactly which Foundation feature you wanted — you pay for a pass you did not want, and the answer offered here is that the pass is not wasted because it waits for you. That is true and it is also a rationalisation. If it grates in use, the fix is a persisted last-route-used that re-opens where you left off, which is a smaller change than a chooser.

The second soft spot is the in-place update. Writing only the rationale is the safe choice and it quietly throws away most of what a dive learned. A dive that settles Whose Hands knows the persona ids; the Features entry keeps whatever was guessed when the feature was first written.

78/100

High on Phase 1 — the duplication defect is confirmed by reading the code, and the fix has one shape. High on the queue-reuse approach, which removes more work than it adds. The seventy-eight rather than something higher is the front-door question: the recommendation here preserves the Lab’s premise at a real cost to the case where you already know what you want to dive on, and that trade has not been tested against use. The layout phase is a placeholder, so a sixth of the plan is not yet specified.