From 86d9bc3f48637395a43d21782ee7e24adf7878f4 Mon Sep 17 00:00:00 2001 From: robbond Date: Wed, 5 Aug 2026 10:20:28 +0100 Subject: [PATCH] refactor: clarify investigation map as ux placeholder --- components/investigation-map.jsx | 6 +-- docs/reasoning-contract-backlog.md | 26 ++++++++++- lib/map/investigation-map-adapter.js | 65 +++++++++++++--------------- tests/e2e/happy-path.spec.js | 40 ++++++++--------- 4 files changed, 76 insertions(+), 61 deletions(-) diff --git a/components/investigation-map.jsx b/components/investigation-map.jsx index 1360013..1bf3d1d 100644 --- a/components/investigation-map.jsx +++ b/components/investigation-map.jsx @@ -82,12 +82,12 @@ export default function InvestigationMap({ turnCount = 0 }) { if (!hasActiveTopics && groups.established.length === 0) return null; return ( -
+

- Investigation Map + Investigation Map Preview

- We are building an understanding of your situation one question at a time. + This preview shows where a future reasoning map may appear. Its final shape will emerge from the reasoning engine.

diff --git a/docs/reasoning-contract-backlog.md b/docs/reasoning-contract-backlog.md index dad316c..a165481 100644 --- a/docs/reasoning-contract-backlog.md +++ b/docs/reasoning-contract-backlog.md @@ -99,10 +99,32 @@ temporary workaround and the desired eventual contract. ## Investigation Map (Workspace UX) +### Open design decision — final map shape intentionally unresolved + +The current Investigation Map implementation exists **only** to validate: +- placement within the workspace; +- information density at preview scale; +- status presentation (established / current / unknown); +- responsive layout across viewports; +- interaction with surrounding components across turns. + +It is NOT a committed design. The eventual map should be derived from the reasoning engine, not from hard-coded UI categories. + +The following are unresolved design questions — do NOT treat them as agreed contract fields: + +- Will the engine provide a flat topic list, hierarchy, branches, or grouped clusters? +- Who determines ordering — engine or user interaction? +- Will there be evidence counts, completion percentages, or path metadata? +- How does the map handle dynamic addition/removal of topics during investigation? + +### Current entry (temporary) + | Feature | UI need | Temporary mock | Desired reasoning output | Likely stage | Notes | | ---------------------- | ---------------------------------- | --------------------------------------------------- | -------------------------------------------------------------- | ---------------------- | -------------------------------------------------------- | -| InvestigationMap | Visible investigation progress | Mock topic set with manual turn-based status progression | Engine emits `investigationTopics: [{ title, status, ordering?, evidenceCount? }]` | Each turn — start and update response | UI displays topics in engine-determined order; statuses: "established" / "current" / "unknown" | -| InvestigationMap | Topic status evolution across turns | Hardcoded PROGRESSION array indexed by `investigationHistory.length` | Engine determines which topics are established, active, or unknown at each turn | Question selection phase | Topics should not expose graph internals; plain-language labels only | +| InvestigationMap | Visible investigation progress | **mock-only placeholder**: minimal set of neutral topic names (≤5) with manual turn-based status progression | Engine emits `investigationTopics: [{ title, status, ordering?, evidenceCount? }]` | Each turn — start and update response | UI displays topics in engine-determined order; statuses: "established" / "current" / "unknown" | +| InvestigationMap | Topic status evolution across turns | **mock-only placeholder**: Hardcoded PROGRESSION array indexed by `investigationHistory.length` | Engine determines which topics are established, active, or unknown at each turn | Question selection phase | Topics should not expose graph internals; plain-language labels only | + +The current adapter (`lib/map/investigation-map-adapter.js`) uses generic placeholder names (e.g. "Starting point", "Current focus") explicitly because they do NOT represent a domain-specific design decision. ## Open Questions / Future Work diff --git a/lib/map/investigation-map-adapter.js b/lib/map/investigation-map-adapter.js index 89171fe..015e2a0 100644 --- a/lib/map/investigation-map-adapter.js +++ b/lib/map/investigation-map-adapter.js @@ -1,50 +1,46 @@ /** - * Investigation Map Mock Adapter - * - * Provides the Investigation Map with a set of investigation topics and their - * current status (established / current / unknown). - * - * TODO: Replace this mock adapter when the reasoning engine emits real - * investigation data. The eventual contract should provide: - * - `investigationTopics`: [{ title, status, evidenceCount? }] - * - `topicStatus` values: "established" | "current" | "unknown" - * - `topicOrdering`: the reasoning-engine-determined sequence - * - `evidenceCount`: optional count of supporting evidence per topic - * - * Until then, this adapter drives a realistic mock progression across turns. + * ┌─────────────────────────────────────────────────────────────────────┐ + * │ INVESTIGATION MAP — UX PLACEHOLDER ADAPTER │ + * │ │ + * │ This adapter drives a minimal mock to validate UX placement, │ + * │ spacing, status appearance, responsive behaviour, and state │ + * │ change across turns. │ + * │ │ + * │ The topic names below are mock-only placeholders. They are NOT │ + * │ part of the reasoning contract and do NOT imply the final map │ + * │ structure — which may be hierarchical, grouped, branching, or │ + * │ something else entirely. │ + * │ │ + * │ The UI will eventually consume real reasoning output once that │ + * │ design stabilises. │ + * └─────────────────────────────────────────────────────────────────────┘ */ -/** @type {Array<{ title: string }>} */ +/** @type {Array<{ title: string }>} — mock-only placeholder names */ const TOPICS = [ - { title: "Central situation" }, - { title: "Complaint trend direction" }, - { title: "Measurement basis" }, - { title: "Production volume context" }, - { title: "QA process changes" }, - { title: "Product change log" }, - { title: "Support response patterns" }, - { title: "Prior similar cases" }, + { title: "Starting point" }, + { title: "What is known" }, + { title: "Current focus" }, + { title: "Questions still open" }, + { title: "Possible explanations" }, ]; /** * Status progression per turn index. * The reasoning engine will eventually determine these values. + * + * With N placeholder topics we have N-1 progressive states (indices 0 to N-2). + * Beyond that the map stabilises: all topics established except the last one current. */ const PROGRESSION = [ // Turn 0 — initial analysis just started - ["established", "unknown", "unknown", "unknown", "unknown", "unknown", "unknown", "unknown"], + ["established", "current", "unknown", "unknown", "unknown"], // Turn 1 — first question answered - ["established", "established", "current", "unknown", "unknown", "unknown", "unknown", "unknown"], + ["established", "established", "current", "unknown", "unknown"], // Turn 2 — second question answered - ["established", "established", "established", "current", "unknown", "unknown", "unknown", "unknown"], - // Turn 3 — third question answered - ["established", "established", "established", "established", "current", "unknown", "unknown", "unknown"], - // Turn 4 — fourth question answered - ["established", "established", "established", "established", "established", "current", "unknown", "unknown"], - // Turn 5 — fifth question answered - ["established", "established", "established", "established", "established", "established", "current", "unknown"], - // Turn 6+ — final turn - ["established", "established", "established", "established", "established", "established", "established", "current"], + ["established", "established", "established", "current", "unknown"], + // Turn 3+ — third answer and beyond (all topics resolved, last in progress) + ["established", "established", "established", "established", "current"], ]; /** @@ -53,7 +49,8 @@ const PROGRESSION = [ * @returns {{ title: string, status: 'established' | 'current' | 'unknown' }[]} */ export function getInvestigationMapTopics(turnIndex) { - const idx = Math.min(turnIndex, PROGRESSION.length - 1); + // Clamp to last progression entry so the map stabilises when all topics are covered + const idx = Math.min(Math.max(turnIndex, 0), PROGRESSION.length - 1); return TOPICS.map((topic, i) => ({ title: topic.title, status: PROGRESSION[idx][i], diff --git a/tests/e2e/happy-path.spec.js b/tests/e2e/happy-path.spec.js index fb0cbd7..d591e33 100644 --- a/tests/e2e/happy-path.spec.js +++ b/tests/e2e/happy-path.spec.js @@ -371,7 +371,7 @@ test("update submission replaces response panel with loading card, preserves con /* ═══════ Test: investigation map appears and evolves across turns ═ */ -test("investigation map: appears after start, topics evolve across mocked turns", async ({ page }) => { +test("investigation map preview: appears after start, topics evolve across mocked turns", async ({ page }) => { await page.goto("/"); await page.evaluate(() => { @@ -387,24 +387,22 @@ test("investigation map: appears after start, topics evolve across mocked turns" await waitForLoading(page); await waitForWorkspaceReady(page); - // Verify the investigation map card is present after start - const mapCard = page.locator('[aria-label="Investigation map"]'); + // Verify the investigation map preview card is present after start + const mapCard = page.locator('[aria-label="Investigation map preview"]'); await expect(mapCard).toBeVisible(); - // Map heading visible - await expect(mapCard.getByRole("heading", { name: "Investigation Map" })).toBeVisible(); + // Preview heading visible + await expect(mapCard.getByRole("heading", { name: "Investigation Map Preview" })).toBeVisible(); - // Helper text visible - await expect(mapCard.getByText(/building an understanding/i)).toBeVisible(); + // Placeholder note visible (secondary text) + await expect(mapCard.getByText(/This preview shows where a future reasoning map/i)).toBeVisible(); - // At turn 0 (initial analysis): one topic established, one current, rest unknown - const establishedTopics = page.locator('[data-testid="map-topic-established"]'); - const currentTopics = page.locator('[data-testid="map-topic-current"]'); - const unknownTopics = page.locator('[data-testid="map-topic-unknown"]'); + // Helper for counting topics by status across turns + const byStatus = (status) => page.locator(`[data-testid="map-topic-${status}"]`); - await expect(establishedTopics).toHaveCount(1); - await expect(currentTopics).toHaveCount(1); - await expect(unknownTopics).toHaveCount(6); + // At turn 0: one established + one current (≤5 neutral placeholder topics total) + await expect(byStatus("established")).toHaveCount(1); + await expect(byStatus("current")).toHaveCount(1); // Submit first answer → turn 1: one more established, next topic becomes current const answerTextarea = page.locator('textarea[placeholder*=Answer]'); @@ -418,10 +416,9 @@ test("investigation map: appears after start, topics evolve across mocked turns" // Current investigation remains visible (context preserved) await expect(page.getByRole("heading", { name: "Current investigation" })).toBeVisible({ timeout: 5_000 }); - // Map should update: 2 established, 1 current, 5 unknown - await expect(establishedTopics).toHaveCount(2); - await expect(currentTopics).toHaveCount(1); - await expect(unknownTopics).toHaveCount(5); + // Map updated: established count increased, current still present (state shift across turns) + await expect(byStatus("established")).toHaveCount(2); + await expect(byStatus("current")).toHaveCount(1); // Submit second answer → turn 2 const answerTextarea2 = page.locator('textarea[placeholder*=Answer]'); @@ -432,10 +429,9 @@ test("investigation map: appears after start, topics evolve across mocked turns" await waitForLoading(page); await waitForWorkspaceReady(page); - // After second update: 3 established, current shifts again - await expect(establishedTopics).toHaveCount(3); - await expect(currentTopics).toHaveCount(1); - await expect(unknownTopics).toHaveCount(4); + // After second update: established count increased again (current shifted) + await expect(byStatus("established")).toHaveCount(3); + await expect(byStatus("current")).toHaveCount(1); } } }); \ No newline at end of file