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:
Sam Valladares 2026-03-03 14:04:31 -06:00
parent 816b577f69
commit 9bdcc69ce3
76 changed files with 5915 additions and 332 deletions

View file

@ -4,7 +4,7 @@
use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::response::{Html, Json};
use axum::response::{Json, Redirect};
use chrono::{Duration, Utc};
use serde::Deserialize;
use serde_json::Value;
@ -12,9 +12,9 @@ use serde_json::Value;
use super::events::VestigeEvent;
use super::state::AppState;
/// Serve the dashboard HTML
pub async fn serve_dashboard() -> Html<&'static str> {
Html(include_str!("../dashboard.html"))
/// Redirect root to the SvelteKit dashboard
pub async fn serve_dashboard() -> Redirect {
Redirect::permanent("/dashboard")
}
#[derive(Debug, Deserialize)]
@ -328,9 +328,9 @@ pub async fn health_check(
// MEMORY GRAPH
// ============================================================================
/// Serve the memory graph visualization HTML
pub async fn serve_graph() -> Html<&'static str> {
Html(include_str!("../graph.html"))
/// Redirect legacy graph to SvelteKit dashboard graph page
pub async fn serve_graph() -> Redirect {
Redirect::permanent("/dashboard/graph")
}
#[derive(Debug, Deserialize)]
@ -360,13 +360,21 @@ pub async fn get_graph(
.map(|n| n.id.clone())
.ok_or(StatusCode::NOT_FOUND)?
} else {
// Default: most recent memory
let recent = state.storage
.get_all_nodes(1, 0)
// Default: most connected memory (for a rich initial graph)
let most_connected = state.storage
.get_most_connected_memory()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
recent.first()
.map(|n| n.id.clone())
.ok_or(StatusCode::NOT_FOUND)?
if let Some(id) = most_connected {
id
} else {
// Fallback: most recent memory
let recent = state.storage
.get_all_nodes(1, 0)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
recent.first()
.map(|n| n.id.clone())
.ok_or(StatusCode::NOT_FOUND)?
}
};
// Get subgraph