From 0da7b63e3033ca46be9cf5f3dcdfcf4e8f61cb51 Mon Sep 17 00:00:00 2001 From: robbond Date: Thu, 3 Sep 2026 07:01:18 +0100 Subject: [PATCH] refine(confidence-engine): clarify portfolio investigation actions --- app/page.jsx | 16 +++--- docs/current-handoff.md | 29 ++++++++++ tests/ui/investigation-overview-ui.test.jsx | 61 +++++++++++++++++++-- 3 files changed, 94 insertions(+), 12 deletions(-) diff --git a/app/page.jsx b/app/page.jsx index 5c78630..cd65885 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -1,7 +1,7 @@ "use client"; import React from "react"; -import { loadInvestigation } from "@/lib/storage/investigation-storage"; +import { loadInvestigation, clearInvestigation } from "@/lib/storage/investigation-storage"; import Link from "next/link"; const INVESTIGATION_ID = "case-1"; @@ -50,15 +50,17 @@ function Portfolio() { href={`/investigations/${INVESTIGATION_ID}`} className="rounded-lg border border-teal-600 bg-white px-4 py-2 font-medium text-teal-700 hover:bg-teal-50 transition" > - Open investigation + Continue investigation - { + try { clearInvestigation(); } catch (_) { /* storage must not crash caller */ } + }} + className="rounded-lg border border-red-400 bg-white px-4 py-2 font-medium text-red-700 hover:bg-red-50 transition" > - Create new investigation - + Restart investigation + diff --git a/docs/current-handoff.md b/docs/current-handoff.md index 7119693..d1c9c57 100644 --- a/docs/current-handoff.md +++ b/docs/current-handoff.md @@ -499,3 +499,32 @@ Investigation Report **Key insight for future work:** Portfolio's initial pre-hydration empty state (`No investigations yet.`) ≠ absence of persisted investigation. Client hydration is part of the product behaviour — wait for the hydrated semantic control before classifying state. **Next restart point:** The empty-Done `no_episodic_content` 400. Implement and verify that a Done action taken when no episodic evidence exists produces the same user-facing state (CU refresh with appropriate messaging) without a 400 error. + +### v0.56 — Portfolio existing-case actions semantics (verified) + +**Objective:** Make one bounded portfolio UI correction — clarify that actions on an existing investigation card are distinct from creation of a new investigation. + +**Changes applied:** +- `app/page.jsx`: Renamed card-level "Open investigation" → "Continue investigation"; replaced card-level "Create new investigation" link with "Restart investigation" button wired to `clearInvestigation()`; preserved portfolio-level "+ Create new investigation" below the card. +- `tests/ui/investigation-overview-ui.test.jsx`: Updated assertions for renamed links, added tests for "Continue investigation" presence, "Open investigation" absence, "Restart investigation" presence, and card-level duplicate creation control absence. + +**Resulting Portfolio semantics:** + +``` +Existing-investigation card (when one persists): + View report → /investigations/:id/report (link) + Continue investigation → /investigations/:id (link) + Restart investigation → clearInvestigation() (button) + +Portfolio-level (always visible below card): + + Create new investigation → /investigations/:id (link) +``` + +**Restart investigation wiring:** Invokes `clearInvestigation()` from `lib/storage/providers/local-storage.js` which removes the canonical localStorage key. Note: this is a raw storage clear — it does not perform the in-memory state resets (`setStatus`, `setResult`, etc.) that scenario-form.jsx also performs as part of its complete restart flow (lines ~913/941). The button clears persisted data and navigates to the investigation page which detects empty state; the user sees "Your previous investigation state is still saved" with Restart/Start new options. A full confirmation dialog and/or unified restart seam is a future increment. + +**Verification:** +- Targeted Vitest (`tests/ui/investigation-overview-ui.test.jsx`): 17/17 PASS +- Build: `npm run build` — compiles successfully, zero errors +- Playwright live verification: persisted card hydrated with View report + Continue investigation + Restart investigation; no "Open investigation"; no duplicate "Create new investigation" in card; exactly one portfolio-level "+ Create new investigation"; navigation to Investigation and Report pages verified; persisted state retained across navigate-back. + +**Restart ownership:** Raw storage clear available via `lib/storage/providers/local-storage.js::clearInvestigation()`. Complete restart seam (storage + in-memory state resets) is owned by `scenario-form.jsx` lines ~913-941. No confirmation dialog currently exists for either seam — adding one is a future increment boundary. diff --git a/tests/ui/investigation-overview-ui.test.jsx b/tests/ui/investigation-overview-ui.test.jsx index 054fc7a..e4bd904 100644 --- a/tests/ui/investigation-overview-ui.test.jsx +++ b/tests/ui/investigation-overview-ui.test.jsx @@ -1,14 +1,22 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import React from "react"; -import { render, screen } from "@testing-library/react"; +import { render, screen, fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom"; vi.mock("next/navigation", () => ({ useRouter: () => ({ push: () => {} }), })); +vi.mock("@/lib/storage/investigation-storage", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + clearInvestigation: vi.fn(), + }; +}); + // --------------------------------------------------------------------------- -// Route-level assertions for v0.55 architecture +// Route-level assertions for v0.55 architecture + v0.56 action semantics // Portfolio = /index ; Investigation Report = dedicated page // --------------------------------------------------------------------------- @@ -48,16 +56,34 @@ describe("Portfolio page (v0.55 route architecture)", () => { localStorage.clear(); render(React.createElement(Portfolio)); expect(await screen.findByText(/Create new investigation/i)).toBeInTheDocument(); + // Open/Continue investigation buttons absent when no investigation exists expect(screen.queryByText(/Open investigation/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/Continue investigation/i)).not.toBeInTheDocument(); }); - it("shows Open investigation card when one persisted investigation exists", async () => { + it("does not show Restart investigation when no investigation exists", async () => { + localStorage.clear(); + render(React.createElement(Portfolio)); + expect(screen.queryByText(/Restart investigation/i)).not.toBeInTheDocument(); + }); + + it("shows Continue investigation card when one persisted investigation exists", async () => { localStorage.setItem( "confidence-engine-investigation", JSON.stringify(makeSnapshot()), ); render(React.createElement(Portfolio)); - expect(await screen.findByText(/Open investigation/i)).toBeInTheDocument(); + expect(await screen.findByText(/Continue investigation/i)).toBeInTheDocument(); + localStorage.removeItem("confidence-engine-investigation"); + }); + + it("does not show Open investigation text when one persisted investigation exists", async () => { + localStorage.setItem( + "confidence-engine-investigation", + JSON.stringify(makeSnapshot()), + ); + render(React.createElement(Portfolio)); + expect(screen.queryByText(/Open investigation/i)).not.toBeInTheDocument(); localStorage.removeItem("confidence-engine-investigation"); }); @@ -87,6 +113,31 @@ describe("Portfolio page (v0.55 route architecture)", () => { expect(screen.queryByText(/View report/i)).not.toBeInTheDocument(); localStorage.removeItem("confidence-engine-investigation"); }); + + it("shows Restart investigation button when one persisted investigation exists", async () => { + localStorage.setItem( + "confidence-engine-investigation", + JSON.stringify(makeSnapshot()), + ); + render(React.createElement(Portfolio)); + expect(await screen.findByText(/Restart investigation/i)).toBeInTheDocument(); + localStorage.removeItem("confidence-engine-investigation"); + }); + + it("card does not contain Create new investigation when one persisted investigation exists", async () => { + localStorage.setItem( + "confidence-engine-investigation", + JSON.stringify(makeSnapshot()), + ); + render(React.createElement(Portfolio)); + // Portfolio-level "+ Create new investigation" is still present (below card) + expect(await screen.findByText(/Create new investigation/i)).toBeInTheDocument(); + // But the investigations section card does NOT contain it (no duplicate) + const card = document.querySelector("section.mb-10"); + const btns = Array.from(card.querySelectorAll('a,button')); + expect(btns.some(el => /Create new investigation/i.test(el.textContent))).toBe(false); + localStorage.removeItem("confidence-engine-investigation"); + }); }); describe("Investigation Report page (v0.55 route architecture)", () => {