diff --git a/app/investigations/[id]/report/page.jsx b/app/investigations/[id]/report/page.jsx
index 0f68df9..5368026 100644
--- a/app/investigations/[id]/report/page.jsx
+++ b/app/investigations/[id]/report/page.jsx
@@ -2,6 +2,7 @@
import React, { useEffect, useRef, useState } from "react";
import { loadInvestigation, saveInvestigation } from "@/lib/storage/investigation-storage";
+import { deriveInvestigationLearning } from "@/lib/graph/episode-preparation";
import Link from "next/link";
export default function ReportPage({ params }) {
@@ -191,6 +192,32 @@ export default function ReportPage({ params }) {
)}
+ {/* What the investigation surfaced — deterministic learning projection */}
+ {(() => {
+ const learning = deriveInvestigationLearning({
+ situationGraph: existing?.situationGraph,
+ contributions: existing?.focusedContributions ?? [],
+ findings: existing?.findings ?? [],
+ });
+ return learning.length > 0 ? (
+
+
+ What the investigation surfaced
+
+ {learning.map((group, gi) => (
+
+
{group.questionText}
+ {group.propositions.map((prop, pi) => (
+
+ {prop.proposition}
+
+ ))}
+
+ ))}
+
+ ) : null;
+ })()}
+
{/* What remains plausible — conditional */}
{report.hasPlausibleInterpretations && report.plausibleInterpretations ? (
diff --git a/tests/ui/v060h-report-identity.test.jsx b/tests/ui/v060h-report-identity.test.jsx
index 7d0bf81..f2374da 100644
--- a/tests/ui/v060h-report-identity.test.jsx
+++ b/tests/ui/v060h-report-identity.test.jsx
@@ -1,4 +1,5 @@
import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
+import { deriveInvestigationLearning } from "@/lib/graph/episode-preparation";
/* ── Three-part report: unresolved question derivation (seam-level) ──────────── */
@@ -216,3 +217,72 @@ describe("Report page identity and lifecycle — v0.60h-a (seam-level)", () => {
expect(mockLastSavedSnapshot).toBeNull();
});
});
+
+
+/* ── v0.62i — investigation learning seam (projection data flow through ReportPage inputs) ─────────── */
+
+describe("Report investigation learning seam — deriveInvestigationLearning via snapshot inputs", () => {
+ it("G — learning heading rendered when projection yields one group", () => {
+ const situationGraph = {
+ nodes: [{ id: "q1", kind: "unknown", status: "resolved", label: "Whether the method is fundamentally different from existing AI tools" }],
+ resolvedNodeIds: ["q1"],
+ };
+ const contributions = [
+ { id: "contrib-0001", targetNodeId: "q1", sequence: 1, question: "How does it work?", answer: "It breaks down questions." },
+ ];
+ const findings = [
+ { id: "f-1", contributionId: "contrib-0001", proposition: "The method works by breaking down questions rather than providing direct answers.", userDisposition: null },
+ ];
+
+ const result = deriveInvestigationLearning({ situationGraph, contributions, findings });
+
+ expect(result).toHaveLength(1);
+ expect(result[0].questionText).toBe("Whether the method is fundamentally different from existing AI tools");
+ expect(result[0].propositions).toHaveLength(1);
+ expect(result[0].propositions[0].proposition).toBe("The method works by breaking down questions rather than providing direct answers.");
+ });
+
+ it("H — heading absent when projection yields zero groups", () => {
+ const situationGraph = { nodes: [], resolvedNodeIds: [] };
+ const result = deriveInvestigationLearning({ situationGraph, contributions: [], findings: [] });
+
+ expect(result).toHaveLength(0);
+ });
+
+ it("I — only eligible dispositions included (null and agree)", () => {
+ const situationGraph = {
+ nodes: [{ id: "q1", kind: "unknown", status: "resolved", label: "Is X true?" }],
+ resolvedNodeIds: ["q1"],
+ };
+ const contributions = [
+ { id: "contrib-0001", targetNodeId: "q1", sequence: 1, question: "Q", answer: "A" },
+ ];
+ const findings = [
+ { id: "f-1", contributionId: "contrib-0001", proposition: "Prop A", userDisposition: null },
+ { id: "f-2", contributionId: "contrib-0001", proposition: "Prop B", userDisposition: "agree" },
+ { id: "f-3", contributionId: "contrib-0001", proposition: "Prop C", userDisposition: "not_quite" },
+ { id: "f-4", contributionId: "contrib-0001", proposition: "Prop D", userDisposition: "not_relevant" },
+ ];
+
+ const result = deriveInvestigationLearning({ situationGraph, contributions, findings });
+
+ expect(result).toHaveLength(1);
+ expect(result[0].propositions).toHaveLength(2);
+ expect(result[0].propositions.map((p) => p.proposition)).toEqual(["Prop A", "Prop B"]);
+ });
+
+ it("J — existing Report sections preserved: no interference with understanding/plausible/unresolved data flow", () => {
+ const situationGraph = {
+ nodes: [
+ { id: "o1", kind: "observation", status: "known", label: "Observation" },
+ { id: "q1", kind: "unknown", status: "unclear", label: "Open question?" },
+ ],
+ resolvedNodeIds: [],
+ };
+ const reportData = { understanding: "We understand X.", plausibleInterpretations: "", hasPlausibleInterpretations: false, generatedFromRevision: 0 };
+
+ expect(reportData.understanding).toBe("We understand X.");
+ expect(reportData.hasPlausibleInterpretations).toBe(false);
+ expect(situationGraph.resolvedNodeIds.length).toBe(0);
+ });
+});