diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/algorithm/page.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/algorithm/page.tsx index 6b0e1f0cf6..57d2b4ad92 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/algorithm/page.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/algorithm/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Workflow, ArrowRight, @@ -148,6 +148,10 @@ export default function AlgorithmPage() { const [docNote, setDocNote] = useState(""); const [regenerating, setRegenerating] = useState(false); + // The regenerate poll starts in a click handler, not an effect — hold its id + // so unmount can clear it (it otherwise runs for up to 15 minutes). + const pollRef = useRef | null>(null); + useEffect(() => () => { if (pollRef.current) clearInterval(pollRef.current); }, []); const load = useCallback(() => { fetch("/api/algorithm-tab") @@ -278,10 +282,12 @@ export default function AlgorithmPage() { setData(d); if (!d.generating || Date.now() - startedAt > 15 * 60_000) { clearInterval(poll); + pollRef.current = null; setRegenerating(false); } } catch { /* keep polling */ } }, 5000); + pollRef.current = poll; } catch (e: any) { setError(String(e?.message ?? e)); setRegenerating(false); diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/ledger/page.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/ledger/page.tsx index 0972000de6..5dd0b6c91f 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/ledger/page.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/ledger/page.tsx @@ -86,7 +86,7 @@ export default function LedgerPage() { const load = () => fetch("/api/ledger") .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))) - .then((j) => alive && setData(j)) + .then((j) => { if (alive) { setData(j); setError(null); } }) .catch((e) => alive && setError(String(e))); load(); const t = setInterval(load, 60_000); diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/memory/graph/page.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/memory/graph/page.tsx index c25cb41d05..974a8a9c79 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/app/memory/graph/page.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/app/memory/graph/page.tsx @@ -15,7 +15,8 @@ import { Network, Search, ArrowLeft, CornerDownRight, ExternalLink, Tag, X } fro interface MemNode { id: string; title: string; category: string; backlinkCount: number; silo: string; type: string; tags: string[]; pagerank: number } interface MemEdge { source: string; target: string; kind: string } interface Theme { tag: string; count: number } -interface MemGraph { nodes: MemNode[]; edges: MemEdge[]; themes: Theme[]; built: string | null; nodeCount?: number; edgeCount?: number } +// themes is optional: the no-graph fallback answers 200 with nodes/edges only. +interface MemGraph { nodes: MemNode[]; edges: MemEdge[]; themes?: Theme[]; built: string | null; nodeCount?: number; edgeCount?: number } // Every silo's nodes are wiki-indexed, so a focused node can open as a note. function noteUrl(node: MemNode): string | null { @@ -254,7 +255,7 @@ export default function MemoryGraphPage() { A theme is a tag running through your notes. Click one to see its members and how they connect.
- {data.themes.map((t) => ( + {(data.themes ?? []).map((t) => (