feat: begin responsive workspace layout

This commit is contained in:
2026-08-05 10:51:44 +01:00
parent 2f87cca88d
commit cf05c969bf
4 changed files with 141 additions and 63 deletions
+5
View File
@@ -90,3 +90,8 @@ The workspace should always answer:
The interface should minimise cognitive load by presenting the current state first and allowing progressively deeper exploration only when requested.
The engine may contain hundreds of reasoning nodes; the user should only see the information required to take the next meaningful action.
## Why workspace layout matters (v0.7)
This phase optimises for simultaneous visibility instead of sequential scrolling.
Related panels — Understanding alongside Investigation Map, Situation alongside History — can appear side-by-side on wide screens while mobile continues to stack everything vertically. The reasoning engine is completely unaware of these changes; only the presentation layer is affected.
+60
View File
@@ -136,6 +136,66 @@ Avoid:
The next question should be the strongest visual element.
## Workspace Layout Philosophy
The Confidence Engine is a workspace, not a document.
Documents optimise for reading from top to bottom.
Workspaces optimise for allowing related information to be visible simultaneously.
As investigations become larger, users should not be forced into unnecessary
vertical scrolling simply because horizontal space is available.
Layout decisions should always ask:
> "How much useful investigation context can be seen at one time?"
rather than:
> "How narrow can the content column be?"
### Principles
- **Active investigation remains the primary focus.** The current question and response form are always fully visible first.
- **Frequently referenced information should remain visible.** Understanding and Investigation Map should be scannable without scrolling away from the active question.
- **Reference material may share horizontal space on larger displays.** Situation and History can sit side-by-side when there is room.
- **Layout should adapt to available space without changing the investigation flow.** The same information is always present; only its arrangement changes.
- **Mobile and tablet continue to use a stacked single-column layout.** No progressive disclosure at small sizes — every section remains accessible by scrolling, just as it always has been.
- **Desktop progressively exposes more simultaneous context.** Instead of simply adding whitespace, wider screens reveal horizontal relationships between related panels.
### Desktop layout model (wide screens)
```
┌───────────────────── full-width ─────────────────────┐
│ Investigation Summary │
├───────────────────────────────────────────────────────┤
│ Active Workspace │ Working Memory │
│ (full width) │ Understanding Map │
│ Current Investigation │ │
│ Response └─────────────────────────────────┘
├───────────────────────────────────────────────────────┤
│ Reference: Situation │ History │
├───────────────────────────────────────────────────────┤
│ Developer Details (always below) │
└───────────────────────────────────────────────────────┘
```
### Visual goal
The page should feel less like a long report and more like an investigator's
workspace. The eye should be able to compare Understanding alongside Investigation Map without scrolling, and Situation alongside History in the same way.
### What this phase does NOT include
- No card redesigns.
- No new navigation.
- No account management or top bar.
- No tabs, collapsing layouts, resizable panes, floating panels, or masonry.
- No typography or colour changes.
This is a layout-only phase. The reasoning engine should remain completely unaware of presentation decisions.
## TL;DR Workspace Rules
The newest state is the most important state.
+74 -63
View File
@@ -656,10 +656,10 @@ export default function ReasoningWorkspace({
</div>
) : (
<>
{/* ── Investigation summary card ─────────────── */}
{/* ── Investigation summary card — always full-width ─────────── */}
<InvestigationSummaryPanel graph={graph} selectedQuestion={selectedQ} result={result} updateStatus={updateStatus} />
{/* ── Active investigation context (always visible while question exists) ─ */}
{/* Active workspace — primary focus, full-width column ─────── */}
{hasSelectedQuestion && (
<>
<CurrentInvestigationCard selectedQuestion={selectedQ} graph={graph} />
@@ -669,71 +669,82 @@ export default function ReasoningWorkspace({
{/* Post-update acknowledgement — only after success, never during loading */}
{updateStatus === "success" && !isUpdating && <UpdateAcknowledgement updateResult={result} />}
{/* ── Local update loading (always visible when updating) ─ */}
{isUpdating && (
<LoadingOverlay
isLoading={true}
elapsed={updateElapsed}
currentMessage={updateMsg}
variant="update"
/>
)}
{/* ── Response form — hidden during update loading ─ */}
{!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">
Response
</label>
<textarea
id="rw-answer"
value={answer}
onChange={(e) => setAnswer(e.target.value)}
rows={4}
disabled={updateStatus === "loading"}
className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 disabled:cursor-not-allowed disabled:opacity-60"
placeholder="Enter the answer to the selected question..."
{/* ── Active workspace (left) + Working memory (right) ─────── */}
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
{/* Left column — active workspace */}
<div className="space-y-6">
{/* ── Local update loading (always visible when updating) ─ */}
{isUpdating && (
<LoadingOverlay
isLoading={true}
elapsed={updateElapsed}
currentMessage={updateMsg}
variant="update"
/>
)}
{/* ── Response form — hidden during update loading ─ */}
{!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">
Response
</label>
<textarea
id="rw-answer"
value={answer}
onChange={(e) => setAnswer(e.target.value)}
rows={4}
disabled={updateStatus === "loading"}
className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400 disabled:cursor-not-allowed disabled:opacity-60"
placeholder="Enter the answer to the selected question..."
/>
</div>
<div className="flex items-center justify-between">
<p className="text-xs text-gray-400">
One update turn only in this prototype.
</p>
<button
type="submit"
disabled={!answer.trim()}
className="rounded-lg bg-blue-700 px-5 py-2.5 text-sm font-medium text-white transition hover:bg-blue-600 disabled:cursor-not-allowed disabled:opacity-40"
>
Update
</button>
</div>
</form>
)}
{/* ── Terminal state: outcome card — hidden during update loading ─ */}
{!isUpdating && status === "success" && !hasSelectedQuestion && graph && genuineCompletion && (
<CompletionCard summary={resolveCurrentSummary(propUnderstanding || graph?.currentSummary || result?.updatedSituationGraph?.currentSummary)} />
)}
{!isUpdating && status === "success" && !hasSelectedQuestion && graph && !genuineCompletion && (
<EvidenceLimitCard summary={resolveCurrentSummary(propUnderstanding || graph?.currentSummary || result?.updatedSituationGraph?.currentSummary)} />
)}
</div>
{/* Right column — working memory: Understanding + Map */}
{hasSelectedQuestion && (
<div className="space-y-6">
{hasCurrentSummaryCondition && (
<CurrentUnderstandingCard currentSummary={graph?.currentSummary || result?.updatedSituationGraph?.currentSummary} plainLanguage={propUnderstanding || null} />
)}
<InvestigationMap turnCount={investigationHistory.length} />
</div>
<div className="flex items-center justify-between">
<p className="text-xs text-gray-400">
One update turn only in this prototype.
</p>
<button
type="submit"
disabled={!answer.trim()}
className="rounded-lg bg-blue-700 px-5 py-2.5 text-sm font-medium text-white transition hover:bg-blue-600 disabled:cursor-not-allowed disabled:opacity-40"
>
Update
</button>
</div>
</form>
)}
)}
</div>
{/* ── Terminal state: outcome card — hidden during update loading ─ */}
{!isUpdating && status === "success" && !hasSelectedQuestion && graph && genuineCompletion && (
<CompletionCard summary={resolveCurrentSummary(propUnderstanding || graph?.currentSummary || result?.updatedSituationGraph?.currentSummary)} />
)}
{!isUpdating && status === "success" && !hasSelectedQuestion && graph && !genuineCompletion && (
<EvidenceLimitCard summary={resolveCurrentSummary(propUnderstanding || graph?.currentSummary || result?.updatedSituationGraph?.currentSummary)} />
)}
{/* ── Reference row — Situation + History side-by-side on desktop ─ */}
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
<OriginalSituation scenario={scenario} centralStatement={graph?.centralStatement} />
{investigationHistory.length > 0 && (
<InvestigationHistory turns={investigationHistory} />
)}
</div>
{/* ── Current understanding (active investigation only) ─ */}
{hasCurrentSummaryCondition && hasSelectedQuestion && (
<CurrentUnderstandingCard currentSummary={graph?.currentSummary || result?.updatedSituationGraph?.currentSummary} plainLanguage={propUnderstanding || null} />
)}
{/* ── Investigation Map (visible during active investigation) ─ */}
{hasSelectedQuestion && (
<InvestigationMap turnCount={investigationHistory.length} />
)}
<OriginalSituation scenario={scenario} centralStatement={graph?.centralStatement} />
{investigationHistory.length > 0 && <InvestigationHistory turns={investigationHistory} />}
{/* ── Developer details (collapsed by default) ── */}
{/* ── Developer details (collapsed by default, always below) ─ */}
{(status === "success" || status === "error") && graph && (
<DeveloperDetails
graph={graph}
+2
View File
@@ -141,3 +141,5 @@ The current adapter (`lib/map/investigation-map-adapter.js`) uses generic placeh
6. **Recovery action granularity**: The recovery cards currently offer a single "restart investigation" action. Future engine contracts could support partial recovery (e.g., retry with different parameters, switch models) rather than full restart.
7. **Investigation duration tracking**: The summary panel computes elapsed time from `Date.now() - updatedAt`. If the engine emits proper timestamps, the UI can show accurate elapsed duration and investigate stalls (>5 min between turns).
8. **Layout independence (v0.7 workspace layout phase)**: Reasoning outputs must remain entirely independent of presentation layout. The UI's responsive workspace layout — which progressively reveals simultaneous context on wide screens — is a pure presentation concern. No reasoning contract field should be added, removed, or modified to accommodate layout changes. Future reasoning outputs should carry data semantically; how that data arranges itself visually is the responsibility of the presentation layer alone.