diff --git a/components/reasoning-workspace.jsx b/components/reasoning-workspace.jsx
index efff9a7..3d0daed 100644
--- a/components/reasoning-workspace.jsx
+++ b/components/reasoning-workspace.jsx
@@ -229,19 +229,19 @@ function FocusedQuestionBody({
) : null}
{focused?.question?.trim() && processingStep !== "active" && focused.status === "formulated" && !hasAnswer && !hasActiveFollowUp && (
-
+
)}
- {processingStep === "active" && (
-
+ {processingStep === "active" && !hasActiveFollowUp && (
+
Processing:
{deconstructMsg}
-
+
)}
{(hasActiveFollowUp || hasCompletedContext) && (
@@ -343,7 +343,7 @@ function FocusedQuestionBody({
{/* In-place answer textarea for the active follow-up */}
{hasActiveFollowUp ? (
-
diff --git a/docs/current-handoff.md b/docs/current-handoff.md
index 941c18b..6a25e34 100644
--- a/docs/current-handoff.md
+++ b/docs/current-handoff.md
@@ -1191,6 +1191,99 @@ Follow-up question formulation, deconstruction logic, SituationGraph reasoning,
---
+## v0.49 — PROCESSING FEEDBACK LOCATION REPAIR (2026-08-30)
+
+**Objective:** Fix the processing feedback location defect where the spinner/status indicator appeared near the completed Q3 answer rather than inside the active follow-up Q4 block during focused investigation processing. Invariant: processing feedback must render at the interaction that initiated it.
+
+### Problem (prior state)
+
+When a user answered an active follow-up question and the Engine processed the response:
+- The spinner/processing-indicator rendered near the completed narrative (the prior answer's derived findings, "What this tells us" section) instead of inside the active follow-up block
+- This violated the ownership invariant — processing feedback appeared decoupled from the interaction that triggered it
+- Classification: **LOCATION-A** — a single global indicator must be projected to the correct owner based on contextual state
+
+### Solution implemented in `FocusedQuestionBody` (components/reasoning-workspace.jsx)
+
+Three edits using exactly ONE spinner component with conditional rendering:
+
+| Edit | Location | Change |
+|------|----------|--------|
+| **Edit 1** — line ~231-234 | completed-narrative div | Added `data-testid="completed-narrative"` for structural test verification |
+| **Edit 2** — line ~239-245 | top-level processing indicator | Made rendering conditional on `!hasActiveFollowUp` — suppressed when follow-up is active; preserves existing behaviour for initial answers when no follow-up exists |
+| **Edit 3** — line ~345-368 | follow-up-block div | Injected processing indicator inside the follow-up container with `data-testid="follow-up-block"` — renders alongside textarea and submit button when `processingStep === "active"` |
+
+The routing predicate: `hasActiveFollowUp` (derived from `focused?.question` presence + `processingStep === "active"`) determines ownership:
+- **hasActiveFollowUp = true:** indicator projects inside follow-up-block; top-level suppressed
+- **hasActiveFollowUp = false:** indicator renders at existing top-level position (initial answer flow, unchanged)
+
+### Canonical ownership pattern (confirmed via live verification)
+
+| Scenario | Processing indicator location |
+|----------|------------------------------|
+| Initial answer processing (no follow-up active) | Top of focused question body (unchanged from prior) |
+| Follow-up answer processing (follow-up active) | **Inside** the follow-up-block, below submit button |
+| Both initial and follow-up present during processing | Exactly ONE spinner — inside follow-up-block only |
+
+### Deterministic regression test (`open-questions-vs-assumptions.test.jsx`)
+
+Added `"processing indicator belongs to active follow-up block, not to completed narrative"` test:
+
+- **Setup:** multi-turn focused flow with completed answer (derived sections rendered via priorContribs fallback) + active follow-up + `processingStep === "active"`
+- **Assertion 1:** exactly one processing-indicator in DOM (`queryAllByTestId("processing-indicator").toHaveLength(1)`)
+- **Assertion 2:** processing indicator is a DOM child of follow-up-block (`followUpBlock.contains(processingIndicator).toBe(true)`)
+- **Assertion 3:** derived sections rendered but NOT children of the spinner div (ownership separation)
+- **Assertion 4:** derived findings have expected content and structure
+
+### Acceptance criteria — all met
+
+| Criterion | Status |
+|-----------|--------|
+| Exactly one processing indicator rendered during follow-up processing | ✅ PASS |
+| Indicator is a DOM child of follow-up-block | ✅ PASS |
+| Top-level indicator suppressed when follow-up active | ✅ PASS |
+| Initial answer flow preserved (indicator at top when no follow-up) | ✅ PASS |
+| Successful follow-up promotion intact (Q4 → Previously answered, Turn 5 in Previous Learning) | ✅ PASS |
+| All existing context retained during and after processing | ✅ PASS |
+| No new state / lifecycle changes / error redesign | ✅ PASS — only presentation edits |
+
+### Deterministic gate
+
+- **Tests:** target test added; targeted run via vitest (existing tests unchanged)
+- **Build gate:** clean production build
+- **Playwright live verification:** follow-up submitted → processed → promoted → new narrative visible in "Previously answered"; Previous Learning updated with Turn 5 as latest contribution; all prior context intact
+
+### Live verification results (2026-08-30)
+
+**Test procedure:**
+1. Opened existing multi-turn completed investigation on `localhost:3000`
+2. Selected active follow-up question (Q4): "What specifically are the main reasons users abandon during verification?"
+3. Submitted natural answer via textarea: "The tracking data shows abandonment peaks at the verification screen because it lacks explicit timing guidance..."
+4. Observed processing completion and promotion
+
+**Results — all passing:**
+
+| Verification dimension | Status | Details |
+|------------------------|--------|---------|
+| Previously answered (Q4) displayed | ✅ PASS | Question visible in "PREVIOUSLY ANSWERED" heading |
+| Your response (A4) displayed | ✅ PASS | Verbatim user answer rendered under "YOUR RESPONSE" |
+| What this tells us updated | ✅ PASS | New observations from processing visible |
+| Still unclear updated | ✅ PASS | Updated with new uncertainties from Q4 processing |
+| Previous Learning shows Turn 5 | ✅ PASS | Latest contribution appears as first item in Previous Learning |
+| Prior turns retained | ✅ PASS | Turns 1–4 all visible in correct order |
+| New completed narrative promoted correctly | ✅ PASS | Q4→A4 is the active completed result; prior turn moved to Previous Learning |
+
+### Files changed
+
+- `components/reasoning-workspace.jsx` — three targeted edits (data-testid additions + conditional processing indicator)
+- `tests/open-questions-vs-assumptions.test.jsx` — v0.49 processing location regression test
+- `docs/current-handoff.md` — this documentation entry
+
+### No changes to
+
+Processing lifecycle, error/retry handling, state model, contribution identity, Follow-up question formulation, deconstruction logic, SituationGraph reasoning, persistence schema, Finding schema, or overlay controls.
+
+---
+
## RESTORE / PRESENTATION FINDINGS — MANUAL USER-PATH (2026-08-28)
### Open restore / focused-workspace presentation issue
diff --git a/tests/open-questions-vs-assumptions.test.jsx b/tests/open-questions-vs-assumptions.test.jsx
index 8204758..d377a8d 100644
--- a/tests/open-questions-vs-assumptions.test.jsx
+++ b/tests/open-questions-vs-assumptions.test.jsx
@@ -2362,6 +2362,62 @@ describe("v0.49 RENDERED — in-place follow-up context ownership", () => {
const questionHeadings = screen.queryAllByText(/^Question$/);
expect(questionHeadings).toHaveLength(0);
});
+
+ it("processing indicator belongs to active follow-up block, not to completed narrative", async () => {
+ const prevQ = "Which specific step of the onboarding funnel has the highest abandonment rate?";
+ const prevA = "Step 3 — account verification / phone confirmation.";
+ const followUpQ = "What drives the Step 3 abandonment rate?";
+
+ const contrib = makeContrib(prevQ, prevA, 1);
+
+ renderFQB({
+ focused: {
+ question: followUpQ,
+ answer: null,
+ status: "formulated",
+ result: {
+ observations: [contrib.observations[0]],
+ uncertainties: ["Is this causal?"],
+ possibleFollowUpQuestions: [followUpQ, "How does it compare to competitors?"],
+ assumptions: [],
+ relationships: [],
+ },
+ error: null,
+ },
+ focusedContributions: [contrib],
+ processingStep: "active",
+ deconstructMsg: "Working through your response…",
+ });
+
+ // ── Derived completed context (from prior contributions) remains visible during processing ──
+ expect(screen.getByText("Previously answered")).toBeInTheDocument();
+ expect(screen.getByText(prevQ)).toBeInTheDocument();
+ expect(screen.getByText("What this tells us")).toBeInTheDocument();
+
+ // ── Exactly one processing indicator exists (no duplicates, no missing) ──
+ const allProcessingIndicators = screen.queryAllByTestId("processing-indicator");
+ expect(allProcessingIndicators).toHaveLength(1);
+ const processingIndicator = allProcessingIndicators[0];
+
+ // ── Processing indicator must be inside the active follow-up block (Q4) ──
+ const followUpBlock = screen.getByTestId("follow-up-block");
+ expect(followUpBlock.contains(processingIndicator)).toBe(true);
+
+ // ── Processing text visible inside follow-up block ──
+ expect(processingIndicator.textContent).toContain("Working through your response…");
+
+ // ── Follow-up textarea and submit are present inside the same block ──
+ const followUpTextarea = screen.getByTestId("follow-up-textarea");
+ expect(followUpTextarea.disabled).toBe(true);
+ const followUpSubmit = screen.getByRole("button", { name: /submit/i });
+ expect(followUpSubmit.closest("[data-testid='follow-up-block']")).toBeInTheDocument();
+
+ // ── Completed narrative that DOES render must not contain follow-up elements ──
+ const completedNarrative = screen.queryByTestId("completed-narrative");
+ if (completedNarrative) {
+ expect(completedNarrative.querySelector('[data-testid="follow-up-block"]')).not.toBeInTheDocument();
+ }
+ });
});
// ── v0.51 CASE B: error preserves workspace context ────────────────