From 6d11c1d503ad37d155c4399409b2da076af2ac29 Mon Sep 17 00:00:00 2001 From: robbond Date: Wed, 5 Aug 2026 08:27:22 +0100 Subject: [PATCH] fix: unify reasoning mode across submissions --- .claude/ux-guidelines.md | 75 ++++++++++++++++++++++ components/investigation-summary-panel.jsx | 6 +- components/reasoning-workspace.jsx | 2 +- components/scenario-form.jsx | 12 +++- tests/e2e/happy-path.spec.js | 41 ++++++++++++ 5 files changed, 132 insertions(+), 4 deletions(-) diff --git a/.claude/ux-guidelines.md b/.claude/ux-guidelines.md index 5863dfd..e862a13 100644 --- a/.claude/ux-guidelines.md +++ b/.claude/ux-guidelines.md @@ -177,3 +177,78 @@ History should remain collapsed unless the user chooses to inspect previous reas ### Original situation Once an investigation has started, the original scenario becomes reference material rather than the primary focus. + +## Interaction Modes + +The Confidence Engine operates in two distinct modes. + +### Workspace Mode + +The user is reading, thinking, and providing information. + +The interface should: + +- present the current investigation +- allow the user to answer +- show the current understanding +- provide investigation history + +The workspace is interactive. + +--- + +### Reasoning Mode + +The engine is analysing the available evidence. + +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. + +--- + +### Transition + +Every submission follows the same lifecycle: + +User submits information +↓ +Workspace pauses +↓ +Reasoning mode +↓ +Updated workspace appears + +The interaction should be identical whether the submission is: + +- the initial situation +- an investigation answer +- a future uploaded document +- any other evidence + +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/investigation-summary-panel.jsx b/components/investigation-summary-panel.jsx index 875d794..c3a3b23 100644 --- a/components/investigation-summary-panel.jsx +++ b/components/investigation-summary-panel.jsx @@ -29,7 +29,7 @@ function humaniseDuration(seconds) { /* ── Component ────────────────────────────────────────────── */ -function InvestigationSummaryPanel({ graph, selectedQuestion, result }) { +function InvestigationSummaryPanel({ graph, selectedQuestion, result, updateStatus }) { // ── Current status ──────────────────────────────────────────── // TODO: reasoning should emit an explicit status field such as // "investigating", "evidence_limit_reached", "resolution_achieved". @@ -38,7 +38,9 @@ function InvestigationSummaryPanel({ graph, selectedQuestion, result }) { const hasGraph = Boolean(graph); let currentStatus; - if (!hasGraph) { + if (updateStatus === "loading") { + currentStatus = { label: "Reasoning", level: "investigating" }; + } else if (!hasGraph) { currentStatus = { label: "Not started", level: "idle" }; } else if (isInvestigating) { currentStatus = { label: "Investigation in progress", level: "investigating" }; diff --git a/components/reasoning-workspace.jsx b/components/reasoning-workspace.jsx index 3a7097e..661be78 100644 --- a/components/reasoning-workspace.jsx +++ b/components/reasoning-workspace.jsx @@ -673,7 +673,7 @@ export default function ReasoningWorkspace({ ) : ( <> {/* ── Investigation summary card ─────────────── */} - + {/* ── Active investigation: question + form (top priority) ─ */} {canAnswer && ( diff --git a/components/scenario-form.jsx b/components/scenario-form.jsx index 109171e..91f677c 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 === "success") && ( + {(status === "success" || status === "error") && updateStatus !== "loading" && ( )} + {/* ── 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 bdb4b51..b987b52 100644 --- a/tests/e2e/happy-path.spec.js +++ b/tests/e2e/happy-path.spec.js @@ -304,4 +304,45 @@ test("loading overlay appears on submit and disappears after response", async ({ // Normal journey test: wait directly for the stable resulting state. await waitForWorkspaceReady(page); +}); + +/* ═══════ Test: update loading hides workspace and shows overlay ═ */ + +test("update submission hides workspace, shows reasoning overlay, then restores workspace", async ({ page }) => { + // Use normal (700ms) delay so the loading overlay is observable during updates + await page.goto("/"); + + await page.evaluate(() => { + window.__MOCK_ENABLED = true; + window.__MOCK_DELAY = "normal"; + window.__MOCK_SCENARIO = "complete"; + }); + + // Start investigation + const textarea = page.locator("textarea[placeholder*=Describe]"); + await textarea.fill(INVESTIGATION_SCENARIOS[0].centralStatement); + await page.getByRole("button", { name: "Analyse" }).click(); + + await waitForWorkspaceReady(page); + + // Workspace visible after initial analysis + await expect(page.locator('[data-testid="reasoning-workspace"]')).toBeVisible(); + + // Submit an answer to trigger update loading + const answerTextarea = page.locator('textarea[placeholder*=Answer]'); + if (await answerTextarea.isVisible({ timeout: 3000 })) { + 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 }); + + // Loading overlay should be visible (with role="status" for accessibility) + 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/); + } }); \ No newline at end of file