Feature/product platform foundation v0.62 #1
@@ -348,3 +348,24 @@ Returning users should control repeated guidance.
|
|||||||
The workspace should remain the primary visual focus.
|
The workspace should remain the primary visual focus.
|
||||||
|
|
||||||
Information should naturally flow from left to right.
|
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.
|
||||||
|
|||||||
@@ -82,15 +82,15 @@ export default function InvestigationMap({ turnCount = 0 }) {
|
|||||||
if (!hasActiveTopics && groups.established.length === 0) return null;
|
if (!hasActiveTopics && groups.established.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-gray-100 bg-gray-50/50 p-4" role="region" aria-label="Investigation map preview">
|
<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-sm font-semibold tracking-tight text-gray-400">
|
<h2 className="mb-1 text-[11px] font-medium tracking-widest uppercase text-gray-300">
|
||||||
Investigation Map
|
Investigation Map
|
||||||
</h2>
|
</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.
|
Active investigation topics and their status.
|
||||||
</p>
|
</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) => (
|
{topics.map((topic, i) => (
|
||||||
<TopicRow key={`${topic.title}-${i}`} title={topic.title} status={topic.status} />
|
<TopicRow key={`${topic.title}-${i}`} title={topic.title} status={topic.status} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ function InvestigationSummaryPanel({ graph, selectedQuestion, result, updateStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
const statusColors = {
|
const statusColors = {
|
||||||
idle: { border: "border-gray-200", bg: "bg-gray-50", text: "text-gray-600" },
|
idle: { border: "border-gray-200/60", bg: "bg-gray-50/40", text: "text-gray-400" },
|
||||||
investigating: { border: "border-blue-200", bg: "bg-blue-50", text: "text-blue-700" },
|
investigating: { border: "border-blue-200/60", bg: "bg-blue-50/30", text: "text-blue-600" },
|
||||||
complete: { border: "border-green-200", bg: "bg-green-50", text: "text-green-700" },
|
complete: { border: "border-green-200/60", bg: "bg-green-50/30", text: "text-green-600" },
|
||||||
limit: { border: "border-gray-300", bg: "bg-gray-100", text: "text-gray-500" },
|
limit: { border: "border-gray-200", bg: "bg-gray-50/40", text: "text-gray-400" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const colors = statusColors[currentStatus.level] || statusColors.idle;
|
const colors = statusColors[currentStatus.level] || statusColors.idle;
|
||||||
@@ -125,10 +125,10 @@ function InvestigationSummaryPanel({ graph, selectedQuestion, result, updateStat
|
|||||||
{/* Current understanding */}
|
{/* Current understanding */}
|
||||||
{currentUnderstanding && (
|
{currentUnderstanding && (
|
||||||
<div>
|
<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
|
What we understand so far
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm leading-relaxed text-gray-700">{currentUnderstanding}</p>
|
<p className="text-sm leading-relaxed text-gray-600">{currentUnderstanding}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ function UnexpectedStateCard({ stateName, onRetry, onRestart }) {
|
|||||||
|
|
||||||
function ContinueLaterBanner({ onRestart }) {
|
function ContinueLaterBanner({ onRestart }) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-blue-200 bg-blue-50 px-5 py-4 text-center">
|
<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-800">
|
<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.
|
Your previous investigation state is still saved. You can continue where you left off or start fresh.
|
||||||
</p>
|
</p>
|
||||||
{onRestart && (
|
{onRestart && (
|
||||||
@@ -248,13 +248,13 @@ function CurrentInvestigationCard({ selectedQuestion, graph }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="investigation-card rounded-lg border-2 border-green-300 bg-green-50 p-6">
|
<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-2 text-sm font-bold tracking-wide text-green-700">
|
<h2 className="mb-3 text-xs font-bold tracking-widest uppercase text-green-600/70">
|
||||||
Investigation
|
Investigation
|
||||||
</h2>
|
</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 && (
|
{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}
|
{whyMattersText}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -317,11 +317,11 @@ function CurrentUnderstandingCard({ currentSummary, plainLanguage }) {
|
|||||||
if (!summary) return null;
|
if (!summary) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-gray-100 bg-white px-6 pt-5 pb-6">
|
<div className="rounded-lg border border-gray-200/60 bg-white/80 px-6 pt-5 pb-6">
|
||||||
<h2 className="mb-4 text-xl font-semibold tracking-tight text-gray-800">
|
<h2 className="mb-4 text-base font-semibold tracking-tight text-gray-400">
|
||||||
Understanding
|
Understanding
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm leading-relaxed text-gray-700">{summary}</p>
|
<p className="text-sm leading-relaxed text-gray-600">{summary}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -331,11 +331,11 @@ function PlainLanguageCard({ summary }) {
|
|||||||
if (!summary) return null;
|
if (!summary) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-gray-100 bg-white px-6 pt-5 pb-6">
|
<div className="rounded-lg border border-gray-200/60 bg-white/80 px-6 pt-5 pb-6">
|
||||||
<h2 className="mb-4 text-xl font-semibold tracking-tight text-gray-800">
|
<h2 className="mb-4 text-base font-semibold tracking-tight text-gray-400">
|
||||||
Understanding
|
Understanding
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm leading-relaxed text-gray-700">{summary}</p>
|
<p className="text-sm leading-relaxed text-gray-600">{summary}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -352,7 +352,7 @@ function InvestigationHistoryCard({ turn }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<details
|
<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}
|
key={turn.id}
|
||||||
open={!isCollapsed}
|
open={!isCollapsed}
|
||||||
data-testid="investigation-turn"
|
data-testid="investigation-turn"
|
||||||
@@ -383,7 +383,7 @@ function InvestigationHistory({ turns }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3" data-testid="investigation-history">
|
<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
|
History
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -402,12 +402,12 @@ function OriginalSituation({ scenario, centralStatement }) {
|
|||||||
if (!text) return null;
|
if (!text) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-gray-100 bg-gray-50/70 px-5 py-4">
|
<div className="rounded-lg border border-gray-200/60 bg-gray-50/40 px-5 py-4">
|
||||||
<h2 className="mb-2 text-xs font-semibold tracking-wider text-gray-400">
|
<h2 className="mb-2 text-[11px] font-medium tracking-widest uppercase text-gray-300">
|
||||||
Situation
|
Situation
|
||||||
</h2>
|
</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}
|
{text}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -440,7 +440,7 @@ function UpdateAcknowledgement({ updateResult }) {
|
|||||||
className="transition-all duration-1500 ease-in"
|
className="transition-all duration-1500 ease-in"
|
||||||
style={{ opacity: visible ? 0.7 : 0, maxHeight: visible ? "4rem" : "0", marginBottom: visible ? "1rem" : "0" }}
|
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}
|
{summary}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -450,11 +450,11 @@ function UpdateAcknowledgement({ updateResult }) {
|
|||||||
// ── Developer details disclosure ──────────────────────────────
|
// ── Developer details disclosure ──────────────────────────────
|
||||||
function DeveloperDetails({ graph, selectedQuestion, diagnostics, newlySurfacedNodeIds, updateResult }) {
|
function DeveloperDetails({ graph, selectedQuestion, diagnostics, newlySurfacedNodeIds, updateResult }) {
|
||||||
return (
|
return (
|
||||||
<details className="rounded-lg border border-gray-200 bg-gray-50">
|
<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-600 hover:text-gray-800">
|
<summary className="cursor-pointer px-5 py-3 text-sm font-medium text-gray-400 hover:text-gray-600">
|
||||||
Developer details
|
Developer details
|
||||||
</summary>
|
</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 && (
|
{graph && (
|
||||||
<SituationGraphView
|
<SituationGraphView
|
||||||
situationGraph={graph}
|
situationGraph={graph}
|
||||||
@@ -482,13 +482,13 @@ function LoadingOverlay({ isLoading, elapsed, currentMessage, variant }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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">
|
<div className="flex items-center gap-3">
|
||||||
<ActivitySpinner />
|
<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>
|
</div>
|
||||||
<p className="mt-2 text-sm text-blue-700">{statusText}</p>
|
<p className="mt-3 text-sm text-blue-600/60">{statusText}</p>
|
||||||
<p className="mt-1 text-xs text-blue-500" aria-live="polite">
|
<p className="mt-2 text-xs text-blue-400/50" aria-live="polite">
|
||||||
This has been running for {elapsed}s.
|
This has been running for {elapsed}s.
|
||||||
{variant === "initial" && elapsed >= 45 && (
|
{variant === "initial" && elapsed >= 45 && (
|
||||||
<span className="block mt-1">This can take around a minute with the current local model.</span>
|
<span className="block mt-1">This can take around a minute with the current local model.</span>
|
||||||
|
|||||||
@@ -395,18 +395,18 @@ export default function ScenarioForm() {
|
|||||||
{/* Left panel — Facilitator (1/3 on desktop) */}
|
{/* Left panel — Facilitator (1/3 on desktop) */}
|
||||||
{!hideFacilitatorOnLanding && (
|
{!hideFacilitatorOnLanding && (
|
||||||
<div className="md:col-span-1">
|
<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">
|
<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-3 text-sm font-semibold tracking-wide text-gray-500 uppercase">Before we begin</h2>
|
<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-gray-700 mb-4">
|
<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.
|
The Confidence Engine helps build confidence by understanding situations before deciding what to do.
|
||||||
</p>
|
</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.
|
You do not need to know exactly what the problem is.
|
||||||
</p>
|
</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.
|
Simply describe what you have observed. We will work through it together, one question at a time.
|
||||||
</p>
|
</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
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="dismiss-facilitator"
|
id="dismiss-facilitator"
|
||||||
@@ -428,7 +428,7 @@ export default function ScenarioForm() {
|
|||||||
|
|
||||||
{/* Right panel — Workspace (2/3 on desktop) */}
|
{/* Right panel — Workspace (2/3 on desktop) */}
|
||||||
<div className={hideFacilitatorOnLanding ? "md:col-span-3" : "md:col-span-2"}>
|
<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
|
<textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
value={scenario}
|
value={scenario}
|
||||||
@@ -458,8 +458,8 @@ export default function ScenarioForm() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{status === "idle" && MOCK_ENABLED && (
|
{status === "idle" && MOCK_ENABLED && (
|
||||||
<details className="rounded-lg border border-gray-200 bg-gray-50">
|
<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-600">Developer details</summary>
|
<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 className="space-y-3 px-4 pb-4">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="mock-scenario" className="block text-xs font-medium text-gray-500 mb-1">Mock scenario</label>
|
<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"
|
type="button"
|
||||||
onClick={() => handleScenarioFill(s.key)}
|
onClick={() => handleScenarioFill(s.key)}
|
||||||
disabled={!scenario.trim() && scenario !== s.centralStatement}
|
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)
|
{s.label} ({s.turnCount} turns)
|
||||||
</button>
|
</button>
|
||||||
@@ -558,7 +558,7 @@ export default function ScenarioForm() {
|
|||||||
setCurrentUnderstanding(null);
|
setCurrentUnderstanding(null);
|
||||||
setUpdateError(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
|
Start new investigation
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -293,6 +293,33 @@ Questions
|
|||||||
- Does "Don't show again" feel preferable to automatically hiding the introduction?
|
- 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?
|
- 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:
|
Status:
|
||||||
Experimental.
|
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.
|
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.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user