fix: unify reasoning mode across submissions
This commit is contained in:
@@ -177,3 +177,78 @@ History should remain collapsed unless the user chooses to inspect previous reas
|
|||||||
### Original situation
|
### Original situation
|
||||||
|
|
||||||
Once an investigation has started, the original scenario becomes reference material rather than the primary focus.
|
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.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ function humaniseDuration(seconds) {
|
|||||||
|
|
||||||
/* ── Component ────────────────────────────────────────────── */
|
/* ── Component ────────────────────────────────────────────── */
|
||||||
|
|
||||||
function InvestigationSummaryPanel({ graph, selectedQuestion, result }) {
|
function InvestigationSummaryPanel({ graph, selectedQuestion, result, updateStatus }) {
|
||||||
// ── Current status ────────────────────────────────────────────
|
// ── Current status ────────────────────────────────────────────
|
||||||
// TODO: reasoning should emit an explicit status field such as
|
// TODO: reasoning should emit an explicit status field such as
|
||||||
// "investigating", "evidence_limit_reached", "resolution_achieved".
|
// "investigating", "evidence_limit_reached", "resolution_achieved".
|
||||||
@@ -38,7 +38,9 @@ function InvestigationSummaryPanel({ graph, selectedQuestion, result }) {
|
|||||||
const hasGraph = Boolean(graph);
|
const hasGraph = Boolean(graph);
|
||||||
|
|
||||||
let currentStatus;
|
let currentStatus;
|
||||||
if (!hasGraph) {
|
if (updateStatus === "loading") {
|
||||||
|
currentStatus = { label: "Reasoning", level: "investigating" };
|
||||||
|
} else if (!hasGraph) {
|
||||||
currentStatus = { label: "Not started", level: "idle" };
|
currentStatus = { label: "Not started", level: "idle" };
|
||||||
} else if (isInvestigating) {
|
} else if (isInvestigating) {
|
||||||
currentStatus = { label: "Investigation in progress", level: "investigating" };
|
currentStatus = { label: "Investigation in progress", level: "investigating" };
|
||||||
|
|||||||
@@ -673,7 +673,7 @@ export default function ReasoningWorkspace({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* ── Investigation summary card ─────────────── */}
|
{/* ── 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) ─ */}
|
{/* ── Active investigation: question + form (top priority) ─ */}
|
||||||
{canAnswer && (
|
{canAnswer && (
|
||||||
|
|||||||
@@ -443,7 +443,7 @@ export default function ScenarioForm() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ── Main result workspace ─────────────────────── */}
|
{/* ── Main result workspace ─────────────────────── */}
|
||||||
{(status === "success" || status === "error" || updateStatus === "success") && (
|
{(status === "success" || status === "error") && updateStatus !== "loading" && (
|
||||||
<ReasoningWorkspace
|
<ReasoningWorkspace
|
||||||
scenario={scenario}
|
scenario={scenario}
|
||||||
status={status}
|
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 ── */}
|
{/* ── Continue later banner when session was restored ── */}
|
||||||
{status === "success" && result?.updatedAt && (
|
{status === "success" && result?.updatedAt && (
|
||||||
<ContinueLaterBanner onRestart={() => { clearSession(); setStatus("idle"); setResult(null); setAnswer(""); setUpdateStatus("idle"); setCurrentUnderstanding(null); }} />
|
<ContinueLaterBanner onRestart={() => { clearSession(); setStatus("idle"); setResult(null); setAnswer(""); setUpdateStatus("idle"); setCurrentUnderstanding(null); }} />
|
||||||
|
|||||||
@@ -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.
|
// Normal journey test: wait directly for the stable resulting state.
|
||||||
await waitForWorkspaceReady(page);
|
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/);
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user