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.
|
||||
|
||||
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;
|
||||
|
||||
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} />
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user