mirror of
https://github.com/samvallad33/vestige.git
synced 2026-07-04 22:02:14 +02:00
feat: live memory materialization — nodes spawn in 3D graph in real-time
When memories are created, promoted, deleted, or dreamed via MCP tools, the 3D graph now shows spectacular live animations: - Rainbow particle burst + elastic scale-up on MemoryCreated - Ripple wave cascading to nearby nodes - Green pulse + node growth on MemoryPromoted - Implosion + dissolution on MemoryDeleted - Edge growth animation on ConnectionDiscovered - Purple cascade on DreamStarted/DreamProgress/DreamCompleted - FIFO eviction at 50 live nodes to guard performance Also: graph center defaults to most-connected node, legacy HTML redirects to SvelteKit dashboard, CSS height chain fix in layout. Testing: 150 unit tests (vitest), 11 e2e tests (Playwright with MCP Streamable HTTP client), 22 proof screenshots. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
816b577f69
commit
9bdcc69ce3
76 changed files with 5915 additions and 332 deletions
|
|
@ -6,6 +6,7 @@
|
|||
import { api } from '$stores/api';
|
||||
import { eventFeed } from '$stores/websocket';
|
||||
import type { GraphResponse, GraphNode, GraphEdge, Memory } from '$types';
|
||||
import type { GraphMutation } from '$lib/graph/events';
|
||||
import { filterByDate } from '$lib/graph/temporal';
|
||||
|
||||
let graphData: GraphResponse | null = $state(null);
|
||||
|
|
@ -18,6 +19,10 @@
|
|||
let temporalEnabled = $state(false);
|
||||
let temporalDate = $state(new Date());
|
||||
|
||||
// Live counts that update on mutations
|
||||
let liveNodeCount = $state(0);
|
||||
let liveEdgeCount = $state(0);
|
||||
|
||||
// Filtered graph data based on temporal mode
|
||||
let displayNodes = $derived.by((): GraphNode[] => {
|
||||
if (!graphData) return [];
|
||||
|
|
@ -31,6 +36,40 @@
|
|||
return filterByDate(graphData.nodes, graphData.edges, temporalDate).visibleEdges;
|
||||
});
|
||||
|
||||
function handleGraphMutation(mutation: GraphMutation) {
|
||||
if (!graphData) return;
|
||||
|
||||
switch (mutation.type) {
|
||||
case 'nodeAdded':
|
||||
graphData.nodes = [...graphData.nodes, mutation.node];
|
||||
graphData.nodeCount = graphData.nodes.length;
|
||||
liveNodeCount = graphData.nodeCount;
|
||||
break;
|
||||
case 'nodeRemoved':
|
||||
graphData.nodes = graphData.nodes.filter((n) => n.id !== mutation.nodeId);
|
||||
graphData.nodeCount = graphData.nodes.length;
|
||||
liveNodeCount = graphData.nodeCount;
|
||||
break;
|
||||
case 'edgeAdded':
|
||||
graphData.edges = [...graphData.edges, mutation.edge];
|
||||
graphData.edgeCount = graphData.edges.length;
|
||||
liveEdgeCount = graphData.edgeCount;
|
||||
break;
|
||||
case 'edgesRemoved':
|
||||
graphData.edges = graphData.edges.filter(
|
||||
(e) => e.source !== mutation.nodeId && e.target !== mutation.nodeId
|
||||
);
|
||||
graphData.edgeCount = graphData.edges.length;
|
||||
liveEdgeCount = graphData.edgeCount;
|
||||
break;
|
||||
case 'nodeUpdated': {
|
||||
const node = graphData.nodes.find((n) => n.id === mutation.nodeId);
|
||||
if (node) node.retention = mutation.retention;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => loadGraph());
|
||||
|
||||
async function loadGraph(query?: string, centerId?: string) {
|
||||
|
|
@ -43,6 +82,10 @@
|
|||
query: query || undefined,
|
||||
center_id: centerId || undefined
|
||||
});
|
||||
if (graphData) {
|
||||
liveNodeCount = graphData.nodeCount;
|
||||
liveEdgeCount = graphData.edgeCount;
|
||||
}
|
||||
} catch {
|
||||
error = 'No memories yet. Start using Vestige to populate your graph.';
|
||||
} finally {
|
||||
|
|
@ -96,6 +139,7 @@
|
|||
events={$eventFeed}
|
||||
{isDreaming}
|
||||
onSelect={onNodeSelect}
|
||||
onGraphMutation={handleGraphMutation}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
|
@ -149,9 +193,9 @@
|
|||
<!-- Bottom stats -->
|
||||
<div class="absolute bottom-4 left-4 z-10 text-xs text-dim glass rounded-xl px-3 py-2">
|
||||
{#if graphData}
|
||||
<span>{displayNodes.length} nodes</span>
|
||||
<span>{liveNodeCount} nodes</span>
|
||||
<span class="mx-2 text-subtle">·</span>
|
||||
<span>{displayEdges.length} edges</span>
|
||||
<span>{liveEdgeCount} edges</span>
|
||||
<span class="mx-2 text-subtle">·</span>
|
||||
<span>depth {graphData.depth}</span>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -148,8 +148,8 @@
|
|||
</nav>
|
||||
|
||||
<!-- Main content -->
|
||||
<main class="flex-1 overflow-y-auto pb-16 md:pb-0">
|
||||
<div class="animate-page-in">
|
||||
<main class="flex-1 flex flex-col min-h-0 pb-16 md:pb-0">
|
||||
<div class="animate-page-in flex-1 min-h-0 overflow-y-auto">
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue