diff --git a/src/lib/__tests__/legal-case-files-feed.test.tsx b/src/lib/__tests__/legal-case-files-feed.test.tsx
new file mode 100644
index 0000000..ce7295d
--- /dev/null
+++ b/src/lib/__tests__/legal-case-files-feed.test.tsx
@@ -0,0 +1,90 @@
+/**
+ * Tests for LegalCaseFilesFeed.
+ * - Assert getLatestNodes is NEVER called regardless of store state.
+ */
+import { describe, it, expect, vi, beforeEach } from "vitest"
+import { render } from "@testing-library/react"
+import React from "react"
+
+// ── Spy on graph-api to ensure getLatestNodes is never called ─────────────────
+const getLatestNodesMock = vi.fn()
+
+vi.mock("@/lib/graph-api", () => ({
+ getLatestNodes: getLatestNodesMock,
+}))
+
+// ── Mock mock-data ────────────────────────────────────────────────────────────
+vi.mock("@/lib/mock-data", () => ({
+ isMocksEnabled: () => false,
+ MOCK_NODES: [],
+ MOCK_EDGES: [],
+}))
+
+// ── Graph store ───────────────────────────────────────────────────────────────
+const graphState = {
+ nodes: [] as unknown[],
+ edges: [],
+ loading: false,
+ selectedNode: null,
+ setSelectedNode: vi.fn(),
+ setSidebarSelectedNode: vi.fn(),
+ clearSelection: vi.fn(),
+ setGraphData: vi.fn(),
+ setLoading: vi.fn(),
+}
+
+vi.mock("@/stores/graph-store", () => ({
+ useGraphStore: (sel?: (s: unknown) => unknown) => {
+ if (!sel) return graphState
+ return sel(graphState)
+ },
+}))
+
+// ── App store ─────────────────────────────────────────────────────────────────
+vi.mock("@/stores/app-store", () => ({
+ useAppStore: (sel?: (s: unknown) => unknown) => {
+ const state = { searchTerm: "" }
+ return sel ? sel(state) : state
+ },
+}))
+
+import { LegalCaseFilesFeed } from "@/skins/legal/legal-case-files-feed"
+
+beforeEach(() => {
+ vi.clearAllMocks()
+ graphState.nodes = []
+})
+
+describe("LegalCaseFilesFeed mount", () => {
+ it("never calls getLatestNodes when store is empty", async () => {
+ render()
+
+ // Wait for any potential async effects
+ await new Promise((r) => setTimeout(r, 50))
+
+ expect(getLatestNodesMock).not.toHaveBeenCalled()
+ })
+
+ it("never calls getLatestNodes when store already has nodes", async () => {
+ graphState.nodes = [{ ref_id: "existing-1", node_type: "Agreement", properties: {} }]
+
+ render()
+
+ await new Promise((r) => setTimeout(r, 50))
+
+ expect(getLatestNodesMock).not.toHaveBeenCalled()
+ })
+
+ it("renders nodes from the store without fetching", async () => {
+ graphState.nodes = [
+ { ref_id: "agr-1", node_type: "Agreement", properties: { title: "Contract Alpha" } },
+ ]
+
+ const { getByText } = render()
+
+ await new Promise((r) => setTimeout(r, 50))
+
+ expect(getByText("Contract Alpha")).toBeDefined()
+ expect(getLatestNodesMock).not.toHaveBeenCalled()
+ })
+})
diff --git a/src/lib/__tests__/legal-graph-pane.test.tsx b/src/lib/__tests__/legal-graph-pane.test.tsx
new file mode 100644
index 0000000..26d442c
--- /dev/null
+++ b/src/lib/__tests__/legal-graph-pane.test.tsx
@@ -0,0 +1,169 @@
+/**
+ * Tests for LegalGraphPane mount behavior.
+ * - Store empty → getLegalInitialNodes called, setGraphData called with result
+ * - Store non-empty → getLegalInitialNodes NOT called
+ */
+import { describe, it, expect, vi, beforeEach } from "vitest"
+import { render } from "@testing-library/react"
+import React from "react"
+
+// ── Hoist all mocks ───────────────────────────────────────────────────────────
+const {
+ getLegalInitialNodesMock,
+ isMocksEnabledMock,
+ setGraphDataMock,
+ setLoadingMock,
+ graphNodes,
+} = vi.hoisted(() => {
+ const graphNodes: unknown[] = []
+ return {
+ getLegalInitialNodesMock: vi.fn(),
+ isMocksEnabledMock: vi.fn(() => false),
+ setGraphDataMock: vi.fn(),
+ setLoadingMock: vi.fn(),
+ graphNodes,
+ }
+})
+
+// ── Module mocks ──────────────────────────────────────────────────────────────
+vi.mock("@/lib/graph-api", () => ({
+ getLegalInitialNodes: getLegalInitialNodesMock,
+}))
+
+vi.mock("@/lib/mock-data", () => ({
+ isMocksEnabled: () => isMocksEnabledMock(),
+ MOCK_NODES: [{ ref_id: "mock-1", node_type: "Topic", properties: {} }],
+ MOCK_EDGES: [{ source: "mock-1", target: "mock-2", edge_type: "RELATED" }],
+}))
+
+vi.mock("@/stores/graph-store", () => {
+ const graphState = {
+ get nodes() { return graphNodes },
+ edges: [],
+ selectedNode: null,
+ loadingNeighborRefs: new Set(),
+ setSelectedNode: vi.fn(),
+ clearSelection: vi.fn(),
+ setGraphData: setGraphDataMock,
+ setLoading: setLoadingMock,
+ }
+ const useGraphStore = (sel?: (s: unknown) => unknown) => {
+ if (!sel) return graphState
+ return sel(graphState)
+ }
+ useGraphStore.getState = () => graphState
+ return { useGraphStore }
+})
+
+vi.mock("@/stores/app-store", () => ({
+ useAppStore: (sel?: (s: unknown) => unknown) => {
+ const state = {
+ graphName: "Test Graph",
+ searchTerm: "",
+ sourcesOpen: false,
+ myContentOpen: false,
+ followingOpen: false,
+ agentOpen: false,
+ clipsOpen: false,
+ workflowsOpen: false,
+ closeAllPanels: vi.fn(),
+ setSearchTerm: vi.fn(),
+ toggleSources: vi.fn(),
+ toggleMyContent: vi.fn(),
+ toggleFollowing: vi.fn(),
+ toggleAgent: vi.fn(),
+ toggleWorkflows: vi.fn(),
+ }
+ return sel ? sel(state) : state
+ },
+}))
+
+vi.mock("@/stores/schema-store", () => ({
+ useSchemaStore: (sel?: (s: unknown) => unknown) => {
+ const state = { schemas: [] }
+ return sel ? sel(state) : state
+ },
+}))
+
+vi.mock("@/components/universe/graph-canvas", () => ({
+ GraphCanvas: () =>
,
+}))
+vi.mock("@/components/search/search-bar", () => ({
+ SearchBar: () => ,
+}))
+vi.mock("@/components/layout/toolkit", () => ({
+ Toolkit: () => ,
+ ToolkitFAB: () => ,
+}))
+
+import { LegalGraphPane } from "@/skins/legal/legal-graph-pane"
+
+beforeEach(() => {
+ vi.clearAllMocks()
+ isMocksEnabledMock.mockReturnValue(false)
+ // Reset graphNodes to empty
+ graphNodes.length = 0
+})
+
+// ── Tests ─────────────────────────────────────────────────────────────────────
+
+describe("LegalGraphPane mount", () => {
+ it("calls getLegalInitialNodes and setGraphData when store is empty", async () => {
+ const mockResult = {
+ nodes: [{ ref_id: "agr-1", node_type: "Agreement", properties: {} }],
+ edges: [],
+ }
+ getLegalInitialNodesMock.mockResolvedValue(mockResult)
+
+ render()
+
+ await vi.waitFor(() => {
+ expect(getLegalInitialNodesMock).toHaveBeenCalledTimes(1)
+ })
+
+ expect(setGraphDataMock).toHaveBeenCalledWith(mockResult.nodes, mockResult.edges)
+ expect(setLoadingMock).toHaveBeenCalledWith(true)
+ })
+
+ it("does NOT call getLegalInitialNodes when store already has nodes", async () => {
+ // Pre-populate graphNodes
+ graphNodes.push({ ref_id: "existing", node_type: "Agreement", properties: {} })
+
+ render()
+
+ await new Promise((r) => setTimeout(r, 50))
+
+ expect(getLegalInitialNodesMock).not.toHaveBeenCalled()
+ })
+
+ it("falls back to MOCK_NODES/MOCK_EDGES when mocks are enabled", async () => {
+ isMocksEnabledMock.mockReturnValue(true)
+
+ render()
+
+ await new Promise((r) => setTimeout(r, 50))
+
+ expect(getLegalInitialNodesMock).not.toHaveBeenCalled()
+ expect(setGraphDataMock).toHaveBeenCalledWith(
+ [{ ref_id: "mock-1", node_type: "Topic", properties: {} }],
+ [{ source: "mock-1", target: "mock-2", edge_type: "RELATED" }]
+ )
+ })
+
+ it("logs error and still calls setLoading(false) when getLegalInitialNodes rejects", async () => {
+ const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
+ getLegalInitialNodesMock.mockRejectedValue(new Error("network error"))
+
+ render()
+
+ await vi.waitFor(() => {
+ expect(consoleSpy).toHaveBeenCalledWith(
+ "[legal-graph-pane] getLegalInitialNodes failed:",
+ expect.any(Error)
+ )
+ })
+
+ expect(setLoadingMock).toHaveBeenCalledWith(false)
+ consoleSpy.mockRestore()
+ })
+})
diff --git a/src/lib/__tests__/legal-seed.test.ts b/src/lib/__tests__/legal-seed.test.ts
new file mode 100644
index 0000000..802e5d9
--- /dev/null
+++ b/src/lib/__tests__/legal-seed.test.ts
@@ -0,0 +1,259 @@
+/**
+ * Unit tests for getLegalInitialNodes() and related legal-skin components.
+ */
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
+
+// ── Sphinx / auth mocks ───────────────────────────────────────────────────────
+const { getL402Mock, getSignedMessageMock } = vi.hoisted(() => ({
+ getL402Mock: vi.fn(),
+ getSignedMessageMock: vi.fn(),
+}))
+
+vi.mock("@/lib/sphinx", () => ({
+ getL402: getL402Mock,
+ getSignedMessage: getSignedMessageMock,
+}))
+
+// Real API mode by default (overridden per-test where needed)
+vi.mock("@/lib/mock-data", () => ({
+ isMocksEnabled: () => false,
+ MOCK_NODES: [],
+ MOCK_EDGES: [],
+ MOCK_REVIEWS: [],
+ MOCK_WORKFLOW_MARKETPLACE: [],
+}))
+
+import { getLegalInitialNodes } from "@/lib/graph-api"
+import type { GraphNode, GraphEdge } from "@/lib/graph-api"
+
+// ── Helpers ───────────────────────────────────────────────────────────────────
+
+function makeNode(ref_id: string, node_type: string): GraphNode {
+ return { ref_id, node_type, properties: { name: ref_id } }
+}
+
+function makeEdge(source: string, target: string, edge_type = "RELATED", ref_id?: string): GraphEdge {
+ return { source, target, edge_type, ref_id }
+}
+
+// Build a minimal fetch mock that handles all the expected URL patterns
+function buildFetchMock(config: {
+ agreements: GraphNode[]
+ expansions: Record
+ topupOrgs?: GraphNode[]
+ topupPersons?: GraphNode[]
+}) {
+ return vi.fn(async (url: string) => {
+ const u = url.toString()
+
+ // Agreement latest
+ if (u.includes("node_type=Agreement")) {
+ return { ok: true, json: async () => ({ nodes: config.agreements }) }
+ }
+ // Organization top-up
+ if (u.includes("node_type=Organization")) {
+ return { ok: true, json: async () => ({ nodes: config.topupOrgs ?? [] }) }
+ }
+ // Person top-up
+ if (u.includes("node_type=Person")) {
+ return { ok: true, json: async () => ({ nodes: config.topupPersons ?? [] }) }
+ }
+ // Node expand (/v2/nodes/:refId?expand=edges)
+ const expandMatch = u.match(/\/v2\/nodes\/([^?]+)\?expand=edges/)
+ if (expandMatch) {
+ const refId = expandMatch[1]
+ const data = config.expansions[refId] ?? { nodes: [], edges: [] }
+ return { ok: true, json: async () => data }
+ }
+
+ return { ok: true, json: async () => ({}) }
+ }) as unknown as typeof fetch
+}
+
+const originalFetch = global.fetch
+
+beforeEach(() => {
+ getSignedMessageMock.mockResolvedValue({ signature: "", message: "" })
+ getL402Mock.mockResolvedValue(null)
+})
+
+afterEach(() => {
+ global.fetch = originalFetch
+ vi.clearAllMocks()
+})
+
+// ── getLegalInitialNodes ───────────────────────────────────────────────────────
+
+describe("getLegalInitialNodes", () => {
+ it("returns agreements + expanded orgs/persons and filters edges correctly", async () => {
+ const agreement = makeNode("agr-1", "Agreement")
+ const org1 = makeNode("org-1", "Organization")
+ const person1 = makeNode("per-1", "Person")
+ const edgeInSet = makeEdge("agr-1", "org-1", "HAS_PARTY", "e-1")
+ const edgeOutOfSet = makeEdge("agr-1", "unknown-node", "UNRELATED", "e-2")
+
+ global.fetch = buildFetchMock({
+ agreements: [agreement],
+ expansions: {
+ "agr-1": {
+ nodes: [org1, person1],
+ edges: [edgeInSet, edgeOutOfSet],
+ },
+ },
+ topupOrgs: [], // not needed — already ≥ 1 (test with < 10 but no top-up orgs)
+ topupPersons: [],
+ })
+
+ const result = await getLegalInitialNodes()
+
+ // Final nodes: 1 agreement + 1 org + 1 person
+ expect(result.nodes).toHaveLength(3)
+ const refIds = result.nodes.map((n) => n.ref_id)
+ expect(refIds).toContain("agr-1")
+ expect(refIds).toContain("org-1")
+ expect(refIds).toContain("per-1")
+
+ // Only the edge with both endpoints in the final set is kept
+ expect(result.edges).toHaveLength(1)
+ expect(result.edges[0].ref_id).toBe("e-1")
+ })
+
+ it("fires top-up for both Org and Person when no neighbors found (0 expansion)", async () => {
+ const agreement = makeNode("agr-1", "Agreement")
+ const topupOrg = makeNode("org-tu-1", "Organization")
+ const topupPerson = makeNode("per-tu-1", "Person")
+
+ global.fetch = buildFetchMock({
+ agreements: [agreement],
+ expansions: { "agr-1": { nodes: [], edges: [] } },
+ topupOrgs: [topupOrg],
+ topupPersons: [topupPerson],
+ })
+
+ const result = await getLegalInitialNodes()
+
+ const types = result.nodes.map((n) => n.node_type)
+ expect(types).toContain("Agreement")
+ expect(types).toContain("Organization")
+ expect(types).toContain("Person")
+ })
+
+ it("top-up adds exactly 2 more orgs when 8 already found via expansion", async () => {
+ const agreement = makeNode("agr-1", "Agreement")
+ // 8 orgs from expansion
+ const expansionOrgs = Array.from({ length: 8 }, (_, i) => makeNode(`org-${i}`, "Organization"))
+ const expansionEdges = expansionOrgs.map((o) =>
+ makeEdge("agr-1", o.ref_id, "HAS_PARTY", `e-${o.ref_id}`)
+ )
+ // 5 top-up orgs (only 2 should be taken)
+ const topupOrgs = Array.from({ length: 5 }, (_, i) => makeNode(`org-tu-${i}`, "Organization"))
+
+ global.fetch = buildFetchMock({
+ agreements: [agreement],
+ expansions: { "agr-1": { nodes: expansionOrgs, edges: expansionEdges } },
+ topupOrgs,
+ topupPersons: [],
+ })
+
+ const result = await getLegalInitialNodes()
+
+ const orgNodes = result.nodes.filter((n) => n.node_type === "Organization")
+ expect(orgNodes).toHaveLength(10)
+ })
+
+ it("deduplicates orgs appearing in multiple agreement expansions", async () => {
+ const agr1 = makeNode("agr-1", "Agreement")
+ const agr2 = makeNode("agr-2", "Agreement")
+ const sharedOrg = makeNode("org-shared", "Organization")
+ const edge1 = makeEdge("agr-1", "org-shared", "HAS_PARTY", "e-1")
+ const edge2 = makeEdge("agr-2", "org-shared", "HAS_PARTY", "e-2")
+
+ global.fetch = buildFetchMock({
+ agreements: [agr1, agr2],
+ expansions: {
+ "agr-1": { nodes: [sharedOrg], edges: [edge1] },
+ "agr-2": { nodes: [sharedOrg], edges: [edge2] },
+ },
+ topupOrgs: [],
+ topupPersons: [],
+ })
+
+ const result = await getLegalInitialNodes()
+
+ const orgNodes = result.nodes.filter((n) => n.node_type === "Organization")
+ expect(orgNodes).toHaveLength(1)
+ expect(orgNodes[0].ref_id).toBe("org-shared")
+ })
+
+ it("excludes edges where one or both endpoints are not in the final node set", async () => {
+ const agreement = makeNode("agr-1", "Agreement")
+ const org = makeNode("org-1", "Organization")
+ const outsider = makeNode("outsider-1", "SomeOtherType")
+
+ const edgeValid = makeEdge("agr-1", "org-1", "HAS_PARTY", "e-valid")
+ const edgeMissingTarget = makeEdge("agr-1", "outsider-1", "UNRELATED", "e-bad-target")
+ const edgeMissingBoth = makeEdge("nobody-a", "nobody-b", "UNRELATED", "e-bad-both")
+
+ global.fetch = buildFetchMock({
+ agreements: [agreement],
+ expansions: {
+ "agr-1": {
+ nodes: [org, outsider],
+ edges: [edgeValid, edgeMissingTarget, edgeMissingBoth],
+ },
+ },
+ topupOrgs: [],
+ topupPersons: [],
+ })
+
+ const result = await getLegalInitialNodes()
+
+ // outsider is not Agreement/Organization/Person — not in final set
+ expect(result.edges).toHaveLength(1)
+ expect(result.edges[0].ref_id).toBe("e-valid")
+ })
+
+ it("deduplicates edges by ref_id", async () => {
+ const agr1 = makeNode("agr-1", "Agreement")
+ const agr2 = makeNode("agr-2", "Agreement")
+ const org = makeNode("org-1", "Organization")
+
+ // Same edge ref_id returned from two expansions
+ const dupEdge1 = makeEdge("agr-1", "org-1", "HAS_PARTY", "dup-edge")
+ const dupEdge2 = makeEdge("agr-1", "org-1", "HAS_PARTY", "dup-edge")
+
+ global.fetch = buildFetchMock({
+ agreements: [agr1, agr2],
+ expansions: {
+ "agr-1": { nodes: [org], edges: [dupEdge1] },
+ "agr-2": { nodes: [org], edges: [dupEdge2] },
+ },
+ topupOrgs: [],
+ topupPersons: [],
+ })
+
+ const result = await getLegalInitialNodes()
+
+ expect(result.edges).toHaveLength(1)
+ })
+
+ it("deduplicates edges by composite key when ref_id is absent", async () => {
+ const agreement = makeNode("agr-1", "Agreement")
+ const org = makeNode("org-1", "Organization")
+
+ // No ref_id on these edges — dedup by source+target+type composite
+ const edge1: GraphEdge = { source: "agr-1", target: "org-1", edge_type: "HAS_PARTY" }
+ const edge2: GraphEdge = { source: "agr-1", target: "org-1", edge_type: "HAS_PARTY" }
+
+ global.fetch = buildFetchMock({
+ agreements: [agreement],
+ expansions: { "agr-1": { nodes: [org], edges: [edge1, edge2] } },
+ topupOrgs: [],
+ topupPersons: [],
+ })
+
+ const result = await getLegalInitialNodes()
+
+ expect(result.edges).toHaveLength(1)
+ })
+})
diff --git a/src/lib/graph-api.ts b/src/lib/graph-api.ts
index 3183516..59f2811 100644
--- a/src/lib/graph-api.ts
+++ b/src/lib/graph-api.ts
@@ -876,3 +876,95 @@ export async function addLegalDocumentFile(
if (!response.ok) throw response
return response.json()
}
+
+// ── Legal Domain Seed ─────────────────────────────────────────────────────────
+
+// Curated Agreement-first seed for the legal skin.
+// 1. Fetch up to 10 Agreement nodes.
+// 2. Expand each agreement's 1-hop neighbours; collect Orgs and Persons.
+// 3. Deduplicate by ref_id (Maps).
+// 4. Top-up to 10 Orgs / 10 Persons if needed.
+// 5. Filter edges to only those where both endpoints exist in the final set.
+export async function getLegalInitialNodes(signal?: AbortSignal): Promise {
+ const agreementsResp = await api.get(
+ "/v2/nodes/latest?node_type=Agreement&limit=10&skip_cache=1",
+ undefined,
+ signal
+ )
+ const agreements: GraphNode[] = agreementsResp.nodes ?? []
+
+ const orgs = new Map()
+ const persons = new Map()
+ const allEdges: GraphEdge[] = []
+
+ await Promise.all(
+ agreements.map(async (agreement) => {
+ try {
+ const expanded = await getNode(agreement.ref_id, "edges", signal)
+ for (const node of expanded.nodes ?? []) {
+ if (node.node_type === "Organization") {
+ if (!orgs.has(node.ref_id)) orgs.set(node.ref_id, node)
+ } else if (node.node_type === "Person") {
+ if (!persons.has(node.ref_id)) persons.set(node.ref_id, node)
+ }
+ }
+ allEdges.push(...(expanded.edges ?? []))
+ } catch (err) {
+ console.warn(`[getLegalInitialNodes] expand failed for ${agreement.ref_id}:`, err)
+ }
+ })
+ )
+
+ if (orgs.size < 10) {
+ try {
+ const topupResp = await api.get(
+ "/v2/nodes/latest?node_type=Organization&limit=10&skip_cache=1",
+ undefined,
+ signal
+ )
+ for (const node of topupResp.nodes ?? []) {
+ if (orgs.size >= 10) break
+ if (!orgs.has(node.ref_id)) orgs.set(node.ref_id, node)
+ }
+ } catch (err) {
+ console.warn("[getLegalInitialNodes] Organization top-up failed:", err)
+ }
+ }
+
+ if (persons.size < 10) {
+ try {
+ const topupResp = await api.get(
+ "/v2/nodes/latest?node_type=Person&limit=10&skip_cache=1",
+ undefined,
+ signal
+ )
+ for (const node of topupResp.nodes ?? []) {
+ if (persons.size >= 10) break
+ if (!persons.has(node.ref_id)) persons.set(node.ref_id, node)
+ }
+ } catch (err) {
+ console.warn("[getLegalInitialNodes] Person top-up failed:", err)
+ }
+ }
+
+ const finalNodes: GraphNode[] = [
+ ...agreements,
+ ...orgs.values(),
+ ...persons.values(),
+ ]
+ const finalRefIds = new Set(finalNodes.map((n) => n.ref_id))
+
+ const seenEdges = new Set()
+ const filteredEdges: GraphEdge[] = []
+ for (const edge of allEdges) {
+ if (!finalRefIds.has(edge.source) || !finalRefIds.has(edge.target)) continue
+ const key = edge.ref_id
+ ? edge.ref_id
+ : `${edge.source}__${edge.target}__${edge.edge_type}`
+ if (seenEdges.has(key)) continue
+ seenEdges.add(key)
+ filteredEdges.push(edge)
+ }
+
+ return { nodes: finalNodes, edges: filteredEdges }
+}
diff --git a/src/skins/legal/legal-case-files-feed.tsx b/src/skins/legal/legal-case-files-feed.tsx
index 3c69c9e..d9d61f8 100644
--- a/src/skins/legal/legal-case-files-feed.tsx
+++ b/src/skins/legal/legal-case-files-feed.tsx
@@ -3,9 +3,6 @@
import { useEffect, useMemo, useState } from "react"
import { useGraphStore } from "@/stores/graph-store"
import { useAppStore } from "@/stores/app-store"
-import { useSchemaStore } from "@/stores/schema-store"
-import { isMocksEnabled, MOCK_NODES, MOCK_EDGES } from "@/lib/mock-data"
-import { getLatestNodes } from "@/lib/graph-api"
import { cn } from "@/lib/utils"
import type { GraphNode } from "@/lib/graph-api"
@@ -88,8 +85,6 @@ export function LegalCaseFilesFeed() {
const setSelectedNode = useGraphStore((s) => s.setSelectedNode)
const setSidebarSelectedNode = useGraphStore((s) => s.setSidebarSelectedNode)
const clearSelection = useGraphStore((s) => s.clearSelection)
- const setGraphData = useGraphStore((s) => s.setGraphData)
- const setLoading = useGraphStore((s) => s.setLoading)
const searchTerm = useAppStore((s) => s.searchTerm)
const [activeType, setActiveType] = useState(null)
@@ -99,31 +94,6 @@ export function LegalCaseFilesFeed() {
setActiveType(null)
}, [searchTerm, clearSelection])
- useEffect(() => {
- if (useGraphStore.getState().nodes.length > 0) return
- if (isMocksEnabled()) {
- setGraphData(MOCK_NODES, MOCK_EDGES)
- return
- }
- let cancelled = false
- setLoading(true)
- ;(async () => {
- try {
- const result = await getLatestNodes()
- if (cancelled) return
- setGraphData(result.nodes ?? [], result.edges ?? [])
- } catch (err) {
- console.error("[legal-feed] getLatestNodes failed:", err)
- } finally {
- if (!cancelled) setLoading(false)
- }
- })()
- return () => {
- cancelled = true
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [])
-
const typeCounts = useMemo(() => {
const counts = new Map()
for (const n of nodes) {
diff --git a/src/skins/legal/legal-graph-pane.tsx b/src/skins/legal/legal-graph-pane.tsx
index 0e5e773..00dcb58 100644
--- a/src/skins/legal/legal-graph-pane.tsx
+++ b/src/skins/legal/legal-graph-pane.tsx
@@ -1,5 +1,6 @@
"use client"
+import { useEffect } from "react"
import { Network, Loader2 } from "lucide-react"
import { useGraphStore } from "@/stores/graph-store"
import { useAppStore } from "@/stores/app-store"
@@ -7,7 +8,8 @@ import { useSchemaStore } from "@/stores/schema-store"
import { GraphCanvas } from "@/components/universe/graph-canvas"
import { SearchBar } from "@/components/search/search-bar"
import { Toolkit, ToolkitFAB } from "@/components/layout/toolkit"
-import type { GraphNode } from "@/lib/graph-api"
+import { getLegalInitialNodes, type GraphNode } from "@/lib/graph-api"
+import { isMocksEnabled, MOCK_NODES, MOCK_EDGES } from "@/lib/mock-data"
// ── Legal Network Header ─────────────────────────────────────────────────────
@@ -59,6 +61,35 @@ export function LegalGraphPane() {
const clearSelection = useGraphStore((s) => s.clearSelection)
const loadingNeighbors = useGraphStore((s) => s.loadingNeighborRefs.size > 0)
const schemas = useSchemaStore((s) => s.schemas)
+ const setGraphData = useGraphStore((s) => s.setGraphData)
+ const setLoading = useGraphStore((s) => s.setLoading)
+
+ useEffect(() => {
+ if (useGraphStore.getState().nodes.length > 0) return
+ if (isMocksEnabled()) {
+ setGraphData(MOCK_NODES, MOCK_EDGES)
+ return
+ }
+ const controller = new AbortController()
+ let cancelled = false
+ setLoading(true)
+ ;(async () => {
+ try {
+ const result = await getLegalInitialNodes(controller.signal)
+ if (cancelled) return
+ setGraphData(result.nodes, result.edges)
+ } catch (err) {
+ console.error("[legal-graph-pane] getLegalInitialNodes failed:", err)
+ } finally {
+ if (!cancelled) setLoading(false)
+ }
+ })()
+ return () => {
+ cancelled = true
+ controller.abort()
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
const sourcesOpen = useAppStore((s) => s.sourcesOpen)
const myContentOpen = useAppStore((s) => s.myContentOpen)