- Empty Done immediate transition now sets node.status to resolved
alongside resolvedNodeIds/doneForNowIds — same canonical parked
shape as populated Done (no server call required)
- Clarified-question Re-open removes target from doneForNowIds so
the question visibly returns to Open Questions
- Immediate graph mutation creates new node objects immutably
(React state semantics), touching only the target node
- FocusedQuestionBody derives thread-local contribution subset using
targetNodeId || originatingTargetNodeId matching
- hasCompletedContext, latest completed contrib, and all effective
presentation fallbacks use scoped collection only
- scenario-wide focusedContributions history preserved in memory
- Fresh Question B no longer bleeds Question A's content across
every presentation surface (Previously answered, What this tells us,
Still unclear, Questions this raises, Assumptions, Connections)
- Reopening or revisiting Question A still uses its own history
- Targeted regression: 3 new Vitest cases pass
- Handoff docs updated with v0.52 correction record
Move the milestone invitation from after Clarified Questions to occupy
the same spatial position as Open Questions — between Current Understanding
and Questions we have clarified. Uses ternary: openUnknowns > 0 ? OpenQuestionsUI : milestoneAllowed ? MilestoneInvitation : null, followed by ClarifiedQuestionsUI unconditionally. No duplication of clarified cards or Re-open controls.
Adopt executeEpisodeDone orchestration as the canonical path for
'Done for now' activity boundary: one user Done triggers exactly
prepareCompletedEpisode -> reconsiderCompletedEpisode -> applyValidatedProposal
-> Current Understanding synthesis -> leave focused workspace.
Production changes (components/scenario-form.jsx):
- Add prepareCompletedEpisode, reconsiderCompletedEpisode, applyValidatedProposal imports
- Export executeEpisodeDone({params}) with all 4 domain functions as named
parameters (defaults to module exports) for deterministic test wiring
- Rewrite handleDoneForNowPromotion(targetNodeId) as async: delegates to
executeEpisodeDone pipeline; CU synthesis installed only on success
- Add doneInProgressRef useRef(false) for exactly-once Done enforcement
- On synthesis failure: KEEP updated graph, KEEP Findings, KEEP existing CU
- Retire produceFindingInformedSummary from ScenarioForm (legacy CU writer)
- Remove legacy idempotence guard and Evidence:[] regex dedup
Test changes (tests/ui/scenario-form-episode-done.test.jsx):
- 9 tests verifying orchestration pipeline correctness:
1. Successful path order: prepare -> reconsider -> apply -> synthesis
2. Correct prepared episode input parameters
3. Structured application evidence (no answer fields in context)
4. nextGraph used for synthesis (not stale result state)
5. Reasoning failure: apply not called, CU synthesis not called
6. Application failure: CU synthesis not called, graph not replaced
7. Synthesis failure: nextGraph remains installed (no rollback)
8. Exactly-once per call for each domain function
9. Legacy Done writer retired (pipeline does not produce deterministic summary)
Architecture: after successful /api/cases/update, derive explicit nextGraph +
nextFindings, call synthesizeFromFindings exactly once, replace Current
Understanding with reconstruction result.
Key invariants:
- outcome.summary retired as final CU authority → always synthesis reconstruction
- Explicit derived state (no React-state reread) for graph and findings
- Previous CU preserved on synthesis failure (no fallback to outcome.summary)
- Graph and Findings NOT lost on synthesis failure
- saveInvestigation persistence uses currentUnderstanding, not outcome.summary
Deterministic regression: 7 tests (Cases A-E + 2 edges) covering all rules.
Files: components/scenario-form.jsx, tests/ui/scenario-form-case-update-synthesis.test.jsx
Repair LOCATION-A defect where processing feedback rendered near the
completed narrative instead of inside the active follow-up block.
Changes:
- components/reasoning-workspace.jsx: three targeted edits using a single
spinner component with conditional rendering; hasActiveFollowUp routes
ownership to the correct container
- tests/open-questions-vs-assumptions.test.jsx: regression test confirming
exactly one indicator, DOM child of follow-up-block, ownership separation
Accepted criteria met:
✅ Exactly one processing indicator during follow-up processing
✅ Indicator is a DOM child of follow-up-block
✅ Top-level indicator suppressed when follow-up active
✅ Initial answer flow preserved (top-level when no follow-up)
✅ Successful follow-up promotion intact
✅ All existing context retained
✅ No new state/lifecycle changes/error redesign
Two presentation fixes (no reasoning-engine changes):
A. Follow-up answer continuity — selectFollowUpQuestion clears focused.answer,
which previously caused the completed-narrative framing ('Previously answered'
and 'Your response') to disappear mid-investigation. The guard now treats
a non-null result as sufficient evidence of a completed-context state, so the
user's verbatim answer and derived findings remain visible while a follow-up is
being formulated.
B. Prior-contributions chronology — display order in 'Previous learning' panels
has been reversed at the presentation boundary (newest → oldest). This means
users see the most recently learned evidence first, without modifying data-order
anywhere else. Applies to both PriorContributionsSummary and
SecondaryPreviousLearning.
When a reopened completed turn is displayed, distinguish it from an active question:
- 'PREVIOUSLY ANSWERED' + 'YOUR RESPONSE' headings for completed turns (hasAnswer=true)
- Bare 'QUESTION' heading preserved for active follow-ups (answer=null)
- Verbatim user answer rendered under its own heading — never conflated with Engine-derived findings
- Causal narrative: Question → Your response → What this tells us
Gate results:
- 102 tests passed (78 existing + 24 new v0.49 provenance narrative tests)
- Clean production build
- Live verification on localhost:3000 confirmed correct rendering
ThreadContributionsBadge, PriorContributionsSummary, and
SecondaryPreviousLearning all filtered contributions via
c.targetNodeId === nodeId. Multi-turn follow-up Contributions carry a
different immediate targetNodeId while the canonical origin remains on
Findings (originatingTargetNodeId).
Repaired: all contribution filters now match on EITHER
c.targetNodeId === nodeId || c.originatingTargetNodeId === nodeId.
handleDeconstructSubmit carries originatingTargetNodeId from
focusedPresentationItemId as provenance for cold-return recovery.
- Document live Playwright verification of amber INVESTIGATING indicator
on Open Questions cards with matching contribution targetNodeId
- Document 11 new deterministic tests in focused-investigation-history describe block
- Confirm UNCLEAR and INVESTIGATING coexist independently (epistemic vs activity)
- Note that cue only renders inside OpenQuestionsPanel, not initial reflection surface
- ThreadContributionsBadge (rendered per-node on OpenQuestionsPanel
cards and Done-for-now cards) now shows an amber INVESTIGATING
indicator when the node has matching contributions via targetNodeId
identity match.
- UNCLEAR and INVESTIGATING cues coexist independently on the same
card — UNCLEAR is epistemic state, INVESTIGATING is activity cue.
- Deterministic test suite added: focused-investigation-history (11
tests) covering identity matching, zero-contrib edge cases,
multiple-contrib coalescing, done-for-now retention, and uncoupling
from UNCLEAR state.
- All 57 tests pass.
Record: v0.48 persistence objective complete; Finding eligibility resolved;
next boundary is isolated Finding-informed Current Understanding (feature/
finding-informed-understanding-v0.49); broader Finding-system questions
intentionally deferred. No production or test changes.
Verified: deriveFindingsFromContributions() exists with passing tests but
is never called in production. The contributions → findings seam is un-wired:
1. handleDeconstructSubmit() sends contribution to ScenarioForm
2. appendFocusedContribution() stores it in focusedContributions[]
3. findings state stays [] — no derivation ever runs
4. empty findings sent to /api/cases/update (which only echoes them back)
5. nothing renders from the findings surface
Fix: call deriveFindingsFromContributions after contribution is appended.