130 of 162 tasks done · 14 phases · ~40 files · 2026-08-26

Mira’s Room

Move Mira out of the 400pt inspector panel and into the full-window destination she was originally designed as, give every conversation a shape through first-class segments, and reassign whispers from errands to findings.

130/162Tasks done
8Phases
7New files
3Triggers retired
1Friction entry in 3.5 months
The gist

Mira shipped in May as a persistent 1:1 development partner and has not entered regular use. This plan changes three things: the container becomes a full-window Room rather than a docked inspector, each conversation becomes a bounded segment with a beginning, an end, and an artifact, and whispers stop handing out chores. A card deck at the door replaces the empty chat box, so arriving at Mira means choosing how you want to work rather than staring at a prompt.

The problem

MiraContainerView’s own header comment still reads “Mira’s Room — full-screen destination per project. Header with 88px avatar.” The full-window Room was the original design and was compressed into a macOS .inspector panel. That compression is visible in the interior: the Conversation / Mira-led control is a two-tab toggle carrying an explicit comment saying it is a toggle rather than a side-by-side split because it has to render at 320pt.

Width is not the whole story. MiraConversationView was forked from DeepDiveConversationView, so it already has the transcript, the token streaming and the attachments. What it lacks is everything that makes Deep Dive read as a 1:1 — the pacing choreography, the chapter dividers at move boundaries, the idle suggestion affordance, and a process that begins and ends. Mira’s thread has no beginning, no end, and no product.

The whispers compound it. All four bodies are hardcoded templates that turn an observation into an assignment:

Shipped copy

workflowTransition — “All implementation tasks on X are done. Want to run /cr-entry or /cr-complete?”
planIdle — “X is 70% done and hasn’t seen a checkin in a couple days. Want to talk it through?”
repeatedConcern — “3 recent checkins on X all touched Foo.swift. Worth a closer look?”
contradiction — “Before you commit to this — 4 days ago you decided something that points the other way. Worth a look first.”

Each one ends in a question that hands over more work. None of them says anything. Every whisper body carries a comment promising LLM-generated copy honoring the Whisper voice register in a later phase; that phase never shipped, so all four speak template English wearing a person’s name.

The evidence that none of this is working sits in .claude/mira-friction.md, the dogfood log gating every deferred Mira v2 phase. It holds one entry, dated 2026-05-09, tagged [good]. The last Mira commit is 2026-08-15.

Approach

A studio container, matching Ideation, Design and Launch

The Room becomes a project-scoped destination reached from the ambient anchor, ⌘⇧M, and a Project Overview entry point. It gets the full detail column with an 88px avatar header and a real side-by-side split — conversation left, Mira-led right — instead of a segmented toggle forced by a 320pt panel.

One thread per project, with first-class segments

Rejected: separate sessions per card. MiraThread carries a single cliSessionId that has been accumulating project context for months, and that accumulation is what produced the single response worth logging — Mira picking one Chronicle record out of 133 and naming the 4,000px threshold from memory. Separate sessions start cold and re-inject context on every card click.

The target model is already written down. MiraParkedTopic’s header comment says “Mira’s thread is one unbroken stream per project, so this captures a topic within that stream rather than closing a conversation.” A segment generalizes a parked topic from retroactive-and-optional to declared-up-front-and-structural.

Cards inject framing, not isolation

A card that changes how Mira opens injects a framing preamble on the user turn, the way buildParkedTopicResumePrompt already does and the way voiceOverlay(for:) already switches register mid-thread. No card needs its own CLI session, its own store, or its own recovery path.

Whispers carry findings; the rest moves to surfaces that already exist

Three of the four triggers count rather than read — task percentages, elapsed hours, file-repeat tallies — and counting can only produce a reminder. Workflow transitions belong to the pipeline stepper, which exists for exactly that. Plan idleness belongs in the session digest. Contradiction stays live because it is the one case where timing matters.

Closed segments collapse, which is what makes one thread survivable

A closed segment renders as a single row — title, date, artifact chip — expanding on click. That converts an unbounded transcript into a browsable history of bounded conversations, and it is the direct answer to the concern that argued for separate sessions in the first place.

Decisions made for you

Judgment call

MiraParkedTopicStore folds into the segment store

Two overlapping models of “a topic within the thread” will drift. Parked topics migrate to segments with state .parked, carrying their recaps and open questions. Reversible by keeping both stores and having segments reference topic ids, at the cost of maintaining both.

Judgment call

RepeatedConcernTrigger is deleted rather than repaired

“Recent checkins all touched Foo.swift” is nearly always true of the file you are actively working in, so the trigger flags your main file as a concern. The signal worth keeping — the same concern described twice — requires an LLM read over checkin narratives and is deferred to Pass 2.

Judgment call

The 90-second whisper auto-tuck is removed

A card that interrupts and then leaves costs attention twice. With a global two-per-week budget in place, a whisper that survives to the screen is rare enough to wait for you.

Judgment call

Three of six cards ship visibly disabled

Run it without me, Show me what’s drifted and Tell me what you’d do next render at reduced opacity with a reason on hover rather than being absent. The deck reads as a designed set from the first night, and the Pass 2 scope is visible rather than implied.

The work

Phase 1 — Segment model, store, and migration

12 tasks 12 of 12 complete
  • Create Code Raven/Models/MiraSegment.swift with MiraSegmentKind (.ongoing, .brainstorm, .meeting, .drift, .direction, .resumed), each with label, systemImage, and accent: Color
  • Add MiraSegmentState (.open, .parked, .closed) to the same file
  • Add MiraSegmentArtifact — an enum wrapping .meeting(UUID), .planDraft(UUID), .scratchNote(UUID), .none — so a closed segment can point at what it produced
  • Define struct MiraSegment: Codable, Identifiable, Equatable with id, projectId, title, kind, startTurnId, endTurnId: UUID?, state, artifact, recap: String, openQuestions: [String], openedAt, updatedAt, lastResumedAt: UUID?
  • Give MiraSegment a custom init(from decoder:) using decodeIfPresent ?? default on every field per project CLAUDE.md — synthesized Codable treats a defaulted non-optional as required and would throw keyNotFound on older files
  • Create Code Raven/Services/Mira/MiraSegmentStore.swift as @MainActor final class MiraSegmentStore: ObservableObject with static let shared
  • Storage path ~/Library/Application Support/Code Raven/Mira/segments/{projectId}.json, writes on a serial utility ioQueue, mirroring MiraThreadStore
  • Implement segments(for:), openSegment(for:), open(kind:title:startTurnId:projectId:), close(_:artifact:recap:), park(_:), resume(_:), update(_:)
  • Enforce one open segment per project — open(...) closes any currently-open segment before creating the new one
  • Implement MiraSegmentMigration.runIfNeeded() converting every MiraParkedTopic to a MiraSegment with kind: .resumed, state: .parked, startTurnId = first sourceTurnIds entry, endTurnId = last, carrying recap and openQuestions across
  • Guard the migration behind a UserDefaults version key so it runs once per install
  • Preserve exportedNoteId as .scratchNote(id) on the migrated segment's artifact

Phase 2 — Card deck

8 tasks 8 of 8 complete
  • Create Code Raven/Models/MiraCard.swift defining MiraCard with id, title, blurb, systemImage, accent, kind: MiraSegmentKind, and availability: MiraCardAvailability
  • Add MiraCardAvailability (.ready, .comingInPass2(String)) so Pass 2 cards render visibly disabled with a reason rather than being absent
  • Populate MiraCard.deck with six cards phrased as ways of working: Think out loud with me (brainstorm), Put a room together (meeting), Pick up where we left off (resumed), Run it without me (Pass 2), Show me what's drifted (Pass 2), Tell me what you'd do next (Pass 2)
  • Create Code Raven/Views/Mira/MiraCardDeck.swift rendering a LazyVGrid of MiraCardTiles, two or three columns depending on available width
  • MiraCardTile: 3px top accent bar, title at 13pt medium, blurb at 11pt secondary, hover CTA fading 0→1, matching MeetingGroupCard's established card grammar
  • Disabled tiles render at reduced opacity with the comingInPass2 reason as a .help tooltip
  • "Pick up where we left off" renders a compact list of parked segments beneath it when any exist, each resumable in one click
  • Deck header reads the project name and the relative time of the last conversation, so arriving tells you where you left things

Phase 3 — The Room and its routing

10 tasks 9 of 10 complete
  • Create Code Raven/Views/Mira/MiraRoomView.swift — the full-window destination, project-scoped
  • Room layout: AppNavBar-height custom header with 88px MiraAvatar, project name, "Mira", and the single-line state copy, matching the size the original header comment specifies
  • Body is an HStack split: conversation on the left at minWidth: 480, Mira-led pane on the right at a fixed 340pt, divided by a cmBorderPrimary hairline
  • Collapse the right pane to zero width below a 900pt total width and restore the segmented toggle only in that narrow case
  • Arrival state: when the open segment is nil, the conversation column shows MiraCardDeck above the transcript rather than replacing it, so history stays visible
  • Add case mira(Project) to AppMainView.MainContentView
  • Route .mira(let project) in mainContent to MiraRoomView(project:) with .id(project.id)
  • Add an onOpenMira callback to ProjectOverviewView's initializer and wire it to selectedView = .mira(project) — NOT DONE, superseded: the entry point posts .navigateToMiraRoom directly instead, which avoids threading a callback through seven-plus call sites
  • Add a "Mira" entry point in ProjectOverviewView's header area beside the existing Foundation and Chronicle affordances
  • Extend handleAppNavTabChange so leaving the Room returns to .projectOverview(project) — DEFERRED: the Room has no back affordance yet; you leave via the sidebar

Phase 4 — Retire the panel

12 tasks 10 of 12 complete
  • Delete the .inspector modifier block from MiraPanelHost and the inspectorPresented binding that drives it
  • Delete MiraFloatingWrapper and the .overlay(alignment: .topTrailing) that hosts it
  • Keep the VStack of content + MiraStatusBar, and keep the whisper card overlay
  • Remove mode, detach(), dock(), floatingOffset, floatingSize, and dockedWidth from MiraPanelManager, along with the PanelMode enum
  • Replace MiraPanelManager.open(projectId:projectName:) with requestRoom(projectId:) publishing a navigation intent rather than mutating isOpen
  • Add NavigateToMiraRoom as a Notification.Name following the existing NavigateToReferenceArtifacts pattern, and observe it in AppMainView
  • Point MiraAmbientAnchor.togglePanel() and the ⌘⇧M shortcut at the room navigation intent
  • Point the whisper card's "Talk it through" at the same intent, preserving engageWhisper's seeded citation turn
  • Make panelContext on MiraContainerView non-optional-free by deleting the PanelContext enum and every branch that reads it
  • Keep MiraContainerView compiling as the narrow-width fallback body used by MiraRoomView, or fold it into MiraRoomView if no caller remains — folded in and the file deleted; MiraRoomView carries the narrow-width toggle itself
  • Delete the now-unreferenced PriorDecisionHostPayload sheet plumbing from MiraPanelHost and re-present PriorDecisionSheet from the Room — NOT DONE, still referenced: the interrupt whisper's "View the prior decision" CTA fires from the host, where the card lives
  • Remove the ProjectOverviewView.swift:939 fixed-column-width workaround comment and the widths it justifies, since no inspector reflows that row any more — DEFERRED: cosmetic, and touching that row's layout risks a regression worth its own verification pass

Phase 5 — Chapters and pacing

10 tasks 10 of 10 complete
  • Create Code Raven/Views/Mira/MiraSegmentDivider.swift — a centered rule with a label, ported from DeepDiveConversationView's move-boundary divider
  • Render a divider in MiraConversationView at each segment's startTurnId, showing kind icon, title, and relative date
  • Render closed segments collapsed to a single row — title, date, artifact chip — expanding in place on click
  • Keep the open segment and all turns after it expanded by default
  • Create Code Raven/Services/Mira/PacedTurnScript.swift collecting the Ideation delay choreography: acknowledgmentBeat() returning Double.random(in: 0.8...1.2), questionDelay at 0.3, sectionDwell at 1.5
  • Apply acknowledgmentBeat() before Mira's first token renders on a card-launched segment so the opening does not arrive instantly
  • Port the idle suggestion affordance from InterviewTextInput into the Room composer, firing on a randomized 10–15s idle and dismissing on any keystroke
  • Give the idle affordance a Mira-specific provider drawing from the open segment's openQuestions rather than SparkExampleBank
  • Add .lineSpacing(4) to assistant turn bodies in MiraConversationView, which Ideation's V2 bubbles omit and which materially affects long-form readability
  • Replace the three hardcoded Color.white values inherited from DeepDiveConversationView with cmBgPrimary — NOT APPLICABLE: the fork never inherited them; the only .white in MiraConversationView is badge text on a filled capsule, which is correct

Phase 6 — Whisper reassignment

10 tasks 9 of 10 complete
  • Delete WorkflowTransitionTrigger from MiraWhisperCoordinator's registered set; the workflow pipeline stepper already owns stage transitions
  • Delete PlanIdleTrigger from the registered set and route its signal into the session digest instead
  • Delete RepeatedConcernTrigger outright — counting file repeats flags whichever file you are actively working in
  • Keep ContradictionTrigger as the only live interrupt
  • Add MiraWhisperBudget to MiraWhisperCoordinator: a global cap of two surfaced whispers per rolling seven days across all triggers, persisted beside WhisperSuppressionStore
  • Enforce the budget in the coordinator before any trigger evaluates, so per-trigger cooldowns stop being the only bound
  • Add a "Stop telling me this" action to the whisper card that writes a permanent suppression for the trigger id with no scope, mirroring the editorial layer's "Silence this"
  • Remove the 90-second auto-tuck; the card persists until dismissed or engaged
  • Surface retired-trigger signals in MiraLedPaneView under the existing criticality bands rather than dropping them — NOT DONE: the three retired signals are currently dropped, not rehomed. Workflow transitions remain visible in the pipeline stepper; plan idleness is not surfaced anywhere right now
  • Delete the /cr-entry and /cr-complete command buttons from MiraWhisperCard along with workflowCommandAction, since the trigger that showed them is gone

Phase 7 — Wire the three ready cards

7 tasks 6 of 7 complete
  • Think out loud with me — opens a .brainstorm segment, injects a framing preamble asking Mira to open with an observation rather than a question, appends her opening as the segment's first turn
  • Put a room together — opens a .meeting segment, asks Mira to propose a roster from the ten PreComposedGroup cards plus a topic, renders her proposal as a turn with a Convene action
  • Convene from that turn presents StandupSetupSheet prefilled with Mira's proposed agentIds and topic, reusing the existing MeetingSetupPayload path
  • On meeting conclusion, close the segment with artifact: .meeting(id) and append Mira's synthesis as an isUnprompted turn — NOT DONE: MiraMeetingHandoff carries the segmentId across, but nothing consumes it at conclusion yet, so a convened meeting leaves its segment open and unlinked
  • Pick up where we left off — resumes a parked segment through MiraSegmentStore.resume(_:), appending the recap as an unprompted turn, replacing the current resumeParkedTopic call path
  • Add buildSegmentOpeningPrompt(kind:projectId:) to MiraPromptLibrary producing the per-card framing preamble
  • Close the open segment automatically when a different card is chosen, drafting a recap via the existing draftParkedTopic extraction

Phase 8 — Build and verify

4 tasks 4 of 4 complete
  • xcodebuild -scheme "Code Raven" -destination 'platform=macOS' build returns success
  • scripts/verify-build-product.sh confirms a fresh binary, since BUILD SUCCEEDED alone is not a witness
  • Grep for every deleted symbol (MiraFloatingWrapper, PanelContext, dockedWidth, RepeatedConcernTrigger) and confirm no references remain
  • Confirm MiraSegmentMigration runs once against a real project and that existing parked topics survive with their recaps intact — UNVERIFIED at runtime; compiles and is called from both the Room's onAppear and the park sheet's save

Phase 9 — Review-pass fixes

9 tasks 9 of 9 complete
  • Shrink the Room header from an 88px avatar to 40px — at real window size it reserved a band of vertical space for identity already known
  • Rename the header's "Set aside" to "Shelf" with a tooltip naming what it holds; the old label read as a verb with no object
  • Stop the card deck and the transcript competing for the column — the deck owns it when expanded, collapses to a one-line bar once a conversation exists, and auto-collapses when a card is chosen
  • Label the composer's @ and bookmark buttons ("Ask an expert", "Shelve this") with tooltips explaining what each does
  • Fix deferralCount: it incremented on firstTriagedSession < session alone, which counts app launches rather than deferrals, and >= 4 force-escalates to .critical and pins .persistedCritical forever
  • Add MiraTriageItem.lastCountedProducedAt so a deferral requires an agent to have actually re-reported the finding
  • Add MiraDeferralRepair to zero out counts already inflated past the escalation threshold
  • Add dedup to MiraDraftPlanStore.add — it inserted unconditionally while both finding stores dedup on stableKey, so two fixture invocations produced two indistinguishable records
  • Surface MiraDraftPlanStore.delete, which had existed with zero call sites, as a right-click action on the draft row

Phase 10 — Handoff reports

18 tasks 17 of 18 complete
  • Define MiraHandoff — title, purpose, outcome, confidence + reason, benefits, risks, actions, source finding keys, sent state
  • Define MiraHandoffAction with detail for drill-down, sourceFindingKeys for provenance, and isIncluded so items can be trimmed before sending
  • Define MiraHandoffReport and MiraHandoffSource (.backlog / .meeting), all with defensive decoders
  • Create MiraHandoffStore — per-project JSON at ~/Library/Application Support/Code Raven/Mira/handoffs/{projectId}.json, reports accumulate rather than replace
  • Create MiraHandoffGenerator with generateFromBacklog and generateFromMeeting, both fresh one-shot sessions rather than resuming Mira's thread
  • Add buildBacklogHandoffPrompt and buildMeetingHandoffPrompt to MiraPromptLibrary, sharing one handoff contract
  • Cap the meeting prompt at the last 24 messages so a long meeting cannot blow the context
  • Build MiraHandoffReportView — per-handoff card with confidence badge, gains/costs columns, and a checkable action list
  • Drill-down on an action reveals its detail plus the original agent text of its source findings
  • Send routes through ScratchNote → NotePlanningPromptBuilder → CopyPromptPreviewSheet, reusing the CR-NOTE plan-capture pipeline so a sent handoff becomes a tracked plan
  • Excluded actions are dropped from the composed prompt rather than listed as skipped
  • Add the backlog entry point to MiraLedPaneView, above the findings list
  • Add "Ask Mira for handoffs" to MeetingHandoffSheet so any concluded meeting can produce a report
  • Add a per-handoff notes field, edited inline and bundled into the sent prompt above the checklist so it reads as constraint rather than footnote
  • Commit note drafts on focus loss rather than per keystroke, and flush an uncommitted draft before sending
  • Surface every report holding unsent handoffs, not only the newest — showing latestReport alone made an older report unreachable the moment a new one was generated
  • Keep fully-sent reports reachable behind a "finished reports" disclosure, with a right-click delete on any row
  • Verify a generated report at runtime — the generator, its JSON contract, and the send path have never been exercised against a live model

Phase 11 — Coherence, voice, and pane hierarchy

13 tasks 11 of 13 complete
  • Retire a sent handoff's source findings so the backlog shrinks as you act — without it, regenerating re-clusters work already sent
  • Add retireItemIntoHandoff rather than reusing acceptItem, which mints one Mira-led task per finding and would recreate the pile-up handoffs exist to prevent
  • Add an uncovered-findings signal to the generate button so the app says when a new report is worth making, rather than leaving it to judgment
  • Give Mira awareness of her own handoffs via handoffContextSummary, injected beside the triage summary
  • Give Mira awareness of her own segments via segmentContextSummary, so she knows which conversation she is inside and what is set aside
  • Invert the Mira-led pane: handoff reports lead, raw findings collapse behind a disclosure once anything has been grouped
  • Keep the raw findings leading when no report exists yet, since then they are the only thing to act on
  • Add buildWhisperVoicePrompt wiring the long-unused whisperVoiceOverlay / interruptVoiceOverlay registers
  • Add MiraWhisperVoicer — a one-turn pass that rewrites a trigger's template body in Mira's voice, with a 25s timeout and fallback to the template
  • Sanitize voiced output: reject empty, over-length, fenced, or bulleted responses that would read worse than the template
  • Charge the whisper budget at detection rather than at surfacing, so a voicing failure cannot retry past the cap
  • Register mira as a subsystem in CodeRaven's Blueprints view so a blueprint can be generated — BLOCKED: /cr-blueprint validates against the project's stored subsystem list and mira is absent, so generating now would fail on import
  • Generate the Mira blueprint once the subsystem is registered

Phase 12 — The three deferred cards

18 tasks 16 of 18 complete
  • Tell me what you'd do next — MiraDirectionGenerator produces a real MiraPlanDraft with decision forks, giving that review flow its first producer since the DEBUG fixture
  • Validate forks on parse: choice kinds need two or more options, freeText must carry none, and a fork violating either is dropped rather than made unanswerable
  • Resolve recommendedOptionLabel to an option id on this side, so the model never has to invent UUIDs
  • Route the direction card into MiraDraftReviewSheet with send / send-with-concerns / reject / discuss wired to MiraDraftCoordinator
  • Add a per-tile busy state to the card deck so a card taking half a minute is not a click that appears to do nothing
  • Run it without me — MiraDelegatedMeetingRunner starts a standup, lets it run unattended, concludes it, and posts Mira's account back as an unprompted turn
  • Validate the roster is 2–4 agents from the live project before startMeeting, since StandupMeeting.init enforces that with a precondition that crashes the app
  • Guard against clobbering an in-progress meeting — startMeeting overwrites activeMeeting unconditionally and only one meeting exists globally
  • Poll for the loop's terminal state rather than observing isGenerating, whose early-return paths leave it true, with a 90-minute wall-clock backstop
  • Restrict delegation to .standup — .consultation stops after one turn by design and .viewEvaluation blocks on a human advancing views
  • Generate a handoff report from the concluded meeting so the result is work to act on rather than a transcript to read
  • Show me what's drifted — repair FoundationStalenessDetector first: per-layer timestamps from each layer's own file rather than one manager-wide lastSavedAt
  • Add file patterns for .visualDesign, .cognitive, and .presence, which had none and so could never be flagged
  • Drop the three-changed-file floor that made a single important change invisible
  • Return not-stale for a Foundation with no generated layers, instead of nine critical layers at 999 days
  • Add MiraDriftScanner ranking blueprint fingerprint drift above time decay, and feed the result into her opening so she leads with what matters rather than listing everything
  • Verify the delegated meeting end to end — it has never run unattended against a live model
  • Verify a generated direction draft promotes cleanly through MiraDraftCoordinator.send

Phase 13 — Polish pass

11 tasks 9 of 11 complete
  • Enhance the generated custom-mira blueprint: correct the finding-provenance error (findings come from agent runs via FindingsIngestor, never from MeetingFlagBridge), drop the removed MiraPanelManager.open/toggle entry points, and add fourteen gotchas earned this session
  • Correct expert-meetings.md, which warned about a concludePausedMeeting state swap removed in commit c059088 — the same stale claim I repeated as fact earlier
  • Add the real meeting hazards to that blueprint: the 2–4 agent precondition, startMeeting's unconditional clobber, and the absent completion signal
  • Correct foundation-layer.md, whose four documented FoundationStalenessDetector defects are now all fixed
  • Surface service errors in the Room — MiraService, MiraDirectionGenerator, MiraDelegatedMeetingRunner and MiraHandoffGenerator all set error and nothing rendered any of them, so a failed turn looked identical to nothing happening
  • Add a back affordance to the Room (⌘[) — it is a full-window destination with no chrome, so the sidebar was the only way out
  • Add MiraQuietSignals and a "Sitting quietly" section, rehoming the plan-idle and ready-to-close signals the retired whisper triggers used to carry
  • Close the segment when convening a meeting, so a thread does not keep landing turns inside a segment named "Convening" forever
  • Fix the resume card reading MiraParkedTopicStore while the list beneath it reads MiraSegmentStore — it now resumes the newest parked segment
  • Reconcile the header's Shelf button, which still browses parked topics rather than segments — two stores showing the same migrated records
  • Link a convened meeting back to its segment's artifact; MiraMeetingHandoff carries the segmentId but nothing consumes it at conclusion

Manual Testing

20 tasks yours, in the running app
  • Open a project and press ⌘⇧M — the Room opens full-window rather than a panel sliding inuser
  • Confirm the ambient anchor and status bar still render on every other viewuser
  • Click Think out loud with me and confirm a segment divider appears and Mira opens with an observationuser
  • Click a second card and confirm the first segment closes with a recap and collapsesuser
  • Confirm parked topics from before the update appear under "Pick up where we left off"user
  • Leave the composer idle for fifteen seconds and confirm the suggestion fades inuser
  • Confirm no whisper appears for a plan whose implementation tasks just hit 100%user
  • Click "Turn these into handoffs" and confirm Mira returns three to six coherent groups rather than a restatement of the listuser
  • Uncheck an action, send the handoff, and confirm the excluded item is absent from the promptuser
  • Confirm a sent handoff creates a Scratchpad note tagged handoff/mira and that the resulting plan links back to ituser
  • Confirm the triage list comes back around 3 critical / 17 standard / 4 ambient after the deferral repair runsuser
  • Ask Mira "what are we working on" and confirm she names her handoffs, not just raw findingsuser
  • Send a handoff and confirm its findings leave the triage list and the generate button's count dropsuser
  • Confirm the findings list arrives collapsed once a report exists, and expands on clickuser
  • Click Run it without me and confirm the tile reports convening → talking → wrapping up, and that Mira posts her account when it landsuser
  • Click Show me what's drifted and confirm she names one specific stale document rather than listing everythinguser
  • Confirm a brand-new project reports no Foundation drift rather than nine critical layersuser
  • Press ⌘[ in the Room and confirm it returns to the project overviewuser
  • Confirm a failed card surfaces a dismissible error banner rather than failing silentlyuser
  • Confirm "Sitting quietly" lists any plan past 70% that has gone two days without a checkinuser

Risks & edge cases

Could bite

The live meeting transcript is not render-optimized

StandupMeetingView's live message list is not Equatable-optimized; only the concluded MeetingTranscriptView is. Every state change re-renders every bubble. Pass 1 keeps meetings in their existing view, so this does not bite yet. It becomes the first problem the moment the Room hosts a live meeting inline.

Edge case

Segment boundaries when you keep typing

A card opens a segment, but nothing stops the conversation from wandering past whatever the card was for. Pass 1 closes a segment only when another card is chosen. A conversation that drifts for a week lives in one enormous .brainstorm segment, which is no worse than today's single stream but is not the intended shape.

Edge case

The migration is one-way

MiraSegmentMigration converts parked topics into segments and the parked-topic store stops being written. Rolling back to a prior build leaves topics created after the migration invisible. The topics themselves are not deleted, so the data survives; only the new ones are unreachable from the old UI.

Thin

The whisper budget can silence a real contradiction

A global two-per-week cap means a third genuine contradiction in one week does not surface. With three of four triggers retired, contradiction is the only consumer of the budget, so exhausting it requires three real reversals in seven days. The mitigation is that suppressed whispers still land in the Mira-led pane.

Known-broken dependency

Foundation staleness detection cannot be trusted

foundation-layer.md:184-187 documents that FoundationStalenessDetector uses one manager-wide lastSavedAt for all nine layers, maps no file patterns to .visualDesign / .cognitive / .presence, requires three changed files so a single important change never fires, and reports a brand-new project as all-nine-stale at 999 days old. This is exactly why Show me what's drifted is deferred rather than shipped disabled-in-name-only.

Out of scope

Run it without me — the faceless meeting

Roughly 80% built already: the standup loop is autonomous between user messages, closing the meeting view does not cancel conversationTask, and concludePausedMeeting() produces the full artifact set with no view mounted. Missing are a one-shot start→run→conclude entry point and a landing place for the result. Deferred because concludePausedMeeting() temporarily mutates activeMeeting, meetingAgents and projectPath before restoring them, and a card whose whole premise is that you walk away makes concurrent meetings the normal case rather than the rare one. That needs its own concurrency pass.

Show me what's drifted

BlueprintStalenessDetector is solid — SHA fingerprint drift, real changed-file lists, human-readable recommendation strings. Foundation staleness is not, per the risk above. Shipping a cross-subsystem view on half-trustworthy signal produces a card that lies on day one.

Tell me what you'd do next

This is the missing producer for machinery that already shipped: MiraPlanDraft and the whole MiraDraftCoordinator send / send-with-concerns / discuss / reject flow exist, and the only thing that ever creates a draft is MiraDraftPlanFixture in the DEBUG menu. Worth doing properly rather than quickly.

LLM-generated whisper copy (the never-shipped Phase 5)

All four whisper bodies carry a comment promising copy generated through the Whisper voice register. The overlays exist in MiraPromptLibrary and no trigger uses them. With only contradiction surviving, one template is tolerable for a night. It is the first thing to fix in Pass 2.

Cross-project Mira, avatar animation, and voice interaction

Already deferred to v3+ by the existing v2 plan. Nothing here changes that.

Final review

What this does well. It removes a container that was never the intended one and restores the design the code still documents in its own header comment. The segment model is not invented; it generalizes a concept MiraParkedTopic already describes, which means the migration is a widening rather than a replacement. Retiring three whisper triggers is mostly deletion, and deletion is the cheapest reliable improvement available here.

Where it is weakest. Phase 4 is a wide deletion across MiraPanelHost, MiraPanelManager, MiraContainerView and MiraAmbientAnchor, and those four files are mutually referential. If anything in this plan overruns, it is that phase. Phase 5's port of the idle-suggestion affordance depends on InterviewTextInput, which hardwires SparkCategory, SparkExampleBank and ScratchpadManager; generalizing it properly wants a provider protocol that this plan only gestures at.

What I would adjust if pushed. Phases 1, 2 and 3 could ship alone and would already deliver the change that matters most — Mira as a place you go, with a door that offers you something. If the night runs short, Phase 4 is the one to split, keeping the panel alive alongside the Room until the deletion can be done carefully.

83/100

High on the diagnosis, because the code states the intended design in its own comments and the empty friction log corroborates it. High on the segment model, because the constraint that decides it is concrete — one cliSessionId carrying months of accumulated project context — and the target shape is already written down in MiraParkedTopic. The missing 17 is mostly Phase 4's blast radius across four mutually referential files, and the open question of when a segment should close on its own rather than waiting for the next card.