Feature/product platform foundation v0.62 #1

Merged
robbond merged 683 commits from feature/product-platform-foundation-v0.62 into feature/emergent-unknowns-v0.5 2026-09-09 07:58:20 +01:00
3 changed files with 94 additions and 12 deletions
Showing only changes of commit 0da7b63e30 - Show all commits
+9 -7
View File
@@ -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
</Link>
<Link
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"
<button
onClick={() => {
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
</Link>
Restart investigation
</button>
</div>
</div>
</section>
+29
View File
@@ -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.
+56 -5
View File
@@ -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)", () => {