fix: unify reasoning mode across submissions

This commit is contained in:
2026-08-05 08:27:22 +01:00
parent c87fd65e13
commit 6d11c1d503
5 changed files with 132 additions and 4 deletions
+75
View File
@@ -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.
+4 -2
View File
@@ -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" };
+1 -1
View File
@@ -673,7 +673,7 @@ export default function ReasoningWorkspace({
) : (
<>
{/* ── Investigation summary card ─────────────── */}
<InvestigationSummaryPanel graph={graph} selectedQuestion={selectedQ} result={result} />
<InvestigationSummaryPanel graph={graph} selectedQuestion={selectedQ} result={result} updateStatus={updateStatus} />
{/* ── Active investigation: question + form (top priority) ─ */}
{canAnswer && (
+11 -1
View File
@@ -443,7 +443,7 @@ export default function ScenarioForm() {
)}
{/* ── Main result workspace ─────────────────────── */}
{(status === "success" || status === "error" || updateStatus === "success") && (
{(status === "success" || status === "error") && updateStatus !== "loading" && (
<ReasoningWorkspace
scenario={scenario}
status={status}
@@ -475,6 +475,16 @@ export default function ScenarioForm() {
/>
)}
{/* ── Update loading (reasoning mode) ─────────── */}
{updateStatus === "loading" && status === "success" && (
<LoadingOverlay
isLoading={true}
elapsed={updateElapsed}
currentMessage={updateMsg}
variant="update"
/>
)}
{/* ── Continue later banner when session was restored ── */}
{status === "success" && result?.updatedAt && (
<ContinueLaterBanner onRestart={() => { clearSession(); setStatus("idle"); setResult(null); setAnswer(""); setUpdateStatus("idle"); setCurrentUnderstanding(null); }} />
+41
View File
@@ -305,3 +305,44 @@ 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/);
}
});