diff --git a/apps/dashboard/src/lib/components/DuplicateCluster.svelte b/apps/dashboard/src/lib/components/DuplicateCluster.svelte index f414ec5..98802f2 100644 --- a/apps/dashboard/src/lib/components/DuplicateCluster.svelte +++ b/apps/dashboard/src/lib/components/DuplicateCluster.svelte @@ -2,7 +2,9 @@ DuplicateCluster — renders a single cosine-similarity cluster from the `find_duplicates` MCP tool. Shows similarity bar (color-coded by severity), stacked memory cards with type/retention/tags/date, and action controls - (Merge all → highest-retention winner, Review → expand, Dismiss → hide). + (Review → expand, Dismiss → hide). The "Merge all → winner" button renders + ONLY when the host wires `onMerge` to a real merge action — today the merge + itself lives in the MCP `dedup` tool, so the dashboard page omits it. Pure helpers live in `./duplicates-helpers.ts` and are unit-tested there. Keep this file focused on rendering + glue. @@ -160,17 +162,20 @@ {/each} - +
- + {#if onMerge} + + {/if}
{/if} diff --git a/apps/dashboard/src/lib/observatory/ObservatoryStage.svelte b/apps/dashboard/src/lib/observatory/ObservatoryStage.svelte index 0c1fda4..bbe556c 100644 --- a/apps/dashboard/src/lib/observatory/ObservatoryStage.svelte +++ b/apps/dashboard/src/lib/observatory/ObservatoryStage.svelte @@ -20,7 +20,7 @@ import RescueVerdict from '$lib/observatory/overlays/RescueVerdict.svelte'; import ObservatoryCanvas from '$lib/components/ObservatoryCanvas.svelte'; import { DEMO_MODES, type DemoMode } from '$lib/observatory/types'; - import type { ObservatoryEngine } from '$lib/observatory/engine'; + import type { EngineStatus, ObservatoryEngine } from '$lib/observatory/engine'; import { NodeRenderer } from '$lib/observatory/node-renderer'; import { BirthRenderer } from '$lib/observatory/birth-renderer'; import { RescueRenderer } from '$lib/observatory/rescue-renderer'; @@ -62,6 +62,14 @@ * back with its memory id (the host opens its inspector panel). */ onpick?: (memoryId: string) => void; + /** + * Fired when the WebGPU engine cannot run — `'gpu' in navigator` was + * true but requestAdapter() returned null, requestDevice() threw, or + * the device was lost (Linux/VM/blocklisted GPU). Hosts with a + * non-WebGPU renderer switch to it instead of stranding the user on + * the offline panel. + */ + onfallback?: (reason: string) => void; } let { @@ -74,7 +82,8 @@ onexit, embedded = false, chrome = 'full', - onpick + onpick, + onfallback }: Props = $props(); // GPU picking — screen px → NDC → NodeRenderer.pickAt (one readback/click). @@ -169,6 +178,13 @@ renderer = new NodeRenderer(e); } + // Adapter/device boot failure or a lost device — let the host swap to a + // renderer that works here (the graph page flips to the classic Three.js + // inspector) instead of leaving a dead field on screen. + function handleStatus(s: EngineStatus) { + if (s.state === 'unsupported' || s.state === 'error') onfallback?.(s.reason); + } + // Upload the memory field once the engine AND the graph are both ready. $effect(() => { if (engine && renderer && graphData && !uploaded) { @@ -299,6 +315,7 @@ {freezeFrame} onframe={handleFrame} onready={handleReady} + onstatus={handleStatus} /> diff --git a/apps/dashboard/src/routes/(app)/contradictions/+page.svelte b/apps/dashboard/src/routes/(app)/contradictions/+page.svelte index 5373045..4569c47 100644 --- a/apps/dashboard/src/routes/(app)/contradictions/+page.svelte +++ b/apps/dashboard/src/routes/(app)/contradictions/+page.svelte @@ -11,7 +11,6 @@ severityColor, severityLabel, truncate, - uniqueMemoryCount, avgTrustDelta as avgTrustDeltaFn, } from '$components/contradiction-helpers'; @@ -22,6 +21,10 @@ // System-wide count from the backend, vs. the derived stats below which // reflect only the pairs the page holds. let totalDetected = $state(0); + // How many memories the backend actually scanned — it only analyzes the + // most recent `limit` memories, so the stat copy must say so instead of + // implying a whole-corpus sweep. + let memoriesAnalyzed = $state(0); let loading = $state(true); let error = $state(null); @@ -32,10 +35,12 @@ const res = await api.contradictions(); contradictions = res.contradictions; totalDetected = res.total; + memoriesAnalyzed = res.memoriesAnalyzed; } catch (e) { error = e instanceof Error ? e.message : 'Failed to load contradictions'; contradictions = []; totalDetected = 0; + memoriesAnalyzed = 0; } finally { loading = false; } @@ -114,10 +119,10 @@ focusedPairIndex = i; } - // --- Stats. `totalDetected` is the backend's system-wide count; everything - // else is derived from the pairs the page actually holds so the numbers are + // --- Stats. `totalDetected` + `memoriesAnalyzed` come from the backend + // (which scans only the most recent `limit` memories); everything else is + // derived from the pairs the page actually holds so the numbers are // self-consistent with what the user sees. --- - const totalMemoriesInvolved = $derived(uniqueMemoryCount(contradictions)); const avgTrustDelta = $derived(avgTrustDeltaFn(contradictions)); // Map filtered index -> original index in `contradictions` so the @@ -194,7 +199,7 @@
- contradictions across {totalMemoriesInvolved.toLocaleString()} memories + contradictions among the {memoriesAnalyzed.toLocaleString()} most recent memories
diff --git a/apps/dashboard/src/routes/(app)/duplicates/+page.svelte b/apps/dashboard/src/routes/(app)/duplicates/+page.svelte index 05f6d98..b1eb331 100644 --- a/apps/dashboard/src/routes/(app)/duplicates/+page.svelte +++ b/apps/dashboard/src/routes/(app)/duplicates/+page.svelte @@ -2,8 +2,10 @@ Memory Hygiene — Duplicate Detection Dashboard exposure of the `find_duplicates` MCP tool. Threshold slider (0.70-0.95) reruns cosine-similarity clustering. Each cluster renders as a - DuplicateCluster with similarity bar, stacked memory cards, and merge / - review / dismiss actions. + DuplicateCluster with similarity bar, stacked memory cards, and review / + dismiss actions. This page is inspection-only: the actual merge lives in + the MCP `dedup` tool, so no onMerge is wired here (the merge button only + renders when a host provides a real merge action). -->