diff --git a/.claude/ux-guidelines.md b/.claude/ux-guidelines.md
index e862a13..db9ac3f 100644
--- a/.claude/ux-guidelines.md
+++ b/.claude/ux-guidelines.md
@@ -197,18 +197,35 @@ The workspace is interactive.
---
-### Reasoning Mode
+### Reasoning Mode (initial analysis)
-The engine is analysing the available evidence.
+The engine is constructing the first investigation from nothing.
+
+A full primary loading state appears:
+
+- prominent overlay with spinner, rotating status messages, elapsed timer;
+- the entire workspace is replaced until reasoning completes;
+- no partial or changing content is visible during processing.
+
+---
+
+### Reasoning Mode (subsequent answers — localised)
+
+The investigation already exists.
+
+Only the active response panel is replaced by the loading card:
+
+- Current investigation question remains visible for context;
+- Current understanding, Original situation, and Investigation history persist;
+- Terminal state cards are suppressed during loading;
+- The workspace layout remains stable and recognisable;
+- Recovery states appear in place of the loading card if reasoning fails.
The interface should:
-- clearly indicate that reasoning is in progress
-- temporarily suspend the workspace
-- reassure the user that their input has been accepted
-- avoid displaying partial or changing reasoning
-
-The workspace is paused until reasoning completes.
+- clearly indicate that reasoning is in progress via the response-panel overlay;
+- reassure the user that their answer has been accepted;
+- avoid displaying partial or changing reasoning outside the response panel.
---
@@ -218,37 +235,10 @@ Every submission follows the same lifecycle:
User submits information
↓
-Workspace pauses
+Loading card appears (full-page for initial analysis, localised for updates)
↓
-Reasoning mode
-↓
-Updated workspace appears
+Updated workspace returns
-The interaction should be identical whether the submission is:
-
-- the initial situation
-- an investigation answer
-- a future uploaded document
-- any other evidence
+The interaction is consistent in intent — both modes confirm input acceptance and pause the active response area — but the page-level behaviour differs because one constructs from nothing while the other refines existing context.
Users should never wonder whether their input has been accepted or whether the engine is still reasoning.
-
----
-
-## Consistent Submission Lifecycle
-
-Every submission follows the same lifecycle regardless of context.
-
-1. User submits → workspace pauses immediately, stale content disappears
-2. Reasoning mode appears with loading overlay (spinner, rotating status messages, elapsed timer)
-3. Updated workspace returns or error state appears
-
-The loading card must appear before any async request begins and remain visible until the result arrives.
-
-During reasoning mode:
-- The workspace is fully hidden (not just disabled)
-- No stale question, response form, or active indicators are visible
-- The Investigation Summary Panel shows "Reasoning" rather than "Investigation in progress"
-- Recovery states render on top of the loading overlay when applicable
-
-This applies equally to initial analysis and all update submissions.
diff --git a/components/reasoning-workspace.jsx b/components/reasoning-workspace.jsx
index 661be78..3171a49 100644
--- a/components/reasoning-workspace.jsx
+++ b/components/reasoning-workspace.jsx
@@ -636,20 +636,13 @@ export default function ReasoningWorkspace({
variant="initial"
/>
)}
- {updateStatus === "loading" && (
-
- )}
- {/* ── Provider unavailable recovery ──────────────── */}
+ {/* ── Provider unavailable recovery (always visible) ───────────── */}
{isProviderUnavailable && (
)}
- {/* ── Malformed response recovery ────────────────── */}
+ {/* ── Malformed response recovery (always visible) ──────────────── */}
{isMalformedResponse && (
)}
@@ -675,15 +668,19 @@ export default function ReasoningWorkspace({
{/* ── Investigation summary card ─────────────── */}
- {/* ── Active investigation: question + form (top priority) ─ */}
- {canAnswer && (
+ {/* ── Active investigation context (always visible while question exists) ─ */}
+ {hasSelectedQuestion && (
<>
+ >
+ )}
- {/* Post-update acknowledgement */}
- {updateStatus === "success" && }
+ {/* Post-update acknowledgement — only after success, never during loading */}
+ {updateStatus === "success" && !isUpdating && }
- {/* Response form */}
+ {/* ── Response area (replaced by loading / error during reasoning) ─ */}
+ {!isUpdating ? (
+ canAnswer && (
- >
+ )
+ ) : (
+ /* ── Local update loading card (replaces response panel) ─ */}
+
)}
- {/* ── Terminal state: outcome card (no active question) ─ */}
- {status === "success" && !hasSelectedQuestion && graph && genuineCompletion && (
+ {/* ── Terminal state: outcome card — hidden during update loading ─ */}
+ {!isUpdating && status === "success" && !hasSelectedQuestion && graph && genuineCompletion && (
)}
- {status === "success" && !hasSelectedQuestion && graph && !genuineCompletion && (
+ {!isUpdating && status === "success" && !hasSelectedQuestion && graph && !genuineCompletion && (
)}
@@ -753,7 +757,7 @@ export default function ReasoningWorkspace({
Error: {result.error}
)}
- {updateStatus === "error" && result?.updateError && (
+ {updateStatus === "error" && !isProviderUnavailable && !isMalformedResponse && result?.updateError && (
Update error: {result.updateError.error || JSON.stringify(result.updateError)}
diff --git a/components/scenario-form.jsx b/components/scenario-form.jsx
index 91f677c..d067896 100644
--- a/components/scenario-form.jsx
+++ b/components/scenario-form.jsx
@@ -443,7 +443,7 @@ export default function ScenarioForm() {
)}
{/* ── Main result workspace ─────────────────────── */}
- {(status === "success" || status === "error") && updateStatus !== "loading" && (
+ {(status === "success" || status === "error") && (
)}
- {/* ── Update loading (reasoning mode) ─────────── */}
- {updateStatus === "loading" && status === "success" && (
-
- )}
-
{/* ── Continue later banner when session was restored ── */}
{status === "success" && result?.updatedAt && (
{ clearSession(); setStatus("idle"); setResult(null); setAnswer(""); setUpdateStatus("idle"); setCurrentUnderstanding(null); }} />
diff --git a/tests/e2e/happy-path.spec.js b/tests/e2e/happy-path.spec.js
index b987b52..a49f457 100644
--- a/tests/e2e/happy-path.spec.js
+++ b/tests/e2e/happy-path.spec.js
@@ -308,7 +308,7 @@ test("loading overlay appears on submit and disappears after response", async ({
/* ═══════ Test: update loading hides workspace and shows overlay ═ */
-test("update submission hides workspace, shows reasoning overlay, then restores workspace", async ({ page }) => {
+test("update submission replaces response panel with loading card, preserves context", async ({ page }) => {
// Use normal (700ms) delay so the loading overlay is observable during updates
await page.goto("/");
@@ -326,23 +326,45 @@ test("update submission hides workspace, shows reasoning overlay, then restores
await waitForWorkspaceReady(page);
// Workspace visible after initial analysis
- await expect(page.locator('[data-testid="reasoning-workspace"]')).toBeVisible();
+ const workspace = page.locator('[data-testid="reasoning-workspace"]');
+ await expect(workspace).toBeVisible();
// Submit an answer to trigger update loading
const answerTextarea = page.locator('textarea[placeholder*=Answer]');
if (await answerTextarea.isVisible({ timeout: 3000 })) {
+ // Current investigation visible before click
+ await expect(page.getByRole("heading", { name: "Current investigation" })).toBeVisible();
+
+ // Loading overlay absent before click
+ await expect(page.locator('[data-testid="loading-overlay"][role="status"]')).not.toBeVisible();
+
await answerTextarea.fill("The figures are comparable.");
await page.getByRole("button", { name: "Update situation" }).click();
- // Workspace should disappear during update loading (reasoning mode)
- await expect(page.locator('[data-testid="reasoning-workspace"]')).not.toBeVisible({ timeout: 5_000 });
+ // Current investigation remains visible during update loading
+ await expect(page.getByRole("heading", { name: "Current investigation" })).toBeVisible({ timeout: 5_000 });
- // Loading overlay should be visible (with role="status" for accessibility)
+ // Response textarea hidden — replaced by loading overlay
+ const rwTextarea = page.locator('#rw-answer');
+ await expect(rwTextarea).not.toBeVisible({ timeout: 3_000 });
+
+ // Loading overlay visible in response panel position
const overlay = page.locator('[data-testid="loading-overlay"][role="status"]');
await expect(overlay).toBeVisible({ timeout: 5_000 });
// Status text should indicate reasoning mode (update-specific copy)
- const statusText = overlay.getByRole("status");
- await expect(statusText).toHaveText(/Working through your situation/);
+ await expect(overlay.getByText(/Working through your situation/)).toHaveCount(1);
+
+ // Current understanding card remains visible (supporting context preserved)
+ const cuHeading = page.getByRole("heading", { name: "Current understanding" });
+ if (await cuHeading.isVisible({ timeout: 2_000 })) {
+ await expect(cuHeading).toBeVisible();
+ }
+
+ // After loading completes, workspace returns with updated question
+ await waitForWorkspaceReady(page);
+ await expect(workspace).toBeVisible();
+ await expect(rwTextarea).toBeVisible();
+ await expect(overlay).not.toBeVisible({ timeout: 3_000 });
}
});
\ No newline at end of file