From bbe709aac50d3d75bb6fda1991bc5f1162a08b9c Mon Sep 17 00:00:00 2001 From: elhoim Date: Mon, 3 Aug 2026 07:01:01 +0000 Subject: [PATCH 1/5] fix(observability): stop the Memory Graph route crashing on fresh installs The no-graph fallback returns HTTP 200 with communities and no themes, and the only guard was !data, which a truthy themeless object passes. --- .../LIFEOS/PULSE/Observability/src/app/memory/graph/page.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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) => (
From 524028cdfcd72afee64bfc7a4c4fa2acac4c962d Mon Sep 17 00:00:00 2001 From: elhoim Date: Mon, 3 Aug 2026 07:01:02 +0000 Subject: [PATCH 5/5] fix(observability): abort in-flight CapabilityStrip fetches on window switch Without an AbortController the slower 24h response overwrote the newer 1h one. --- .../src/components/activity/CapabilityStrip.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/LifeOS/install/LIFEOS/PULSE/Observability/src/components/activity/CapabilityStrip.tsx b/LifeOS/install/LIFEOS/PULSE/Observability/src/components/activity/CapabilityStrip.tsx index f81fca9ff5..1995efe9c9 100644 --- a/LifeOS/install/LIFEOS/PULSE/Observability/src/components/activity/CapabilityStrip.tsx +++ b/LifeOS/install/LIFEOS/PULSE/Observability/src/components/activity/CapabilityStrip.tsx @@ -150,21 +150,25 @@ export default function CapabilityStrip() { const [data, setData] = useState(null); const [windowMin, setWindowMin] = useState(60); - const fetchData = useCallback(async () => { + const fetchData = useCallback(async (signal?: AbortSignal) => { try { const d = await localOnlyApiCall( `/api/capabilities?window=${windowMin}`, + { signal }, ); - setData(d); + if (!signal?.aborted) setData(d); } catch { // strip stays hidden until data arrives } }, [windowMin]); useEffect(() => { - fetchData(); - const interval = setInterval(fetchData, 10000); - return () => clearInterval(interval); + // Aborted on window switch, so a slower response for the old window can + // never land on top of the newer one. + const ac = new AbortController(); + fetchData(ac.signal); + const interval = setInterval(() => fetchData(ac.signal), 10000); + return () => { ac.abort(); clearInterval(interval); }; }, [fetchData]); const { active, quiet } = useMemo(() => {