fix: localise update reasoning state
This commit is contained in:
+28
-38
@@ -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.
|
||||
|
||||
@@ -636,20 +636,13 @@ export default function ReasoningWorkspace({
|
||||
variant="initial"
|
||||
/>
|
||||
)}
|
||||
{updateStatus === "loading" && (
|
||||
<LoadingOverlay
|
||||
elapsed={updateElapsed}
|
||||
currentMessage={updateMsg}
|
||||
variant="update"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ── Provider unavailable recovery ──────────────── */}
|
||||
{/* ── Provider unavailable recovery (always visible) ───────────── */}
|
||||
{isProviderUnavailable && (
|
||||
<ProviderUnavailableCard onRestart={onRestart} />
|
||||
)}
|
||||
|
||||
{/* ── Malformed response recovery ────────────────── */}
|
||||
{/* ── Malformed response recovery (always visible) ──────────────── */}
|
||||
{isMalformedResponse && (
|
||||
<MalformedResponseCard onRestart={onRestart} />
|
||||
)}
|
||||
@@ -675,15 +668,19 @@ export default function ReasoningWorkspace({
|
||||
{/* ── Investigation summary card ─────────────── */}
|
||||
<InvestigationSummaryPanel graph={graph} selectedQuestion={selectedQ} result={result} updateStatus={updateStatus} />
|
||||
|
||||
{/* ── Active investigation: question + form (top priority) ─ */}
|
||||
{canAnswer && (
|
||||
{/* ── Active investigation context (always visible while question exists) ─ */}
|
||||
{hasSelectedQuestion && (
|
||||
<>
|
||||
<CurrentInvestigationCard selectedQuestion={selectedQ} graph={graph} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Post-update acknowledgement */}
|
||||
{updateStatus === "success" && <UpdateAcknowledgement updateResult={result} />}
|
||||
{/* Post-update acknowledgement — only after success, never during loading */}
|
||||
{updateStatus === "success" && !isUpdating && <UpdateAcknowledgement updateResult={result} />}
|
||||
|
||||
{/* Response form */}
|
||||
{/* ── Response area (replaced by loading / error during reasoning) ─ */}
|
||||
{!isUpdating ? (
|
||||
canAnswer && (
|
||||
<form onSubmit={handleUpdateCaptureAndSubmit} className="space-y-4 rounded-lg border border-gray-200 bg-white p-5">
|
||||
<div>
|
||||
<label htmlFor="rw-answer" className="mb-2 block text-sm font-medium text-gray-700">
|
||||
@@ -701,25 +698,32 @@ export default function ReasoningWorkspace({
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-xs text-gray-400">
|
||||
{updateStatus === "loading" ? "Updating..." : "One update turn only in this prototype."}
|
||||
One update turn only in this prototype.
|
||||
</p>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={updateStatus === "loading" || !answer.trim()}
|
||||
disabled={!answer.trim()}
|
||||
className="rounded-lg bg-blue-700 px-5 py-2 text-sm font-medium text-white transition hover:bg-blue-600 disabled:cursor-not-allowed disabled:opacity-40"
|
||||
>
|
||||
{updateStatus === "loading" ? "Updating..." : "Update situation"}
|
||||
Update situation
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
/* ── Local update loading card (replaces response panel) ─ */}
|
||||
<LoadingOverlay
|
||||
elapsed={updateElapsed}
|
||||
currentMessage={updateMsg}
|
||||
variant="update"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* ── 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 && (
|
||||
<CompletionCard summary={resolveCurrentSummary(propUnderstanding || graph?.currentSummary || result?.updatedSituationGraph?.currentSummary)} />
|
||||
)}
|
||||
{status === "success" && !hasSelectedQuestion && graph && !genuineCompletion && (
|
||||
{!isUpdating && status === "success" && !hasSelectedQuestion && graph && !genuineCompletion && (
|
||||
<EvidenceLimitCard summary={resolveCurrentSummary(propUnderstanding || graph?.currentSummary || result?.updatedSituationGraph?.currentSummary)} />
|
||||
)}
|
||||
|
||||
@@ -753,7 +757,7 @@ export default function ReasoningWorkspace({
|
||||
Error: {result.error}
|
||||
</div>
|
||||
)}
|
||||
{updateStatus === "error" && result?.updateError && (
|
||||
{updateStatus === "error" && !isProviderUnavailable && !isMalformedResponse && result?.updateError && (
|
||||
<div className="rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
Update error: {result.updateError.error || JSON.stringify(result.updateError)}
|
||||
</div>
|
||||
|
||||
@@ -443,7 +443,7 @@ export default function ScenarioForm() {
|
||||
)}
|
||||
|
||||
{/* ── Main result workspace ─────────────────────── */}
|
||||
{(status === "success" || status === "error") && updateStatus !== "loading" && (
|
||||
{(status === "success" || status === "error") && (
|
||||
<ReasoningWorkspace
|
||||
scenario={scenario}
|
||||
status={status}
|
||||
@@ -475,16 +475,6 @@ 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); }} />
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user