exp(06): focused investigation — visual hierarchy without layout changes

Emphasise the active investigation card through stronger elevation,
clearer borders, and improved spacing. Quiet supporting panels by
reducing border opacity, softening heading weight, and lowering
text contrast — making them available without competing for attention.

Facilitator card receives a warm surface tint to read as a briefing
card rather than a generic panel.

Documentation: close experiment 05 with findings, add experiment 06
to the design evolution log, add Attention Hierarchy to UX guidelines,
defer dark mode to a future Investigation Mode experiment.

Presentation changes only — no reasoning, prompts, graph, API, or
backend modifications.
This commit is contained in:
2026-08-05 12:41:13 +01:00
parent f46c419168
commit 46f2d12726
6 changed files with 102 additions and 46 deletions
+21
View File
@@ -348,3 +348,24 @@ Returning users should control repeated guidance.
The workspace should remain the primary visual focus.
Information should naturally flow from left to right.
## Attention Hierarchy
The current task always owns the user's attention.
Supporting information should remain available without competing.
Visual emphasis should come primarily from hierarchy rather than colour.
Reduce distraction before adding decoration.
Calm interfaces improve reasoning.
Hierarchy flows from strongest to quietest:
1. The current investigation question (strongest visual element)
2. The response area (interactive, clear action)
3. Supporting context (visible but restrained)
4. Reference material (available, low priority)
The workspace should feel like an active desk — the work in progress is prominent, supporting tools are within reach but not shouting for attention.
+4 -4
View File
@@ -82,15 +82,15 @@ export default function InvestigationMap({ turnCount = 0 }) {
if (!hasActiveTopics && groups.established.length === 0) return null;
return (
<div className="rounded-lg border border-gray-100 bg-gray-50/50 p-4" role="region" aria-label="Investigation map preview">
<h2 className="mb-1 text-sm font-semibold tracking-tight text-gray-400">
<div className="rounded-lg border border-gray-200/60 bg-gray-50/30 p-4" role="region" aria-label="Investigation map preview">
<h2 className="mb-1 text-[11px] font-medium tracking-widest uppercase text-gray-300">
Investigation Map
</h2>
<p className="mb-3 text-xs text-gray-400">
<p className="mb-3 text-xs text-gray-400/70">
Active investigation topics and their status.
</p>
<div className="space-y-px border-t border-gray-100 pt-3" role="list" aria-label="Investigation topics">
<div className="space-y-px border-t border-gray-200/60 pt-3" role="list" aria-label="Investigation topics">
{topics.map((topic, i) => (
<TopicRow key={`${topic.title}-${i}`} title={topic.title} status={topic.status} />
))}
+6 -6
View File
@@ -62,10 +62,10 @@ function InvestigationSummaryPanel({ graph, selectedQuestion, result, updateStat
}
const statusColors = {
idle: { border: "border-gray-200", bg: "bg-gray-50", text: "text-gray-600" },
investigating: { border: "border-blue-200", bg: "bg-blue-50", text: "text-blue-700" },
complete: { border: "border-green-200", bg: "bg-green-50", text: "text-green-700" },
limit: { border: "border-gray-300", bg: "bg-gray-100", text: "text-gray-500" },
idle: { border: "border-gray-200/60", bg: "bg-gray-50/40", text: "text-gray-400" },
investigating: { border: "border-blue-200/60", bg: "bg-blue-50/30", text: "text-blue-600" },
complete: { border: "border-green-200/60", bg: "bg-green-50/30", text: "text-green-600" },
limit: { border: "border-gray-200", bg: "bg-gray-50/40", text: "text-gray-400" },
};
const colors = statusColors[currentStatus.level] || statusColors.idle;
@@ -125,10 +125,10 @@ function InvestigationSummaryPanel({ graph, selectedQuestion, result, updateStat
{/* Current understanding */}
{currentUnderstanding && (
<div>
<h3 className="mb-1 text-xs font-bold uppercase tracking-wider text-gray-400">
<h3 className="mb-1 text-[11px] font-medium tracking-widest uppercase text-gray-400/70">
What we understand so far
</h3>
<p className="text-sm leading-relaxed text-gray-700">{currentUnderstanding}</p>
<p className="text-sm leading-relaxed text-gray-600">{currentUnderstanding}</p>
</div>
)}
+25 -25
View File
@@ -99,8 +99,8 @@ function UnexpectedStateCard({ stateName, onRetry, onRestart }) {
function ContinueLaterBanner({ onRestart }) {
return (
<div className="rounded-lg border border-blue-200 bg-blue-50 px-5 py-4 text-center">
<p className="text-sm text-blue-800">
<div className="rounded-lg border border-blue-200/60 bg-blue-50/40 px-5 py-4 text-center">
<p className="text-sm text-blue-700/70">
Your previous investigation state is still saved. You can continue where you left off or start fresh.
</p>
{onRestart && (
@@ -248,13 +248,13 @@ function CurrentInvestigationCard({ selectedQuestion, graph }) {
}
return (
<div className="investigation-card rounded-lg border-2 border-green-300 bg-green-50 p-6">
<h2 className="mb-2 text-sm font-bold tracking-wide text-green-700">
<div className="investigation-card rounded-lg border-[2.5px] border-green-400 bg-gradient-to-b from-green-50 to-white p-8 shadow-sm">
<h2 className="mb-3 text-xs font-bold tracking-widest uppercase text-green-600/70">
Investigation
</h2>
<p className="text-xl font-semibold leading-snug text-gray-900">{q}</p>
<p className="text-2xl font-semibold leading-tight text-gray-900">{q}</p>
{whyMattersText && (
<p className="mt-4 text-sm leading-relaxed text-green-800">
<p className="mt-5 text-sm leading-relaxed text-green-800/80">
{whyMattersText}
</p>
)}
@@ -317,11 +317,11 @@ function CurrentUnderstandingCard({ currentSummary, plainLanguage }) {
if (!summary) return null;
return (
<div className="rounded-lg border border-gray-100 bg-white px-6 pt-5 pb-6">
<h2 className="mb-4 text-xl font-semibold tracking-tight text-gray-800">
<div className="rounded-lg border border-gray-200/60 bg-white/80 px-6 pt-5 pb-6">
<h2 className="mb-4 text-base font-semibold tracking-tight text-gray-400">
Understanding
</h2>
<p className="text-sm leading-relaxed text-gray-700">{summary}</p>
<p className="text-sm leading-relaxed text-gray-600">{summary}</p>
</div>
);
}
@@ -331,11 +331,11 @@ function PlainLanguageCard({ summary }) {
if (!summary) return null;
return (
<div className="rounded-lg border border-gray-100 bg-white px-6 pt-5 pb-6">
<h2 className="mb-4 text-xl font-semibold tracking-tight text-gray-800">
<div className="rounded-lg border border-gray-200/60 bg-white/80 px-6 pt-5 pb-6">
<h2 className="mb-4 text-base font-semibold tracking-tight text-gray-400">
Understanding
</h2>
<p className="text-sm leading-relaxed text-gray-700">{summary}</p>
<p className="text-sm leading-relaxed text-gray-600">{summary}</p>
</div>
);
}
@@ -352,7 +352,7 @@ function InvestigationHistoryCard({ turn }) {
return (
<details
className="rounded-lg border border-gray-100 bg-gray-50/60"
className="rounded-lg border border-gray-200/60 bg-gray-50/30"
key={turn.id}
open={!isCollapsed}
data-testid="investigation-turn"
@@ -383,7 +383,7 @@ function InvestigationHistory({ turns }) {
return (
<div className="space-y-3" data-testid="investigation-history">
<h2 className="text-xs font-semibold tracking-wider text-gray-400">
<h2 className="text-[11px] font-medium tracking-widest uppercase text-gray-300">
History
</h2>
<div className="space-y-2">
@@ -402,12 +402,12 @@ function OriginalSituation({ scenario, centralStatement }) {
if (!text) return null;
return (
<div className="rounded-lg border border-gray-100 bg-gray-50/70 px-5 py-4">
<h2 className="mb-2 text-xs font-semibold tracking-wider text-gray-400">
<div className="rounded-lg border border-gray-200/60 bg-gray-50/40 px-5 py-4">
<h2 className="mb-2 text-[11px] font-medium tracking-widest uppercase text-gray-300">
Situation
</h2>
<p className="whitespace-pre-wrap text-sm leading-relaxed text-gray-700">
<p className="whitespace-pre-wrap text-sm leading-relaxed text-gray-600">
{text}
</p>
</div>
@@ -440,7 +440,7 @@ function UpdateAcknowledgement({ updateResult }) {
className="transition-all duration-1500 ease-in"
style={{ opacity: visible ? 0.7 : 0, maxHeight: visible ? "4rem" : "0", marginBottom: visible ? "1rem" : "0" }}
>
<div className="rounded-lg border border-blue-200 bg-blue-50/60 px-4 py-2 text-xs text-blue-800">
<div className="rounded-md border border-blue-200/60 bg-blue-50/30 px-4 py-2 text-xs text-blue-700/60">
{summary}
</div>
</div>
@@ -450,11 +450,11 @@ function UpdateAcknowledgement({ updateResult }) {
// ── Developer details disclosure ──────────────────────────────
function DeveloperDetails({ graph, selectedQuestion, diagnostics, newlySurfacedNodeIds, updateResult }) {
return (
<details className="rounded-lg border border-gray-200 bg-gray-50">
<summary className="cursor-pointer px-5 py-3 text-sm font-medium text-gray-600 hover:text-gray-800">
<details className="rounded-lg border border-gray-200/60 bg-gray-50/30">
<summary className="cursor-pointer px-5 py-3 text-sm font-medium text-gray-400 hover:text-gray-600">
Developer details
</summary>
<div className="border-t border-gray-200 px-5 pb-4 pt-3 space-y-4">
<div className="border-t border-gray-200/60 px-5 pb-4 pt-3 space-y-4">
{graph && (
<SituationGraphView
situationGraph={graph}
@@ -482,13 +482,13 @@ function LoadingOverlay({ isLoading, elapsed, currentMessage, variant }) {
}
return (
<div className="rounded-lg border border-gray-200 bg-blue-50 px-5 py-6" role="status" aria-busy="true" data-testid="loading-overlay">
<div className="rounded-lg border border-blue-200/60 bg-blue-50/40 px-6 py-7" role="status" aria-busy="true" data-testid="loading-overlay">
<div className="flex items-center gap-3">
<ActivitySpinner />
<span className="text-base font-medium text-blue-900">Working through your situation</span>
<span className="text-base font-medium text-blue-800/70">Working through your situation</span>
</div>
<p className="mt-2 text-sm text-blue-700">{statusText}</p>
<p className="mt-1 text-xs text-blue-500" aria-live="polite">
<p className="mt-3 text-sm text-blue-600/60">{statusText}</p>
<p className="mt-2 text-xs text-blue-400/50" aria-live="polite">
This has been running for {elapsed}s.
{variant === "initial" && elapsed >= 45 && (
<span className="block mt-1">This can take around a minute with the current local model.</span>
+11 -11
View File
@@ -395,18 +395,18 @@ export default function ScenarioForm() {
{/* Left panel — Facilitator (1/3 on desktop) */}
{!hideFacilitatorOnLanding && (
<div className="md:col-span-1">
<div className="rounded-lg border border-gray-100 bg-gray-50/60 px-6 pt-5 pb-6 sticky top-6">
<h2 className="mb-3 text-sm font-semibold tracking-wide text-gray-500 uppercase">Before we begin</h2>
<p className="text-sm leading-relaxed text-gray-700 mb-4">
<div className="rounded-lg border border-amber-200/60 bg-gradient-to-b from-amber-50/60 to-white px-8 pt-7 pb-7 sticky top-6 shadow-sm">
<h2 className="mb-4 text-xs font-bold tracking-widest uppercase text-amber-600/60">Before we begin</h2>
<p className="text-sm leading-relaxed text-amber-900/80 mb-5">
The Confidence Engine helps build confidence by understanding situations before deciding what to do.
</p>
<p className="text-sm leading-relaxed text-gray-700 mb-3">
<p className="text-sm leading-relaxed text-amber-900/70 mb-4">
You do not need to know exactly what the problem is.
</p>
<p className="text-sm leading-relaxed text-gray-700 mb-6">
<p className="text-sm leading-relaxed text-amber-900/70 mb-7">
Simply describe what you have observed. We will work through it together, one question at a time.
</p>
<div className="flex items-center gap-2 pt-4 border-t border-gray-200">
<div className="flex items-center gap-2 pt-5 border-t border-amber-100/60">
<input
type="checkbox"
id="dismiss-facilitator"
@@ -428,7 +428,7 @@ export default function ScenarioForm() {
{/* Right panel — Workspace (2/3 on desktop) */}
<div className={hideFacilitatorOnLanding ? "md:col-span-3" : "md:col-span-2"}>
<h2 className="mb-3 text-sm font-semibold tracking-wide text-gray-500 uppercase">Tell me what's happening</h2>
<h2 className="mb-4 text-xs font-bold tracking-widest uppercase text-gray-400">Tell me what's happening</h2>
<textarea
ref={textareaRef}
value={scenario}
@@ -458,8 +458,8 @@ export default function ScenarioForm() {
)}
{status === "idle" && MOCK_ENABLED && (
<details className="rounded-lg border border-gray-200 bg-gray-50">
<summary className="cursor-pointer px-4 py-2 text-sm font-medium text-gray-600">Developer details</summary>
<details className="rounded-lg border border-gray-200/60 bg-gray-50/30">
<summary className="cursor-pointer px-4 py-2 text-sm font-medium text-gray-400 hover:text-gray-600">Developer details</summary>
<div className="space-y-3 px-4 pb-4">
<div>
<label htmlFor="mock-scenario" className="block text-xs font-medium text-gray-500 mb-1">Mock scenario</label>
@@ -483,7 +483,7 @@ export default function ScenarioForm() {
type="button"
onClick={() => handleScenarioFill(s.key)}
disabled={!scenario.trim() && scenario !== s.centralStatement}
className="rounded-md border border-gray-200 bg-white px-3 py-1.5 text-xs text-gray-600 hover:bg-gray-100 disabled:opacity-30"
className="rounded-md border border-gray-200/60 bg-white/80 px-3 py-1.5 text-xs text-gray-500 hover:bg-gray-50 disabled:opacity-30"
>
{s.label} ({s.turnCount} turns)
</button>
@@ -558,7 +558,7 @@ export default function ScenarioForm() {
setCurrentUnderstanding(null);
setUpdateError(null);
}}
className="rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-600 transition hover:bg-gray-50"
className="rounded-lg border border-gray-200/60 px-4 py-2 text-sm font-medium text-gray-500 transition hover:bg-gray-50/80"
>
Start new investigation
</button>
+35
View File
@@ -293,6 +293,33 @@ Questions
- Does "Don't show again" feel preferable to automatically hiding the introduction?
- Should the facilitator panel become an optional workspace companion rather than mandatory onboarding?
Status:
Completed.
Findings:
- A horizontal facilitator/workspace arrangement feels more natural than stacked onboarding.
- The workspace becomes the visual destination rather than the introduction.
- User-controlled dismissal is preferable to automatic hiding.
- The facilitator feels useful but visually too passive.
- Remaining issues are now visual hierarchy rather than layout architecture.
### Experiment 06 — Focused Investigation
Hypothesis
The interface should gently guide attention towards the current task without hiding supporting information.
Reducing competition between panels may improve concentration more than introducing additional colour or decoration.
Questions
- Does visual emphasis naturally guide the eye?
- Can supporting panels become quieter without disappearing?
- Does the investigation question become the obvious focal point?
- Does the workspace feel calmer?
- Are we approaching a professional investigation environment?
Status:
Experimental.
@@ -318,3 +345,11 @@ The "Don't show this introduction again" checkbox uses sessionStorage as a place
This preference should eventually be handled through user preferences or settings rather than local component state.
TODO: When user accounts are introduced, persist this preference to the user profile so it travels across devices and sessions.
## Future Note — Dark Mode
Dark mode is intentionally deferred.
Once the information architecture and visual hierarchy stabilise we will investigate whether an "Investigation Mode" (rather than a conventional dark mode) improves concentration.
This should be treated as a future UX experiment rather than an accessibility feature.