2026-03-01 21:24:10 -06:00
|
|
|
|
import * as THREE from 'three';
|
|
|
|
|
|
import type { GraphNode } from '$types';
|
|
|
|
|
|
import { NODE_TYPE_COLORS } from '$types';
|
|
|
|
|
|
|
feat(graph): FSRS memory-state colour mode + legend overlay
Closes Agent 1's audit gap #4: FSRS memory state (Active / Dormant /
Silent / Unavailable) was computed server-side per query but never
rendered in the 3D graph. Spheres always tinted by node type.
The new colour mode adds a second channel that users can toggle
between at runtime — Type (default, existing behaviour) and State
(new). The toggle is a radio-pair pill in the graph page's top-right
control bar next to the node-count selector + Dream button.
Buckets + palette:
- Active ≥ 70% emerald #10b981 easily retrievable
- Dormant 40-70% amber #f59e0b retrievable with effort
- Silent 10-40% violet #8b5cf6 difficult, needs cues
- Unavail. < 10% slate #6b7280 needs reinforcement
Thresholds match `execute_system_status` at the backend so the graph
colour bands line up exactly with what the Stats page reports in its
stateDistribution block. Using retention as the proxy for the full
accessibility formula (retention × 0.5 + retrieval × 0.3 + storage ×
0.2) is an approximation — retention is the dominant 0.5 weight and
it is the only FSRS channel the current GraphNode DTO carries. Swap
to the full formula in a future release if the DTO grows.
Implementation:
- `apps/dashboard/src/lib/graph/nodes.ts` — new `MemoryState` type,
`getMemoryState(retention)`, `MEMORY_STATE_COLORS`,
`MEMORY_STATE_DESCRIPTIONS`, `ColorMode`, `getNodeColor(node, mode)`.
- `NodeManager.colorMode` field (default `'type'`). `createNodeMeshes`
now calls `getNodeColor(node, this.colorMode)` so newly-added nodes
during the session follow the toggled mode.
- New `NodeManager.setColorMode(mode)` mutates every live mesh's
material + glow sprite colour in place. Idempotent; cheap. Does NOT
touch opacity/emissive-intensity so the v2.0.5 suppression dimming
layer keeps working unchanged.
- New `MemoryStateLegend.svelte` floating overlay in the bottom-right
when state mode is active (hidden in type mode so the legend doesn't
compete with the node-type palette).
- `Graph3D.svelte` accepts a new `colorMode` prop (default `'type'`)
and runs a `$effect` that calls `setColorMode` on every toggle.
- Dashboard rebuild picks up the new component + wiring.
Tests: 171 vitest, svelte-check 581 files / 0 errors. No backend
changes; this is pure dashboard code.
2026-04-19 20:45:08 -05:00
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// v2.0.8: Memory state coloring (FSRS accessibility bucket)
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
//
|
|
|
|
|
|
// Every knowledge_node has an FSRS accessibility score computed from
|
|
|
|
|
|
// (retention × 0.5 + retrieval × 0.3 + storage × 0.2). That score gates which
|
|
|
|
|
|
// memories surface in search and drives the Active / Dormant / Silent /
|
|
|
|
|
|
// Unavailable lifecycle documented by Bjork & Bjork 1992 dual-strength model.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The backend computes all three channels, but `GraphNode` only carries
|
|
|
|
|
|
// `retention` — which is already the dominant weight (0.5 of 1.0). Using
|
|
|
|
|
|
// retention alone as a proxy is a known approximation; the buckets line up
|
|
|
|
|
|
// with the same thresholds `execute_system_status` uses server-side, so the
|
|
|
|
|
|
// visual labelling matches what `/api/stats` reports in its
|
|
|
|
|
|
// `stateDistribution` block.
|
|
|
|
|
|
|
|
|
|
|
|
export type MemoryState = 'active' | 'dormant' | 'silent' | 'unavailable';
|
|
|
|
|
|
|
|
|
|
|
|
/// Map an FSRS retention score to its accessibility bucket.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Thresholds match `execute_system_status` at the backend so the 3D graph's
|
|
|
|
|
|
/// colours line up with the numbers reported by `/api/stats`.
|
|
|
|
|
|
export function getMemoryState(retention: number): MemoryState {
|
|
|
|
|
|
if (retention >= 0.7) return 'active';
|
|
|
|
|
|
if (retention >= 0.4) return 'dormant';
|
|
|
|
|
|
if (retention >= 0.1) return 'silent';
|
|
|
|
|
|
return 'unavailable';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// FSRS state palette. Distinct from NODE_TYPE_COLORS so the two modes can
|
|
|
|
|
|
/// coexist in the UI without overloading a single colour channel.
|
|
|
|
|
|
export const MEMORY_STATE_COLORS: Record<MemoryState, string> = {
|
|
|
|
|
|
active: '#10b981', // emerald — easily retrievable
|
|
|
|
|
|
dormant: '#f59e0b', // amber — retrievable with effort
|
|
|
|
|
|
silent: '#8b5cf6', // violet — difficult, needs cues
|
|
|
|
|
|
unavailable: '#6b7280', // slate — needs reinforcement
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const MEMORY_STATE_DESCRIPTIONS: Record<MemoryState, string> = {
|
|
|
|
|
|
active: 'Easily retrievable (retention ≥ 70%)',
|
|
|
|
|
|
dormant: 'Retrievable with effort (40–70%)',
|
|
|
|
|
|
silent: 'Difficult, needs cues (10–40%)',
|
|
|
|
|
|
unavailable: 'Needs reinforcement (< 10%)',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-26 14:36:38 -05:00
|
|
|
|
export type AhaGraphKind = 'aha' | 'confusion' | 'failure';
|
|
|
|
|
|
|
|
|
|
|
|
export const AHAGRAPH_COLORS: Record<AhaGraphKind, string> = {
|
|
|
|
|
|
aha: '#FFD700',
|
|
|
|
|
|
confusion: '#EF4444',
|
|
|
|
|
|
failure: '#9CA3AF',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export const AHAGRAPH_DESCRIPTIONS: Record<AhaGraphKind, string> = {
|
|
|
|
|
|
aha: 'Aha moments and breakthroughs',
|
|
|
|
|
|
confusion: 'Confusions and weak spots',
|
|
|
|
|
|
failure: 'Failures and guardrails',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// Color mode controls whether node spheres are tinted by node type,
|
|
|
|
|
|
/// FSRS memory state, or AhaGraph learning tags.
|
feat(graph): FSRS memory-state colour mode + legend overlay
Closes Agent 1's audit gap #4: FSRS memory state (Active / Dormant /
Silent / Unavailable) was computed server-side per query but never
rendered in the 3D graph. Spheres always tinted by node type.
The new colour mode adds a second channel that users can toggle
between at runtime — Type (default, existing behaviour) and State
(new). The toggle is a radio-pair pill in the graph page's top-right
control bar next to the node-count selector + Dream button.
Buckets + palette:
- Active ≥ 70% emerald #10b981 easily retrievable
- Dormant 40-70% amber #f59e0b retrievable with effort
- Silent 10-40% violet #8b5cf6 difficult, needs cues
- Unavail. < 10% slate #6b7280 needs reinforcement
Thresholds match `execute_system_status` at the backend so the graph
colour bands line up exactly with what the Stats page reports in its
stateDistribution block. Using retention as the proxy for the full
accessibility formula (retention × 0.5 + retrieval × 0.3 + storage ×
0.2) is an approximation — retention is the dominant 0.5 weight and
it is the only FSRS channel the current GraphNode DTO carries. Swap
to the full formula in a future release if the DTO grows.
Implementation:
- `apps/dashboard/src/lib/graph/nodes.ts` — new `MemoryState` type,
`getMemoryState(retention)`, `MEMORY_STATE_COLORS`,
`MEMORY_STATE_DESCRIPTIONS`, `ColorMode`, `getNodeColor(node, mode)`.
- `NodeManager.colorMode` field (default `'type'`). `createNodeMeshes`
now calls `getNodeColor(node, this.colorMode)` so newly-added nodes
during the session follow the toggled mode.
- New `NodeManager.setColorMode(mode)` mutates every live mesh's
material + glow sprite colour in place. Idempotent; cheap. Does NOT
touch opacity/emissive-intensity so the v2.0.5 suppression dimming
layer keeps working unchanged.
- New `MemoryStateLegend.svelte` floating overlay in the bottom-right
when state mode is active (hidden in type mode so the legend doesn't
compete with the node-type palette).
- `Graph3D.svelte` accepts a new `colorMode` prop (default `'type'`)
and runs a `$effect` that calls `setColorMode` on every toggle.
- Dashboard rebuild picks up the new component + wiring.
Tests: 171 vitest, svelte-check 581 files / 0 errors. No backend
changes; this is pure dashboard code.
2026-04-19 20:45:08 -05:00
|
|
|
|
/// Type mode is the long-standing default; state mode is the v2.0.8 addition.
|
2026-04-26 14:36:38 -05:00
|
|
|
|
export type ColorMode = 'type' | 'state' | 'ahagraph';
|
feat(graph): FSRS memory-state colour mode + legend overlay
Closes Agent 1's audit gap #4: FSRS memory state (Active / Dormant /
Silent / Unavailable) was computed server-side per query but never
rendered in the 3D graph. Spheres always tinted by node type.
The new colour mode adds a second channel that users can toggle
between at runtime — Type (default, existing behaviour) and State
(new). The toggle is a radio-pair pill in the graph page's top-right
control bar next to the node-count selector + Dream button.
Buckets + palette:
- Active ≥ 70% emerald #10b981 easily retrievable
- Dormant 40-70% amber #f59e0b retrievable with effort
- Silent 10-40% violet #8b5cf6 difficult, needs cues
- Unavail. < 10% slate #6b7280 needs reinforcement
Thresholds match `execute_system_status` at the backend so the graph
colour bands line up exactly with what the Stats page reports in its
stateDistribution block. Using retention as the proxy for the full
accessibility formula (retention × 0.5 + retrieval × 0.3 + storage ×
0.2) is an approximation — retention is the dominant 0.5 weight and
it is the only FSRS channel the current GraphNode DTO carries. Swap
to the full formula in a future release if the DTO grows.
Implementation:
- `apps/dashboard/src/lib/graph/nodes.ts` — new `MemoryState` type,
`getMemoryState(retention)`, `MEMORY_STATE_COLORS`,
`MEMORY_STATE_DESCRIPTIONS`, `ColorMode`, `getNodeColor(node, mode)`.
- `NodeManager.colorMode` field (default `'type'`). `createNodeMeshes`
now calls `getNodeColor(node, this.colorMode)` so newly-added nodes
during the session follow the toggled mode.
- New `NodeManager.setColorMode(mode)` mutates every live mesh's
material + glow sprite colour in place. Idempotent; cheap. Does NOT
touch opacity/emissive-intensity so the v2.0.5 suppression dimming
layer keeps working unchanged.
- New `MemoryStateLegend.svelte` floating overlay in the bottom-right
when state mode is active (hidden in type mode so the legend doesn't
compete with the node-type palette).
- `Graph3D.svelte` accepts a new `colorMode` prop (default `'type'`)
and runs a `$effect` that calls `setColorMode` on every toggle.
- Dashboard rebuild picks up the new component + wiring.
Tests: 171 vitest, svelte-check 581 files / 0 errors. No backend
changes; this is pure dashboard code.
2026-04-19 20:45:08 -05:00
|
|
|
|
|
|
|
|
|
|
/// Pick a hex colour for a node given the active colour mode.
|
|
|
|
|
|
/// Falls back to the grey `unavailable` tone if the node's type is unknown.
|
|
|
|
|
|
export function getNodeColor(node: GraphNode, mode: ColorMode): string {
|
|
|
|
|
|
if (mode === 'state') {
|
|
|
|
|
|
return MEMORY_STATE_COLORS[getMemoryState(node.retention)];
|
|
|
|
|
|
}
|
2026-04-26 14:36:38 -05:00
|
|
|
|
if (mode === 'ahagraph') {
|
|
|
|
|
|
return getAhaGraphColor(node) ?? NODE_TYPE_COLORS[node.type] ?? '#8B95A5';
|
|
|
|
|
|
}
|
feat(graph): FSRS memory-state colour mode + legend overlay
Closes Agent 1's audit gap #4: FSRS memory state (Active / Dormant /
Silent / Unavailable) was computed server-side per query but never
rendered in the 3D graph. Spheres always tinted by node type.
The new colour mode adds a second channel that users can toggle
between at runtime — Type (default, existing behaviour) and State
(new). The toggle is a radio-pair pill in the graph page's top-right
control bar next to the node-count selector + Dream button.
Buckets + palette:
- Active ≥ 70% emerald #10b981 easily retrievable
- Dormant 40-70% amber #f59e0b retrievable with effort
- Silent 10-40% violet #8b5cf6 difficult, needs cues
- Unavail. < 10% slate #6b7280 needs reinforcement
Thresholds match `execute_system_status` at the backend so the graph
colour bands line up exactly with what the Stats page reports in its
stateDistribution block. Using retention as the proxy for the full
accessibility formula (retention × 0.5 + retrieval × 0.3 + storage ×
0.2) is an approximation — retention is the dominant 0.5 weight and
it is the only FSRS channel the current GraphNode DTO carries. Swap
to the full formula in a future release if the DTO grows.
Implementation:
- `apps/dashboard/src/lib/graph/nodes.ts` — new `MemoryState` type,
`getMemoryState(retention)`, `MEMORY_STATE_COLORS`,
`MEMORY_STATE_DESCRIPTIONS`, `ColorMode`, `getNodeColor(node, mode)`.
- `NodeManager.colorMode` field (default `'type'`). `createNodeMeshes`
now calls `getNodeColor(node, this.colorMode)` so newly-added nodes
during the session follow the toggled mode.
- New `NodeManager.setColorMode(mode)` mutates every live mesh's
material + glow sprite colour in place. Idempotent; cheap. Does NOT
touch opacity/emissive-intensity so the v2.0.5 suppression dimming
layer keeps working unchanged.
- New `MemoryStateLegend.svelte` floating overlay in the bottom-right
when state mode is active (hidden in type mode so the legend doesn't
compete with the node-type palette).
- `Graph3D.svelte` accepts a new `colorMode` prop (default `'type'`)
and runs a `$effect` that calls `setColorMode` on every toggle.
- Dashboard rebuild picks up the new component + wiring.
Tests: 171 vitest, svelte-check 581 files / 0 errors. No backend
changes; this is pure dashboard code.
2026-04-19 20:45:08 -05:00
|
|
|
|
return NODE_TYPE_COLORS[node.type] || '#8B95A5';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-26 14:36:38 -05:00
|
|
|
|
export function getAhaGraphColor(node: Pick<GraphNode, 'tags'>): string | null {
|
|
|
|
|
|
const tags = new Set((node.tags ?? []).map((tag) => tag.toLowerCase()));
|
|
|
|
|
|
if (tags.has('aha')) return AHAGRAPH_COLORS.aha;
|
|
|
|
|
|
if (tags.has('confusion') || tags.has('weak-spot')) return AHAGRAPH_COLORS.confusion;
|
|
|
|
|
|
if (tags.has('failure') || tags.has('guardrail')) return AHAGRAPH_COLORS.failure;
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
// Shared radial-gradient texture used for every node's glow Sprite.
|
|
|
|
|
|
// Without a map, THREE.Sprite renders as a flat coloured plane — additive-
|
|
|
|
|
|
// blending + UnrealBloomPass then amplifies its square edges into the
|
|
|
|
|
|
// hard-edged "glowing cubes" artefact reported in issue #31. Using a
|
|
|
|
|
|
// soft radial gradient gives a real round halo and lets bloom do its job.
|
|
|
|
|
|
let sharedGlowTexture: THREE.Texture | null = null;
|
v2.0.8 Pulse — Reasoning Theater + Pulse Toast + Terrarium + Intel Mac (#44)
* docs: anti-hedging banner + master state-and-plan + UI roadmap
Project-level CLAUDE.md line 1 is now the anti-hedging banner referencing
the global rule at /Users/entity002/CLAUDE.md. When Sam commits to an
execution path (content cadence, shipping velocity, outreach, launch),
forecasts compute as if the commitment is already met — no discounting by
execution probability.
VESTIGE_STATE_AND_PLAN.md (1135 lines, 14 parts): single-page reference
any AI agent can read to plan the next phase without re-reading the repo.
Covers current state of every crate, the 24 MCP tools, the 19 WebSocket
event types, the 8 IDE integrations, and v2.1 through v3.0 roadmap.
UI_ROADMAP_v2.1_v2.2.md: compiled UI-gap analysis from 4 parallel research
agents (April 19). 10 critical UI gaps + 2026 bleeding-edge patterns +
unclaimed territory (menu bar widget, voice-first, AR/VR, time-scrubber).
* feat(v2.2-pulse): InsightToast + multi-process STORAGE docs
Two independent ship items landing together on the v2.2 branch ahead of
the Tuesday launch — a new UI surface that makes Vestige's cognitive
events visible in real time, and honest documentation of the multi-process
safety story that underpins the Stigmergic Swarm narrative.
**InsightToast** (apps/dashboard/src/lib/components/InsightToast.svelte,
apps/dashboard/src/lib/stores/toast.ts):
The dashboard already had a working WebSocket event stream on
ws://localhost:3927/ws that broadcast every cognitive event (dream
completions, consolidation sweeps, memory promotions/demotions, active-
forgetting suppression and Rac1 cascades, bridge discoveries). None of
that was surfaced to a user looking at anything other than the raw feed
view. InsightToast subscribes to the existing eventFeed derived store,
filters the spammy lifecycle events (Heartbeat, SearchPerformed,
RetentionDecayed, ActivationSpread, ImportanceScored, MemoryCreated),
and translates the narrative events into ephemeral toasts with a
bioluminescent colored accent matching EVENT_TYPE_COLORS.
Design notes:
- Rate-limited ConnectionDiscovered at 1.5s intervals (dreams emit many).
- Max 4 visible toasts, auto-dismiss at 4.5-7s depending on event weight.
- Click or Enter/Space to dismiss early.
- Bottom-right on desktop, top-banner on mobile.
- Reduced-motion honored via prefers-reduced-motion.
- Zero new websocket subscriptions — everything piggybacks on the
existing derived store.
Also added a "Preview Pulse" button to Settings -> Cognitive Operations
that fires a synthetic sequence of four toasts (DreamCompleted,
ConnectionDiscovered, MemorySuppressed, ConsolidationCompleted) so
the animation is demoable without waiting for real cognitive activity.
**Multi-Process Safety section in docs/STORAGE.md**:
Grounds the Stigmergic Swarm story with concrete tables of what the
current WAL + 5s busy_timeout configuration actually supports vs what
remains experimental. Key honest points:
- Shared --data-dir + ONE vestige-mcp + N clients is the shipping
pattern for multi-agent coordination.
- Two vestige-mcp processes writing the same file is experimental —
documented with the lsof + pkill recovery path.
- Roadmap lists the three items that would promote it to "supported":
advisory file lock, retry-with-jitter on SQLITE_BUSY, and a
concurrent-writer load test.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm run build: clean static build, adapter-static succeeds
* feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
* fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
* test(v2.3): full e2e + integration coverage for Pulse + Birth Ritual
Post-ship verification pass — five parallel write-agents produced 229 new
tests across vitest units, vitest integration, and Playwright browser e2e.
Net suite: 361 vitest pass (up from 251, +110) and 9/9 Playwright pass on
back-to-back runs.
**toast.test.ts (NEW, 661 lines, 42 tests)**
Silent-lobotomy batch walk proven (multi-event tick processes ALL, not
just newest, oldest-first ordering preserved). Hover-panic pause/resume
with remaining-ms math. All 9 event type translations asserted, all 11
noise types asserted silent. ConnectionDiscovered 1500ms throttle.
MAX_VISIBLE=4 eviction. clear() tears down all timers. fireDemoSequence
staggers 4 toasts at 800ms intervals. vi.useFakeTimers + vi.mock of
eventFeed; vi.resetModules in beforeEach for module-singleton isolation.
**websocket.test.ts (NEW, 247 lines, 30 tests)**
injectEvent adds to front, respects MAX_EVENTS=200 with FIFO eviction,
triggers eventFeed emissions. All 6 derived stores (isConnected,
heartbeat, memoryCount, avgRetention, suppressedCount, uptimeSeconds)
verified — defaults, post-heartbeat values, clearEvents preserves
lastHeartbeat. 13 formatUptime boundary cases (0/59/60/3599/3600/
86399/86400 seconds + negative / NaN / ±Infinity).
**effects.test.ts (EXTENDED, +501 lines, +21 tests, 51 total)**
createBirthOrb full lifecycle — sprite count (halo + core), cosmic
center via camera.quaternion, gestation phase (position lock, opacity
rise, scale easing, color tint), flight Bezier arc above linear
midpoint at t=0.5, dynamic mid-flight target redirect. onArrive fires
exactly once at frame 139. Post-arrival fade + disposal cleans scene
children. Sanhedrin Shatter: target goes undefined mid-flight →
onArrive NEVER called, implosion spawned, halo blood-red, eventual
cleanup. dispose() cleans active orbs. Multiple simultaneous orbs.
Custom gestation/flight frame opts honored. Zero-alloc invariant
smoke test (6 orbs × 150 frames, no leaks).
**nodes.test.ts (EXTENDED, +197 lines, +10 tests, 42 total)**
addNode({isBirthRitual:true}) hides mesh/glow/label immediately,
stamps birthRitualPending sentinel with correct totalFrames +
targetScale, does NOT enqueue materialization. igniteNode flips
visibility + enqueues materialization. Idempotent — second call
no-op. Non-ritual nodes unaffected. Unknown id is safe no-op.
Position stored in positions map while invisible (force sim still
sees it). removeNode + late igniteNode is safe.
**events.test.ts (EXTENDED, +268 lines, +7 tests, 55 total)**
MemoryCreated → mesh hidden immediately, 2 birth-orb sprites added,
ZERO RingGeometry meshes and ZERO Points particles at spawn. Full
ritual drive → onArrive fires, node visible + materializing, sentinel
cleared. Newton's Cradle: target mesh scale exactly 0.001 * 1.8 right
after arrival. Dual shockwave: exactly 2 Ring meshes added. Re-read
live position on arrival — force-sim motion during ritual → burst
lands at the NEW position. Sanhedrin abort path → rainbow burst,
shockwave, ripple wave are NEVER called (vi.spyOn).
**three-mock.ts (EXTENDED)**
Added Color.setRGB — production Three.js has it, the Sanhedrin-
Shatter path in effects.ts uses it. Two write-agents independently
monkey-patched the mock inline; consolidated as a 5-line mock
addition so tests stay clean.
**e2e/pulse-toast.spec.ts (NEW, 235 lines, 6 Playwright tests)**
Navigate /dashboard/settings → click Preview Pulse → assert first
toast appears within 500ms → assert >= 2 toasts visible at peak.
Click-to-dismiss removes clicked toast (matched by aria-label).
Hover survives >8s past the 5.5s dwell. Keyboard Enter dismisses
focused toast. CSS animation-play-state:paused on .toast-progress-
fill while hovered, running on mouseleave. Screenshots attached to
HTML report. Zero backend dependency (fireDemoSequence is purely
client-side).
**e2e/birth-ritual.spec.ts (NEW, 199 lines, 3 Playwright tests)**
Canvas mounts on /dashboard/graph (gracefully test.fixme if MCP
backend absent). Settings button injection + SPA route to /graph
→ screenshot timeline at t=0/500/1200/2000/2400/3000ms attached
to HTML report. pageerror + console-error listeners catch any
crash (would re-surface FATAL 6 if reintroduced). Three back-to-
back births — no errors, canvas still dispatches clicks.
Run commands:
cd apps/dashboard && npm test # 361/361 pass, ~600ms
cd apps/dashboard && npx playwright test # 9/9 pass, ~25s
Typecheck: 0 errors, 0 warnings. Build: clean adapter-static.
* fix(graph): default /api/graph to newest-memory center, add sort param
memory_timeline PR #37 exposed the same class of bug in the graph
endpoint: the dashboard Graph page (and the /api/graph endpoint it
hits) defaulted to centering on the most-connected memory, ran BFS at
depth 3, and capped the subgraph at 150 nodes. On a mature corpus this
clustered the visualization around a historical hotspot and hid freshly
ingested memories that hadn't accumulated edges yet. User-visible
symptom: TimeSlider on /graph showing "Feb 21 → Mar 1 2026" when the
database actually contains memories through today (Apr 20).
**Backend (`crates/vestige-mcp/src/dashboard/handlers.rs`):**
- `GraphParams` gains `sort: Option<String>` (accepted: "recent" |
"connected", unknown falls back to "recent").
- New internal `GraphSort` enum + case-insensitive `parse()`.
- Extracted `default_center_id(storage, sort)` so handler logic and
tests share the same branching. Recent path picks `get_all_nodes(1,
0)` (ORDER BY created_at DESC). Connected path picks
`get_most_connected_memory`, degrading gracefully to recent if the
DB has no edges yet.
- Default behaviour flipped from "connected" to "recent" — matches
user expectation of "show me my recent stuff".
**Dashboard (`apps/dashboard`):**
- `api.graph()` accepts `sort?: 'recent' | 'connected'` with JSDoc
explaining the rationale.
- `/graph/+page.svelte` passes `sort: 'recent'` when no query or
center_id is active. Query / center_id paths unchanged — they
already carry their own centering intent.
**Tests:** 6 new unit tests in `handlers::tests`:
- `graph_sort_parse_defaults_to_recent` (None, empty, garbage,
"recent", "Recent", "RECENT")
- `graph_sort_parse_accepts_connected_case_insensitive`
- `default_center_id_recent_returns_newest_node` — ingest 3 nodes,
assert newest is picked
- `default_center_id_connected_prefers_hub_over_newest` — wire a hub
node with 3 spokes, then ingest a newer "lonely" node; assert the
hub wins in Connected mode even though it's older
- `default_center_id_connected_falls_back_to_recent_when_no_edges`
— fresh DB with no connections still returns newest, not 404
- `default_center_id_returns_not_found_on_empty_db` — both modes
return 404 cleanly on empty storage
Build + test:
- cargo test -p vestige-mcp --lib handlers:: → 6/6 pass
- cargo test --workspace --lib → 830/830 pass, 0 failed
- cargo clippy -p vestige-core -p vestige-mcp --lib -- -D warnings →
clean
- npm run check → 0 errors, 0 warnings
- npm test → 361/361 pass
Binary already installed at ~/.local/bin/vestige-mcp (copied from
cargo build --release -p vestige-mcp). New Claude Desktop / Code
sessions will pick it up automatically when they respawn their MCP
subprocess. The dashboard HTTP server on port 3927 needs a manual
relaunch from a terminal with the usual pattern:
nohup bash -c 'tail -f /dev/null | \
VESTIGE_DASHBOARD_ENABLED=true ~/.local/bin/vestige-mcp' \
> /tmp/vestige-mcp.log 2>&1 & disown
* feat(v2.4): UI expansion — 8 new surfaces exposing the cognitive engine
Sam asked: "Build EVERY SINGLE MISSING UI PIECE." 10 parallel agents shipped
10 new viewports over the existing Rust backend, then 11 audit agents
line-by-line reviewed each one, extracted pure-logic helpers, fixed ~30
bugs, and shipped 549 new unit tests. Everything wired through the layout
with single-key shortcuts and a live theme toggle.
**Eight new routes**
- `/reasoning` — Reasoning Theater: Cmd+K ask palette → animated 8-stage
deep_reference pipeline + FSRS-trust-scored evidence cards +
contradiction arcs rendered as live SVG between evidence nodes
- `/duplicates` — threshold-driven cluster detector with winner selection,
Merge/Review/Dismiss actions, debounced slider
- `/dreams` — Dream Cinema: trigger dream + scrubbable 5-stage replay
(Replay → Cross-reference → Strengthen → Prune → Transfer) + insight
cards with novelty glow
- `/schedule` — FSRS Review Calendar: 6×7 grid with urgency color
bands (overdue/today/week/future), retention sparkline, expand-day list
- `/importance` — 4-channel radar (Novelty/Arousal/Reward/Attention) with
composite score + top-important list
- `/activation` — live spreading-activation view: search → SVG concentric
rings with decay animation + live-mode event feed
- `/contradictions` — 2D cosmic constellation of conflicting memories,
arcs colored by severity, tooltips with previews
- `/patterns` — cross-project pattern transfer heatmap with category
filters, top-transferred sidebar
**Three layout additions**
- `AmbientAwarenessStrip` — slim top band with retention vitals, at-risk
count, active intentions, recent dream, activity sparkline, dreaming
indicator, Sanhedrin-watch flash. Pure `$derived` over existing stores.
- `ThemeToggle` — dark/light/auto cycle with matchMedia listener,
localStorage persistence, SSR-safe, reduced-motion-aware. Rendered in
sidebar footer next to the connection dot.
- `MemoryAuditTrail` — per-memory Sources panel integrated as a
Content/Audit tab into the existing /memories expansion.
**Pure-logic helper modules extracted (for testability + reuse)**
reasoning-helpers, duplicates-helpers, dream-helpers, schedule-helpers,
audit-trail-helpers, awareness-helpers, contradiction-helpers,
activation-helpers, patterns-helpers, importance-helpers.
**Bugs fixed during audit (not exhaustive)**
- Trust-color inconsistency between EvidenceCard and the page confidence
ring (0.75 boundary split emerald vs amber)
- `new Date('garbage').toLocaleDateString()` returned literal "Invalid Date"
in 3 components — all now return em-dash or raw string
- NaN propagation in `Math.max(0, Math.min(1, NaN))` across clamps
- Off-by-one PRNG in audit-trail seeded mock (seed === UINT32_MAX yielded
rand() === 1.0 → index out of bounds)
- Duplicates dismissals keyed by array index broke on re-fetch; now keyed
by sorted cluster member IDs with stale-dismissal pruning
- Empty-cluster crash in DuplicateCluster.pickWinner
- Undefined tags crash in DuplicateCluster.safeTags
- Debounce timer leak in threshold slider (missing onDestroy cleanup)
- Schedule day-vs-hour granularity mismatch between calendar cell and
sidebar list ("today" in one, "in 1d" in the other)
- Schedule 500-memory hard cap silently truncated; bumped to 2000 + banner
- Schedule DST boundary bug in daysBetween (wall-clock math vs
startOfDay-normalized)
- Dream stage clamp now handles NaN/Infinity/floats
- Dream double-click debounce via `if (dreaming) return`
- Theme setTheme runtime validation; initTheme idempotence (listener +
style-element dedup on repeat calls)
- ContradictionArcs node radius unclamped (trust < 0 or > 1 rendered
invalid sizes); tooltip position clamp (could push off-canvas)
- ContradictionArcs $state closure capture (width/height weren't reactive
in the derived layout block)
- Activation route was MISSING from the repo — audit agent created it
with identity-based event filtering and proper RAF cleanup
- Layout: ThemeToggle was imported but never rendered — now in sidebar
footer; sidebar overflow-y-auto added for the 16-entry nav
**Tests — 549 new, 910 total passing (0 failures)**
ReasoningChain 42 | EvidenceCard 50
DuplicateCluster 64 | DreamStageReplay 19
DreamInsightCard 43 | FSRSCalendar 32
MemoryAuditTrail 45 | AmbientAwareness 60
theme (store) 31 | ContradictionArcs 43
ActivationNetwork 54 | PatternTransfer 31
ImportanceRadar 35 | + existing 361 tests still green
**Gates passed**
- `npm run check`: 0 errors, 0 warnings across 623 files
- `npm test`: 910/910 passing, 22 test files
- `npm run build`: clean adapter-static output
**Layout wiring**
- Nav array expanded 8 → 16 entries (existing 8 + 8 new routes)
- Single-key shortcuts added: R/A/D/C/P/U/X/N (no conflicts with
existing G/M/T/F/E/I/S/,)
- Cmd+K palette search works across all 16
- Mobile nav = top 5 (Graph, Reasoning, Memories, Timeline, Feed)
- AmbientAwarenessStrip mounted as first child of <main>
- ThemeToggle rendered in sidebar footer (was imported-but-unmounted)
- Theme initTheme() + teardown wired into onMount cleanup chain
Net branch delta: 47 files changed, +13,756 insertions, -6 deletions
* chore(release): v2.0.8 "Pulse"
Bundled release: Reasoning Theater wired to the 8-stage deep_reference
cognitive pipeline, Pulse InsightToast, Memory Birth Ritual (v2.3
Terrarium), 7 new dashboard surfaces (/duplicates, /dreams, /schedule,
/importance, /activation, /contradictions, /patterns), 3D graph
brightness system with auto distance-compensation + user slider, and
contradiction-detection + primary-selection hardening in the
cross_reference tool. Intel Mac (x86_64-apple-darwin) also flows through
to the release matrix from PR #43.
Added:
- POST /api/deep_reference — HTTP surface for the 8-stage pipeline
- DeepReferenceCompleted WebSocket event (primary + supporting +
contradicting memory IDs for downstream graph animation)
- /reasoning route, full UI + Cmd+K Ask palette
- 7 new dashboard surfaces exposing the cognitive engine
- Graph brightness slider + localStorage persistence + distance-based
emissive compensation so nodes don't disappear into fog at zoom-out
Fixed:
- Contradiction-detection false positives: adjacent-domain memories no
longer flagged as conflicts (NEGATION_PAIRS wildcards removed,
shared-words floor 2 → 4, topic-sim floor 0.15 → 0.55, STAGE 5
overlap floor 0.15 → 0.4)
- Primary-memory selection: unified composite 0.5 × relevance + 0.2 ×
trust + 0.3 × term_presence with hard topic-term filter, closing the
class of bug where off-topic high-trust memories won queries about
specific subjects
- Graph default-load fallback from sort=recent to sort=connected when
the newest memory is isolated, both backend and client
Changed:
- Reasoning page information hierarchy: chain renders first as hero,
confidence meter + Primary Source citation footer below
- Cargo feature split: embeddings code-only + ort-download | ort-dynamic
backends; defaults preserve identical behavior for existing consumers
- CI release-build now gates PRs too so multi-platform regressions
surface pre-merge
2026-04-23 02:21:11 -05:00
|
|
|
|
export function getGlowTexture(): THREE.Texture {
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
if (sharedGlowTexture) return sharedGlowTexture;
|
|
|
|
|
|
const size = 128;
|
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
|
|
canvas.width = size;
|
|
|
|
|
|
canvas.height = size;
|
|
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
|
if (!ctx) {
|
|
|
|
|
|
// Fallback: empty 1x1 texture; halos will be invisible but nothing crashes.
|
|
|
|
|
|
sharedGlowTexture = new THREE.Texture();
|
|
|
|
|
|
return sharedGlowTexture;
|
|
|
|
|
|
}
|
|
|
|
|
|
const gradient = ctx.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2);
|
|
|
|
|
|
gradient.addColorStop(0.0, 'rgba(255, 255, 255, 1.0)');
|
|
|
|
|
|
gradient.addColorStop(0.25, 'rgba(255, 255, 255, 0.7)');
|
|
|
|
|
|
gradient.addColorStop(0.55, 'rgba(255, 255, 255, 0.2)');
|
|
|
|
|
|
gradient.addColorStop(1.0, 'rgba(255, 255, 255, 0.0)');
|
|
|
|
|
|
ctx.fillStyle = gradient;
|
|
|
|
|
|
ctx.fillRect(0, 0, size, size);
|
|
|
|
|
|
const tex = new THREE.CanvasTexture(canvas);
|
|
|
|
|
|
tex.needsUpdate = true;
|
|
|
|
|
|
sharedGlowTexture = tex;
|
|
|
|
|
|
return tex;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 14:04:31 -06:00
|
|
|
|
function easeOutElastic(t: number): number {
|
|
|
|
|
|
if (t === 0 || t === 1) return t;
|
|
|
|
|
|
const p = 0.3;
|
|
|
|
|
|
return Math.pow(2, -10 * t) * Math.sin(((t - p / 4) * (2 * Math.PI)) / p) + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function easeInBack(t: number): number {
|
|
|
|
|
|
const s = 1.70158;
|
|
|
|
|
|
return t * t * ((s + 1) * t - s);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface MaterializingNode {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
frame: number;
|
|
|
|
|
|
totalFrames: number;
|
|
|
|
|
|
mesh: THREE.Mesh;
|
|
|
|
|
|
glow: THREE.Sprite;
|
|
|
|
|
|
label: THREE.Sprite;
|
|
|
|
|
|
targetScale: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface DissolvingNode {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
frame: number;
|
|
|
|
|
|
totalFrames: number;
|
|
|
|
|
|
mesh: THREE.Mesh;
|
|
|
|
|
|
glow: THREE.Sprite;
|
|
|
|
|
|
label: THREE.Sprite;
|
|
|
|
|
|
originalScale: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface GrowingNode {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
frame: number;
|
|
|
|
|
|
totalFrames: number;
|
|
|
|
|
|
startScale: number;
|
|
|
|
|
|
targetScale: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 21:24:10 -06:00
|
|
|
|
export class NodeManager {
|
|
|
|
|
|
group: THREE.Group;
|
|
|
|
|
|
meshMap = new Map<string, THREE.Mesh>();
|
2026-03-03 14:04:31 -06:00
|
|
|
|
glowMap = new Map<string, THREE.Sprite>();
|
2026-03-01 21:24:10 -06:00
|
|
|
|
positions = new Map<string, THREE.Vector3>();
|
|
|
|
|
|
labelSprites = new Map<string, THREE.Sprite>();
|
|
|
|
|
|
hoveredNode: string | null = null;
|
|
|
|
|
|
selectedNode: string | null = null;
|
2026-04-26 14:36:38 -05:00
|
|
|
|
/// Colour nodes by type, FSRS state, or AhaGraph learning tags.
|
|
|
|
|
|
/// Switched at runtime via `setColorMode`.
|
feat(graph): FSRS memory-state colour mode + legend overlay
Closes Agent 1's audit gap #4: FSRS memory state (Active / Dormant /
Silent / Unavailable) was computed server-side per query but never
rendered in the 3D graph. Spheres always tinted by node type.
The new colour mode adds a second channel that users can toggle
between at runtime — Type (default, existing behaviour) and State
(new). The toggle is a radio-pair pill in the graph page's top-right
control bar next to the node-count selector + Dream button.
Buckets + palette:
- Active ≥ 70% emerald #10b981 easily retrievable
- Dormant 40-70% amber #f59e0b retrievable with effort
- Silent 10-40% violet #8b5cf6 difficult, needs cues
- Unavail. < 10% slate #6b7280 needs reinforcement
Thresholds match `execute_system_status` at the backend so the graph
colour bands line up exactly with what the Stats page reports in its
stateDistribution block. Using retention as the proxy for the full
accessibility formula (retention × 0.5 + retrieval × 0.3 + storage ×
0.2) is an approximation — retention is the dominant 0.5 weight and
it is the only FSRS channel the current GraphNode DTO carries. Swap
to the full formula in a future release if the DTO grows.
Implementation:
- `apps/dashboard/src/lib/graph/nodes.ts` — new `MemoryState` type,
`getMemoryState(retention)`, `MEMORY_STATE_COLORS`,
`MEMORY_STATE_DESCRIPTIONS`, `ColorMode`, `getNodeColor(node, mode)`.
- `NodeManager.colorMode` field (default `'type'`). `createNodeMeshes`
now calls `getNodeColor(node, this.colorMode)` so newly-added nodes
during the session follow the toggled mode.
- New `NodeManager.setColorMode(mode)` mutates every live mesh's
material + glow sprite colour in place. Idempotent; cheap. Does NOT
touch opacity/emissive-intensity so the v2.0.5 suppression dimming
layer keeps working unchanged.
- New `MemoryStateLegend.svelte` floating overlay in the bottom-right
when state mode is active (hidden in type mode so the legend doesn't
compete with the node-type palette).
- `Graph3D.svelte` accepts a new `colorMode` prop (default `'type'`)
and runs a `$effect` that calls `setColorMode` on every toggle.
- Dashboard rebuild picks up the new component + wiring.
Tests: 171 vitest, svelte-check 581 files / 0 errors. No backend
changes; this is pure dashboard code.
2026-04-19 20:45:08 -05:00
|
|
|
|
colorMode: ColorMode = 'type';
|
2026-03-01 21:24:10 -06:00
|
|
|
|
|
2026-03-03 14:04:31 -06:00
|
|
|
|
private materializingNodes: MaterializingNode[] = [];
|
|
|
|
|
|
private dissolvingNodes: DissolvingNode[] = [];
|
|
|
|
|
|
private growingNodes: GrowingNode[] = [];
|
|
|
|
|
|
|
2026-03-01 21:24:10 -06:00
|
|
|
|
constructor() {
|
|
|
|
|
|
this.group = new THREE.Group();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(graph): FSRS memory-state colour mode + legend overlay
Closes Agent 1's audit gap #4: FSRS memory state (Active / Dormant /
Silent / Unavailable) was computed server-side per query but never
rendered in the 3D graph. Spheres always tinted by node type.
The new colour mode adds a second channel that users can toggle
between at runtime — Type (default, existing behaviour) and State
(new). The toggle is a radio-pair pill in the graph page's top-right
control bar next to the node-count selector + Dream button.
Buckets + palette:
- Active ≥ 70% emerald #10b981 easily retrievable
- Dormant 40-70% amber #f59e0b retrievable with effort
- Silent 10-40% violet #8b5cf6 difficult, needs cues
- Unavail. < 10% slate #6b7280 needs reinforcement
Thresholds match `execute_system_status` at the backend so the graph
colour bands line up exactly with what the Stats page reports in its
stateDistribution block. Using retention as the proxy for the full
accessibility formula (retention × 0.5 + retrieval × 0.3 + storage ×
0.2) is an approximation — retention is the dominant 0.5 weight and
it is the only FSRS channel the current GraphNode DTO carries. Swap
to the full formula in a future release if the DTO grows.
Implementation:
- `apps/dashboard/src/lib/graph/nodes.ts` — new `MemoryState` type,
`getMemoryState(retention)`, `MEMORY_STATE_COLORS`,
`MEMORY_STATE_DESCRIPTIONS`, `ColorMode`, `getNodeColor(node, mode)`.
- `NodeManager.colorMode` field (default `'type'`). `createNodeMeshes`
now calls `getNodeColor(node, this.colorMode)` so newly-added nodes
during the session follow the toggled mode.
- New `NodeManager.setColorMode(mode)` mutates every live mesh's
material + glow sprite colour in place. Idempotent; cheap. Does NOT
touch opacity/emissive-intensity so the v2.0.5 suppression dimming
layer keeps working unchanged.
- New `MemoryStateLegend.svelte` floating overlay in the bottom-right
when state mode is active (hidden in type mode so the legend doesn't
compete with the node-type palette).
- `Graph3D.svelte` accepts a new `colorMode` prop (default `'type'`)
and runs a `$effect` that calls `setColorMode` on every toggle.
- Dashboard rebuild picks up the new component + wiring.
Tests: 171 vitest, svelte-check 581 files / 0 errors. No backend
changes; this is pure dashboard code.
2026-04-19 20:45:08 -05:00
|
|
|
|
/// Switch the active colour mode and re-tint every live node in place.
|
|
|
|
|
|
/// Safe to call mid-animation — the mesh + glow materials are mutable.
|
|
|
|
|
|
/// Suppressed nodes keep their 20% opacity / zero-emissive treatment
|
|
|
|
|
|
/// since that is a separate visual channel (v2.0.5 SIF).
|
|
|
|
|
|
setColorMode(mode: ColorMode) {
|
|
|
|
|
|
if (this.colorMode === mode) return;
|
|
|
|
|
|
this.colorMode = mode;
|
|
|
|
|
|
for (const [id, mesh] of this.meshMap) {
|
|
|
|
|
|
const retention = (mesh.userData.retention as number | undefined) ?? 0;
|
|
|
|
|
|
const type = (mesh.userData.type as string | undefined) ?? 'fact';
|
2026-04-26 14:36:38 -05:00
|
|
|
|
const tags = Array.isArray(mesh.userData.tags)
|
|
|
|
|
|
? (mesh.userData.tags as string[])
|
|
|
|
|
|
: [];
|
feat(graph): FSRS memory-state colour mode + legend overlay
Closes Agent 1's audit gap #4: FSRS memory state (Active / Dormant /
Silent / Unavailable) was computed server-side per query but never
rendered in the 3D graph. Spheres always tinted by node type.
The new colour mode adds a second channel that users can toggle
between at runtime — Type (default, existing behaviour) and State
(new). The toggle is a radio-pair pill in the graph page's top-right
control bar next to the node-count selector + Dream button.
Buckets + palette:
- Active ≥ 70% emerald #10b981 easily retrievable
- Dormant 40-70% amber #f59e0b retrievable with effort
- Silent 10-40% violet #8b5cf6 difficult, needs cues
- Unavail. < 10% slate #6b7280 needs reinforcement
Thresholds match `execute_system_status` at the backend so the graph
colour bands line up exactly with what the Stats page reports in its
stateDistribution block. Using retention as the proxy for the full
accessibility formula (retention × 0.5 + retrieval × 0.3 + storage ×
0.2) is an approximation — retention is the dominant 0.5 weight and
it is the only FSRS channel the current GraphNode DTO carries. Swap
to the full formula in a future release if the DTO grows.
Implementation:
- `apps/dashboard/src/lib/graph/nodes.ts` — new `MemoryState` type,
`getMemoryState(retention)`, `MEMORY_STATE_COLORS`,
`MEMORY_STATE_DESCRIPTIONS`, `ColorMode`, `getNodeColor(node, mode)`.
- `NodeManager.colorMode` field (default `'type'`). `createNodeMeshes`
now calls `getNodeColor(node, this.colorMode)` so newly-added nodes
during the session follow the toggled mode.
- New `NodeManager.setColorMode(mode)` mutates every live mesh's
material + glow sprite colour in place. Idempotent; cheap. Does NOT
touch opacity/emissive-intensity so the v2.0.5 suppression dimming
layer keeps working unchanged.
- New `MemoryStateLegend.svelte` floating overlay in the bottom-right
when state mode is active (hidden in type mode so the legend doesn't
compete with the node-type palette).
- `Graph3D.svelte` accepts a new `colorMode` prop (default `'type'`)
and runs a `$effect` that calls `setColorMode` on every toggle.
- Dashboard rebuild picks up the new component + wiring.
Tests: 171 vitest, svelte-check 581 files / 0 errors. No backend
changes; this is pure dashboard code.
2026-04-19 20:45:08 -05:00
|
|
|
|
const stubNode = {
|
|
|
|
|
|
id,
|
|
|
|
|
|
label: '',
|
|
|
|
|
|
type,
|
|
|
|
|
|
retention,
|
2026-04-26 14:36:38 -05:00
|
|
|
|
tags,
|
feat(graph): FSRS memory-state colour mode + legend overlay
Closes Agent 1's audit gap #4: FSRS memory state (Active / Dormant /
Silent / Unavailable) was computed server-side per query but never
rendered in the 3D graph. Spheres always tinted by node type.
The new colour mode adds a second channel that users can toggle
between at runtime — Type (default, existing behaviour) and State
(new). The toggle is a radio-pair pill in the graph page's top-right
control bar next to the node-count selector + Dream button.
Buckets + palette:
- Active ≥ 70% emerald #10b981 easily retrievable
- Dormant 40-70% amber #f59e0b retrievable with effort
- Silent 10-40% violet #8b5cf6 difficult, needs cues
- Unavail. < 10% slate #6b7280 needs reinforcement
Thresholds match `execute_system_status` at the backend so the graph
colour bands line up exactly with what the Stats page reports in its
stateDistribution block. Using retention as the proxy for the full
accessibility formula (retention × 0.5 + retrieval × 0.3 + storage ×
0.2) is an approximation — retention is the dominant 0.5 weight and
it is the only FSRS channel the current GraphNode DTO carries. Swap
to the full formula in a future release if the DTO grows.
Implementation:
- `apps/dashboard/src/lib/graph/nodes.ts` — new `MemoryState` type,
`getMemoryState(retention)`, `MEMORY_STATE_COLORS`,
`MEMORY_STATE_DESCRIPTIONS`, `ColorMode`, `getNodeColor(node, mode)`.
- `NodeManager.colorMode` field (default `'type'`). `createNodeMeshes`
now calls `getNodeColor(node, this.colorMode)` so newly-added nodes
during the session follow the toggled mode.
- New `NodeManager.setColorMode(mode)` mutates every live mesh's
material + glow sprite colour in place. Idempotent; cheap. Does NOT
touch opacity/emissive-intensity so the v2.0.5 suppression dimming
layer keeps working unchanged.
- New `MemoryStateLegend.svelte` floating overlay in the bottom-right
when state mode is active (hidden in type mode so the legend doesn't
compete with the node-type palette).
- `Graph3D.svelte` accepts a new `colorMode` prop (default `'type'`)
and runs a `$effect` that calls `setColorMode` on every toggle.
- Dashboard rebuild picks up the new component + wiring.
Tests: 171 vitest, svelte-check 581 files / 0 errors. No backend
changes; this is pure dashboard code.
2026-04-19 20:45:08 -05:00
|
|
|
|
createdAt: '',
|
|
|
|
|
|
updatedAt: '',
|
|
|
|
|
|
isCenter: false,
|
|
|
|
|
|
} as GraphNode;
|
|
|
|
|
|
const hex = getNodeColor(stubNode, mode);
|
|
|
|
|
|
const newColor = new THREE.Color(hex);
|
|
|
|
|
|
const mat = mesh.material as THREE.MeshStandardMaterial;
|
|
|
|
|
|
mat.color.copy(newColor);
|
|
|
|
|
|
mat.emissive.copy(newColor);
|
|
|
|
|
|
const glow = this.glowMap.get(id);
|
|
|
|
|
|
if (glow) {
|
|
|
|
|
|
(glow.material as THREE.SpriteMaterial).color.copy(newColor);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 21:24:10 -06:00
|
|
|
|
createNodes(nodes: GraphNode[]): Map<string, THREE.Vector3> {
|
|
|
|
|
|
const phi = (1 + Math.sqrt(5)) / 2;
|
|
|
|
|
|
const count = nodes.length;
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
|
|
const node = nodes[i];
|
|
|
|
|
|
|
|
|
|
|
|
// Fibonacci sphere distribution for initial positions
|
|
|
|
|
|
const y = 1 - (2 * i) / (count - 1 || 1);
|
|
|
|
|
|
const radius = Math.sqrt(1 - y * y);
|
|
|
|
|
|
const theta = (2 * Math.PI * i) / phi;
|
|
|
|
|
|
const spread = 30 + count * 0.5;
|
|
|
|
|
|
|
|
|
|
|
|
const pos = new THREE.Vector3(
|
|
|
|
|
|
radius * Math.cos(theta) * spread,
|
|
|
|
|
|
y * spread,
|
|
|
|
|
|
radius * Math.sin(theta) * spread
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (node.isCenter) pos.set(0, 0, 0);
|
|
|
|
|
|
|
|
|
|
|
|
this.positions.set(node.id, pos);
|
2026-03-03 14:04:31 -06:00
|
|
|
|
this.createNodeMeshes(node, pos, 1.0);
|
2026-03-01 21:24:10 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return this.positions;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 14:04:31 -06:00
|
|
|
|
private createNodeMeshes(node: GraphNode, pos: THREE.Vector3, initialScale: number) {
|
|
|
|
|
|
const size = 0.5 + node.retention * 2;
|
feat(graph): FSRS memory-state colour mode + legend overlay
Closes Agent 1's audit gap #4: FSRS memory state (Active / Dormant /
Silent / Unavailable) was computed server-side per query but never
rendered in the 3D graph. Spheres always tinted by node type.
The new colour mode adds a second channel that users can toggle
between at runtime — Type (default, existing behaviour) and State
(new). The toggle is a radio-pair pill in the graph page's top-right
control bar next to the node-count selector + Dream button.
Buckets + palette:
- Active ≥ 70% emerald #10b981 easily retrievable
- Dormant 40-70% amber #f59e0b retrievable with effort
- Silent 10-40% violet #8b5cf6 difficult, needs cues
- Unavail. < 10% slate #6b7280 needs reinforcement
Thresholds match `execute_system_status` at the backend so the graph
colour bands line up exactly with what the Stats page reports in its
stateDistribution block. Using retention as the proxy for the full
accessibility formula (retention × 0.5 + retrieval × 0.3 + storage ×
0.2) is an approximation — retention is the dominant 0.5 weight and
it is the only FSRS channel the current GraphNode DTO carries. Swap
to the full formula in a future release if the DTO grows.
Implementation:
- `apps/dashboard/src/lib/graph/nodes.ts` — new `MemoryState` type,
`getMemoryState(retention)`, `MEMORY_STATE_COLORS`,
`MEMORY_STATE_DESCRIPTIONS`, `ColorMode`, `getNodeColor(node, mode)`.
- `NodeManager.colorMode` field (default `'type'`). `createNodeMeshes`
now calls `getNodeColor(node, this.colorMode)` so newly-added nodes
during the session follow the toggled mode.
- New `NodeManager.setColorMode(mode)` mutates every live mesh's
material + glow sprite colour in place. Idempotent; cheap. Does NOT
touch opacity/emissive-intensity so the v2.0.5 suppression dimming
layer keeps working unchanged.
- New `MemoryStateLegend.svelte` floating overlay in the bottom-right
when state mode is active (hidden in type mode so the legend doesn't
compete with the node-type palette).
- `Graph3D.svelte` accepts a new `colorMode` prop (default `'type'`)
and runs a `$effect` that calls `setColorMode` on every toggle.
- Dashboard rebuild picks up the new component + wiring.
Tests: 171 vitest, svelte-check 581 files / 0 errors. No backend
changes; this is pure dashboard code.
2026-04-19 20:45:08 -05:00
|
|
|
|
// v2.0.8: respect the active colour mode. Newly-added nodes during the
|
|
|
|
|
|
// same session follow the mode toggled at the UI layer.
|
|
|
|
|
|
const color = getNodeColor(node, this.colorMode);
|
2026-03-03 14:04:31 -06:00
|
|
|
|
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
// v2.0.5 Active Forgetting: suppressed memories dim to 20% opacity
|
|
|
|
|
|
// and lose their emissive glow, mimicking inhibitory-control silencing.
|
|
|
|
|
|
const isSuppressed = (node.suppression_count ?? 0) > 0;
|
|
|
|
|
|
|
2026-03-03 14:04:31 -06:00
|
|
|
|
// Node mesh
|
|
|
|
|
|
const geometry = new THREE.SphereGeometry(size, 16, 16);
|
|
|
|
|
|
const material = new THREE.MeshStandardMaterial({
|
|
|
|
|
|
color: new THREE.Color(color),
|
|
|
|
|
|
emissive: new THREE.Color(color),
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
emissiveIntensity: isSuppressed ? 0.0 : 0.3 + node.retention * 0.5,
|
2026-03-03 14:04:31 -06:00
|
|
|
|
roughness: 0.3,
|
|
|
|
|
|
metalness: 0.1,
|
|
|
|
|
|
transparent: true,
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
opacity: isSuppressed ? 0.2 : 0.3 + node.retention * 0.7,
|
2026-03-03 14:04:31 -06:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const mesh = new THREE.Mesh(geometry, material);
|
|
|
|
|
|
mesh.position.copy(pos);
|
|
|
|
|
|
mesh.scale.setScalar(initialScale);
|
2026-04-26 14:36:38 -05:00
|
|
|
|
mesh.userData = { nodeId: node.id, type: node.type, retention: node.retention, tags: node.tags };
|
2026-03-03 14:04:31 -06:00
|
|
|
|
this.meshMap.set(node.id, mesh);
|
|
|
|
|
|
this.group.add(mesh);
|
|
|
|
|
|
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
// Glow sprite — radial-gradient texture kills the square-halo artefact
|
|
|
|
|
|
// from issue #31. depthWrite:false prevents z-fighting with the sphere.
|
2026-03-03 14:04:31 -06:00
|
|
|
|
const spriteMat = new THREE.SpriteMaterial({
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
map: getGlowTexture(),
|
2026-03-03 14:04:31 -06:00
|
|
|
|
color: new THREE.Color(color),
|
|
|
|
|
|
transparent: true,
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
opacity: initialScale > 0 ? (isSuppressed ? 0.1 : 0.3 + node.retention * 0.35) : 0,
|
2026-03-03 14:04:31 -06:00
|
|
|
|
blending: THREE.AdditiveBlending,
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
depthWrite: false,
|
2026-03-03 14:04:31 -06:00
|
|
|
|
});
|
|
|
|
|
|
const sprite = new THREE.Sprite(spriteMat);
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
// Slightly larger halo — the gradient falls off quickly so we need
|
|
|
|
|
|
// more screen real estate for a visible soft bloom footprint.
|
|
|
|
|
|
sprite.scale.set(size * 6 * initialScale, size * 6 * initialScale, 1);
|
2026-03-03 14:04:31 -06:00
|
|
|
|
sprite.position.copy(pos);
|
|
|
|
|
|
sprite.userData = { isGlow: true, nodeId: node.id };
|
|
|
|
|
|
this.glowMap.set(node.id, sprite);
|
|
|
|
|
|
this.group.add(sprite);
|
|
|
|
|
|
|
|
|
|
|
|
// Text label sprite
|
|
|
|
|
|
const labelText = node.label || node.type;
|
2026-04-19 21:52:14 -05:00
|
|
|
|
const labelSprite = this.createTextSprite(labelText, '#94a3b8');
|
2026-03-03 14:04:31 -06:00
|
|
|
|
labelSprite.position.copy(pos);
|
|
|
|
|
|
labelSprite.position.y += size * 2 + 1.5;
|
|
|
|
|
|
labelSprite.userData = { isLabel: true, nodeId: node.id, offset: size * 2 + 1.5 };
|
|
|
|
|
|
this.group.add(labelSprite);
|
|
|
|
|
|
this.labelSprites.set(node.id, labelSprite);
|
|
|
|
|
|
|
|
|
|
|
|
return { mesh, glow: sprite, label: labelSprite, size };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
v2.0.8 Pulse — Reasoning Theater + Pulse Toast + Terrarium + Intel Mac (#44)
* docs: anti-hedging banner + master state-and-plan + UI roadmap
Project-level CLAUDE.md line 1 is now the anti-hedging banner referencing
the global rule at /Users/entity002/CLAUDE.md. When Sam commits to an
execution path (content cadence, shipping velocity, outreach, launch),
forecasts compute as if the commitment is already met — no discounting by
execution probability.
VESTIGE_STATE_AND_PLAN.md (1135 lines, 14 parts): single-page reference
any AI agent can read to plan the next phase without re-reading the repo.
Covers current state of every crate, the 24 MCP tools, the 19 WebSocket
event types, the 8 IDE integrations, and v2.1 through v3.0 roadmap.
UI_ROADMAP_v2.1_v2.2.md: compiled UI-gap analysis from 4 parallel research
agents (April 19). 10 critical UI gaps + 2026 bleeding-edge patterns +
unclaimed territory (menu bar widget, voice-first, AR/VR, time-scrubber).
* feat(v2.2-pulse): InsightToast + multi-process STORAGE docs
Two independent ship items landing together on the v2.2 branch ahead of
the Tuesday launch — a new UI surface that makes Vestige's cognitive
events visible in real time, and honest documentation of the multi-process
safety story that underpins the Stigmergic Swarm narrative.
**InsightToast** (apps/dashboard/src/lib/components/InsightToast.svelte,
apps/dashboard/src/lib/stores/toast.ts):
The dashboard already had a working WebSocket event stream on
ws://localhost:3927/ws that broadcast every cognitive event (dream
completions, consolidation sweeps, memory promotions/demotions, active-
forgetting suppression and Rac1 cascades, bridge discoveries). None of
that was surfaced to a user looking at anything other than the raw feed
view. InsightToast subscribes to the existing eventFeed derived store,
filters the spammy lifecycle events (Heartbeat, SearchPerformed,
RetentionDecayed, ActivationSpread, ImportanceScored, MemoryCreated),
and translates the narrative events into ephemeral toasts with a
bioluminescent colored accent matching EVENT_TYPE_COLORS.
Design notes:
- Rate-limited ConnectionDiscovered at 1.5s intervals (dreams emit many).
- Max 4 visible toasts, auto-dismiss at 4.5-7s depending on event weight.
- Click or Enter/Space to dismiss early.
- Bottom-right on desktop, top-banner on mobile.
- Reduced-motion honored via prefers-reduced-motion.
- Zero new websocket subscriptions — everything piggybacks on the
existing derived store.
Also added a "Preview Pulse" button to Settings -> Cognitive Operations
that fires a synthetic sequence of four toasts (DreamCompleted,
ConnectionDiscovered, MemorySuppressed, ConsolidationCompleted) so
the animation is demoable without waiting for real cognitive activity.
**Multi-Process Safety section in docs/STORAGE.md**:
Grounds the Stigmergic Swarm story with concrete tables of what the
current WAL + 5s busy_timeout configuration actually supports vs what
remains experimental. Key honest points:
- Shared --data-dir + ONE vestige-mcp + N clients is the shipping
pattern for multi-agent coordination.
- Two vestige-mcp processes writing the same file is experimental —
documented with the lsof + pkill recovery path.
- Roadmap lists the three items that would promote it to "supported":
advisory file lock, retry-with-jitter on SQLITE_BUSY, and a
concurrent-writer load test.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm run build: clean static build, adapter-static succeeds
* feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
* fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
* test(v2.3): full e2e + integration coverage for Pulse + Birth Ritual
Post-ship verification pass — five parallel write-agents produced 229 new
tests across vitest units, vitest integration, and Playwright browser e2e.
Net suite: 361 vitest pass (up from 251, +110) and 9/9 Playwright pass on
back-to-back runs.
**toast.test.ts (NEW, 661 lines, 42 tests)**
Silent-lobotomy batch walk proven (multi-event tick processes ALL, not
just newest, oldest-first ordering preserved). Hover-panic pause/resume
with remaining-ms math. All 9 event type translations asserted, all 11
noise types asserted silent. ConnectionDiscovered 1500ms throttle.
MAX_VISIBLE=4 eviction. clear() tears down all timers. fireDemoSequence
staggers 4 toasts at 800ms intervals. vi.useFakeTimers + vi.mock of
eventFeed; vi.resetModules in beforeEach for module-singleton isolation.
**websocket.test.ts (NEW, 247 lines, 30 tests)**
injectEvent adds to front, respects MAX_EVENTS=200 with FIFO eviction,
triggers eventFeed emissions. All 6 derived stores (isConnected,
heartbeat, memoryCount, avgRetention, suppressedCount, uptimeSeconds)
verified — defaults, post-heartbeat values, clearEvents preserves
lastHeartbeat. 13 formatUptime boundary cases (0/59/60/3599/3600/
86399/86400 seconds + negative / NaN / ±Infinity).
**effects.test.ts (EXTENDED, +501 lines, +21 tests, 51 total)**
createBirthOrb full lifecycle — sprite count (halo + core), cosmic
center via camera.quaternion, gestation phase (position lock, opacity
rise, scale easing, color tint), flight Bezier arc above linear
midpoint at t=0.5, dynamic mid-flight target redirect. onArrive fires
exactly once at frame 139. Post-arrival fade + disposal cleans scene
children. Sanhedrin Shatter: target goes undefined mid-flight →
onArrive NEVER called, implosion spawned, halo blood-red, eventual
cleanup. dispose() cleans active orbs. Multiple simultaneous orbs.
Custom gestation/flight frame opts honored. Zero-alloc invariant
smoke test (6 orbs × 150 frames, no leaks).
**nodes.test.ts (EXTENDED, +197 lines, +10 tests, 42 total)**
addNode({isBirthRitual:true}) hides mesh/glow/label immediately,
stamps birthRitualPending sentinel with correct totalFrames +
targetScale, does NOT enqueue materialization. igniteNode flips
visibility + enqueues materialization. Idempotent — second call
no-op. Non-ritual nodes unaffected. Unknown id is safe no-op.
Position stored in positions map while invisible (force sim still
sees it). removeNode + late igniteNode is safe.
**events.test.ts (EXTENDED, +268 lines, +7 tests, 55 total)**
MemoryCreated → mesh hidden immediately, 2 birth-orb sprites added,
ZERO RingGeometry meshes and ZERO Points particles at spawn. Full
ritual drive → onArrive fires, node visible + materializing, sentinel
cleared. Newton's Cradle: target mesh scale exactly 0.001 * 1.8 right
after arrival. Dual shockwave: exactly 2 Ring meshes added. Re-read
live position on arrival — force-sim motion during ritual → burst
lands at the NEW position. Sanhedrin abort path → rainbow burst,
shockwave, ripple wave are NEVER called (vi.spyOn).
**three-mock.ts (EXTENDED)**
Added Color.setRGB — production Three.js has it, the Sanhedrin-
Shatter path in effects.ts uses it. Two write-agents independently
monkey-patched the mock inline; consolidated as a 5-line mock
addition so tests stay clean.
**e2e/pulse-toast.spec.ts (NEW, 235 lines, 6 Playwright tests)**
Navigate /dashboard/settings → click Preview Pulse → assert first
toast appears within 500ms → assert >= 2 toasts visible at peak.
Click-to-dismiss removes clicked toast (matched by aria-label).
Hover survives >8s past the 5.5s dwell. Keyboard Enter dismisses
focused toast. CSS animation-play-state:paused on .toast-progress-
fill while hovered, running on mouseleave. Screenshots attached to
HTML report. Zero backend dependency (fireDemoSequence is purely
client-side).
**e2e/birth-ritual.spec.ts (NEW, 199 lines, 3 Playwright tests)**
Canvas mounts on /dashboard/graph (gracefully test.fixme if MCP
backend absent). Settings button injection + SPA route to /graph
→ screenshot timeline at t=0/500/1200/2000/2400/3000ms attached
to HTML report. pageerror + console-error listeners catch any
crash (would re-surface FATAL 6 if reintroduced). Three back-to-
back births — no errors, canvas still dispatches clicks.
Run commands:
cd apps/dashboard && npm test # 361/361 pass, ~600ms
cd apps/dashboard && npx playwright test # 9/9 pass, ~25s
Typecheck: 0 errors, 0 warnings. Build: clean adapter-static.
* fix(graph): default /api/graph to newest-memory center, add sort param
memory_timeline PR #37 exposed the same class of bug in the graph
endpoint: the dashboard Graph page (and the /api/graph endpoint it
hits) defaulted to centering on the most-connected memory, ran BFS at
depth 3, and capped the subgraph at 150 nodes. On a mature corpus this
clustered the visualization around a historical hotspot and hid freshly
ingested memories that hadn't accumulated edges yet. User-visible
symptom: TimeSlider on /graph showing "Feb 21 → Mar 1 2026" when the
database actually contains memories through today (Apr 20).
**Backend (`crates/vestige-mcp/src/dashboard/handlers.rs`):**
- `GraphParams` gains `sort: Option<String>` (accepted: "recent" |
"connected", unknown falls back to "recent").
- New internal `GraphSort` enum + case-insensitive `parse()`.
- Extracted `default_center_id(storage, sort)` so handler logic and
tests share the same branching. Recent path picks `get_all_nodes(1,
0)` (ORDER BY created_at DESC). Connected path picks
`get_most_connected_memory`, degrading gracefully to recent if the
DB has no edges yet.
- Default behaviour flipped from "connected" to "recent" — matches
user expectation of "show me my recent stuff".
**Dashboard (`apps/dashboard`):**
- `api.graph()` accepts `sort?: 'recent' | 'connected'` with JSDoc
explaining the rationale.
- `/graph/+page.svelte` passes `sort: 'recent'` when no query or
center_id is active. Query / center_id paths unchanged — they
already carry their own centering intent.
**Tests:** 6 new unit tests in `handlers::tests`:
- `graph_sort_parse_defaults_to_recent` (None, empty, garbage,
"recent", "Recent", "RECENT")
- `graph_sort_parse_accepts_connected_case_insensitive`
- `default_center_id_recent_returns_newest_node` — ingest 3 nodes,
assert newest is picked
- `default_center_id_connected_prefers_hub_over_newest` — wire a hub
node with 3 spokes, then ingest a newer "lonely" node; assert the
hub wins in Connected mode even though it's older
- `default_center_id_connected_falls_back_to_recent_when_no_edges`
— fresh DB with no connections still returns newest, not 404
- `default_center_id_returns_not_found_on_empty_db` — both modes
return 404 cleanly on empty storage
Build + test:
- cargo test -p vestige-mcp --lib handlers:: → 6/6 pass
- cargo test --workspace --lib → 830/830 pass, 0 failed
- cargo clippy -p vestige-core -p vestige-mcp --lib -- -D warnings →
clean
- npm run check → 0 errors, 0 warnings
- npm test → 361/361 pass
Binary already installed at ~/.local/bin/vestige-mcp (copied from
cargo build --release -p vestige-mcp). New Claude Desktop / Code
sessions will pick it up automatically when they respawn their MCP
subprocess. The dashboard HTTP server on port 3927 needs a manual
relaunch from a terminal with the usual pattern:
nohup bash -c 'tail -f /dev/null | \
VESTIGE_DASHBOARD_ENABLED=true ~/.local/bin/vestige-mcp' \
> /tmp/vestige-mcp.log 2>&1 & disown
* feat(v2.4): UI expansion — 8 new surfaces exposing the cognitive engine
Sam asked: "Build EVERY SINGLE MISSING UI PIECE." 10 parallel agents shipped
10 new viewports over the existing Rust backend, then 11 audit agents
line-by-line reviewed each one, extracted pure-logic helpers, fixed ~30
bugs, and shipped 549 new unit tests. Everything wired through the layout
with single-key shortcuts and a live theme toggle.
**Eight new routes**
- `/reasoning` — Reasoning Theater: Cmd+K ask palette → animated 8-stage
deep_reference pipeline + FSRS-trust-scored evidence cards +
contradiction arcs rendered as live SVG between evidence nodes
- `/duplicates` — threshold-driven cluster detector with winner selection,
Merge/Review/Dismiss actions, debounced slider
- `/dreams` — Dream Cinema: trigger dream + scrubbable 5-stage replay
(Replay → Cross-reference → Strengthen → Prune → Transfer) + insight
cards with novelty glow
- `/schedule` — FSRS Review Calendar: 6×7 grid with urgency color
bands (overdue/today/week/future), retention sparkline, expand-day list
- `/importance` — 4-channel radar (Novelty/Arousal/Reward/Attention) with
composite score + top-important list
- `/activation` — live spreading-activation view: search → SVG concentric
rings with decay animation + live-mode event feed
- `/contradictions` — 2D cosmic constellation of conflicting memories,
arcs colored by severity, tooltips with previews
- `/patterns` — cross-project pattern transfer heatmap with category
filters, top-transferred sidebar
**Three layout additions**
- `AmbientAwarenessStrip` — slim top band with retention vitals, at-risk
count, active intentions, recent dream, activity sparkline, dreaming
indicator, Sanhedrin-watch flash. Pure `$derived` over existing stores.
- `ThemeToggle` — dark/light/auto cycle with matchMedia listener,
localStorage persistence, SSR-safe, reduced-motion-aware. Rendered in
sidebar footer next to the connection dot.
- `MemoryAuditTrail` — per-memory Sources panel integrated as a
Content/Audit tab into the existing /memories expansion.
**Pure-logic helper modules extracted (for testability + reuse)**
reasoning-helpers, duplicates-helpers, dream-helpers, schedule-helpers,
audit-trail-helpers, awareness-helpers, contradiction-helpers,
activation-helpers, patterns-helpers, importance-helpers.
**Bugs fixed during audit (not exhaustive)**
- Trust-color inconsistency between EvidenceCard and the page confidence
ring (0.75 boundary split emerald vs amber)
- `new Date('garbage').toLocaleDateString()` returned literal "Invalid Date"
in 3 components — all now return em-dash or raw string
- NaN propagation in `Math.max(0, Math.min(1, NaN))` across clamps
- Off-by-one PRNG in audit-trail seeded mock (seed === UINT32_MAX yielded
rand() === 1.0 → index out of bounds)
- Duplicates dismissals keyed by array index broke on re-fetch; now keyed
by sorted cluster member IDs with stale-dismissal pruning
- Empty-cluster crash in DuplicateCluster.pickWinner
- Undefined tags crash in DuplicateCluster.safeTags
- Debounce timer leak in threshold slider (missing onDestroy cleanup)
- Schedule day-vs-hour granularity mismatch between calendar cell and
sidebar list ("today" in one, "in 1d" in the other)
- Schedule 500-memory hard cap silently truncated; bumped to 2000 + banner
- Schedule DST boundary bug in daysBetween (wall-clock math vs
startOfDay-normalized)
- Dream stage clamp now handles NaN/Infinity/floats
- Dream double-click debounce via `if (dreaming) return`
- Theme setTheme runtime validation; initTheme idempotence (listener +
style-element dedup on repeat calls)
- ContradictionArcs node radius unclamped (trust < 0 or > 1 rendered
invalid sizes); tooltip position clamp (could push off-canvas)
- ContradictionArcs $state closure capture (width/height weren't reactive
in the derived layout block)
- Activation route was MISSING from the repo — audit agent created it
with identity-based event filtering and proper RAF cleanup
- Layout: ThemeToggle was imported but never rendered — now in sidebar
footer; sidebar overflow-y-auto added for the 16-entry nav
**Tests — 549 new, 910 total passing (0 failures)**
ReasoningChain 42 | EvidenceCard 50
DuplicateCluster 64 | DreamStageReplay 19
DreamInsightCard 43 | FSRSCalendar 32
MemoryAuditTrail 45 | AmbientAwareness 60
theme (store) 31 | ContradictionArcs 43
ActivationNetwork 54 | PatternTransfer 31
ImportanceRadar 35 | + existing 361 tests still green
**Gates passed**
- `npm run check`: 0 errors, 0 warnings across 623 files
- `npm test`: 910/910 passing, 22 test files
- `npm run build`: clean adapter-static output
**Layout wiring**
- Nav array expanded 8 → 16 entries (existing 8 + 8 new routes)
- Single-key shortcuts added: R/A/D/C/P/U/X/N (no conflicts with
existing G/M/T/F/E/I/S/,)
- Cmd+K palette search works across all 16
- Mobile nav = top 5 (Graph, Reasoning, Memories, Timeline, Feed)
- AmbientAwarenessStrip mounted as first child of <main>
- ThemeToggle rendered in sidebar footer (was imported-but-unmounted)
- Theme initTheme() + teardown wired into onMount cleanup chain
Net branch delta: 47 files changed, +13,756 insertions, -6 deletions
* chore(release): v2.0.8 "Pulse"
Bundled release: Reasoning Theater wired to the 8-stage deep_reference
cognitive pipeline, Pulse InsightToast, Memory Birth Ritual (v2.3
Terrarium), 7 new dashboard surfaces (/duplicates, /dreams, /schedule,
/importance, /activation, /contradictions, /patterns), 3D graph
brightness system with auto distance-compensation + user slider, and
contradiction-detection + primary-selection hardening in the
cross_reference tool. Intel Mac (x86_64-apple-darwin) also flows through
to the release matrix from PR #43.
Added:
- POST /api/deep_reference — HTTP surface for the 8-stage pipeline
- DeepReferenceCompleted WebSocket event (primary + supporting +
contradicting memory IDs for downstream graph animation)
- /reasoning route, full UI + Cmd+K Ask palette
- 7 new dashboard surfaces exposing the cognitive engine
- Graph brightness slider + localStorage persistence + distance-based
emissive compensation so nodes don't disappear into fog at zoom-out
Fixed:
- Contradiction-detection false positives: adjacent-domain memories no
longer flagged as conflicts (NEGATION_PAIRS wildcards removed,
shared-words floor 2 → 4, topic-sim floor 0.15 → 0.55, STAGE 5
overlap floor 0.15 → 0.4)
- Primary-memory selection: unified composite 0.5 × relevance + 0.2 ×
trust + 0.3 × term_presence with hard topic-term filter, closing the
class of bug where off-topic high-trust memories won queries about
specific subjects
- Graph default-load fallback from sort=recent to sort=connected when
the newest memory is isolated, both backend and client
Changed:
- Reasoning page information hierarchy: chain renders first as hero,
confidence meter + Primary Source citation footer below
- Cargo feature split: embeddings code-only + ort-download | ort-dynamic
backends; defaults preserve identical behavior for existing consumers
- CI release-build now gates PRs too so multi-platform regressions
surface pre-merge
2026-04-23 02:21:11 -05:00
|
|
|
|
addNode(
|
|
|
|
|
|
node: GraphNode,
|
|
|
|
|
|
initialPosition?: THREE.Vector3,
|
|
|
|
|
|
options: { isBirthRitual?: boolean } = {}
|
|
|
|
|
|
): THREE.Vector3 {
|
2026-03-03 14:04:31 -06:00
|
|
|
|
const pos =
|
|
|
|
|
|
initialPosition?.clone() ??
|
|
|
|
|
|
new THREE.Vector3(
|
|
|
|
|
|
(Math.random() - 0.5) * 40,
|
|
|
|
|
|
(Math.random() - 0.5) * 40,
|
|
|
|
|
|
(Math.random() - 0.5) * 40
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
this.positions.set(node.id, pos);
|
|
|
|
|
|
|
|
|
|
|
|
// Create meshes at scale 0
|
|
|
|
|
|
const { mesh, glow, label } = this.createNodeMeshes(node, pos, 0);
|
|
|
|
|
|
mesh.scale.setScalar(0.001); // Avoid zero-scale issues
|
|
|
|
|
|
glow.scale.set(0.001, 0.001, 1);
|
|
|
|
|
|
(glow.material as THREE.SpriteMaterial).opacity = 0;
|
|
|
|
|
|
(label.material as THREE.SpriteMaterial).opacity = 0;
|
|
|
|
|
|
|
v2.0.8 Pulse — Reasoning Theater + Pulse Toast + Terrarium + Intel Mac (#44)
* docs: anti-hedging banner + master state-and-plan + UI roadmap
Project-level CLAUDE.md line 1 is now the anti-hedging banner referencing
the global rule at /Users/entity002/CLAUDE.md. When Sam commits to an
execution path (content cadence, shipping velocity, outreach, launch),
forecasts compute as if the commitment is already met — no discounting by
execution probability.
VESTIGE_STATE_AND_PLAN.md (1135 lines, 14 parts): single-page reference
any AI agent can read to plan the next phase without re-reading the repo.
Covers current state of every crate, the 24 MCP tools, the 19 WebSocket
event types, the 8 IDE integrations, and v2.1 through v3.0 roadmap.
UI_ROADMAP_v2.1_v2.2.md: compiled UI-gap analysis from 4 parallel research
agents (April 19). 10 critical UI gaps + 2026 bleeding-edge patterns +
unclaimed territory (menu bar widget, voice-first, AR/VR, time-scrubber).
* feat(v2.2-pulse): InsightToast + multi-process STORAGE docs
Two independent ship items landing together on the v2.2 branch ahead of
the Tuesday launch — a new UI surface that makes Vestige's cognitive
events visible in real time, and honest documentation of the multi-process
safety story that underpins the Stigmergic Swarm narrative.
**InsightToast** (apps/dashboard/src/lib/components/InsightToast.svelte,
apps/dashboard/src/lib/stores/toast.ts):
The dashboard already had a working WebSocket event stream on
ws://localhost:3927/ws that broadcast every cognitive event (dream
completions, consolidation sweeps, memory promotions/demotions, active-
forgetting suppression and Rac1 cascades, bridge discoveries). None of
that was surfaced to a user looking at anything other than the raw feed
view. InsightToast subscribes to the existing eventFeed derived store,
filters the spammy lifecycle events (Heartbeat, SearchPerformed,
RetentionDecayed, ActivationSpread, ImportanceScored, MemoryCreated),
and translates the narrative events into ephemeral toasts with a
bioluminescent colored accent matching EVENT_TYPE_COLORS.
Design notes:
- Rate-limited ConnectionDiscovered at 1.5s intervals (dreams emit many).
- Max 4 visible toasts, auto-dismiss at 4.5-7s depending on event weight.
- Click or Enter/Space to dismiss early.
- Bottom-right on desktop, top-banner on mobile.
- Reduced-motion honored via prefers-reduced-motion.
- Zero new websocket subscriptions — everything piggybacks on the
existing derived store.
Also added a "Preview Pulse" button to Settings -> Cognitive Operations
that fires a synthetic sequence of four toasts (DreamCompleted,
ConnectionDiscovered, MemorySuppressed, ConsolidationCompleted) so
the animation is demoable without waiting for real cognitive activity.
**Multi-Process Safety section in docs/STORAGE.md**:
Grounds the Stigmergic Swarm story with concrete tables of what the
current WAL + 5s busy_timeout configuration actually supports vs what
remains experimental. Key honest points:
- Shared --data-dir + ONE vestige-mcp + N clients is the shipping
pattern for multi-agent coordination.
- Two vestige-mcp processes writing the same file is experimental —
documented with the lsof + pkill recovery path.
- Roadmap lists the three items that would promote it to "supported":
advisory file lock, retry-with-jitter on SQLITE_BUSY, and a
concurrent-writer load test.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm run build: clean static build, adapter-static succeeds
* feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
* fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
* test(v2.3): full e2e + integration coverage for Pulse + Birth Ritual
Post-ship verification pass — five parallel write-agents produced 229 new
tests across vitest units, vitest integration, and Playwright browser e2e.
Net suite: 361 vitest pass (up from 251, +110) and 9/9 Playwright pass on
back-to-back runs.
**toast.test.ts (NEW, 661 lines, 42 tests)**
Silent-lobotomy batch walk proven (multi-event tick processes ALL, not
just newest, oldest-first ordering preserved). Hover-panic pause/resume
with remaining-ms math. All 9 event type translations asserted, all 11
noise types asserted silent. ConnectionDiscovered 1500ms throttle.
MAX_VISIBLE=4 eviction. clear() tears down all timers. fireDemoSequence
staggers 4 toasts at 800ms intervals. vi.useFakeTimers + vi.mock of
eventFeed; vi.resetModules in beforeEach for module-singleton isolation.
**websocket.test.ts (NEW, 247 lines, 30 tests)**
injectEvent adds to front, respects MAX_EVENTS=200 with FIFO eviction,
triggers eventFeed emissions. All 6 derived stores (isConnected,
heartbeat, memoryCount, avgRetention, suppressedCount, uptimeSeconds)
verified — defaults, post-heartbeat values, clearEvents preserves
lastHeartbeat. 13 formatUptime boundary cases (0/59/60/3599/3600/
86399/86400 seconds + negative / NaN / ±Infinity).
**effects.test.ts (EXTENDED, +501 lines, +21 tests, 51 total)**
createBirthOrb full lifecycle — sprite count (halo + core), cosmic
center via camera.quaternion, gestation phase (position lock, opacity
rise, scale easing, color tint), flight Bezier arc above linear
midpoint at t=0.5, dynamic mid-flight target redirect. onArrive fires
exactly once at frame 139. Post-arrival fade + disposal cleans scene
children. Sanhedrin Shatter: target goes undefined mid-flight →
onArrive NEVER called, implosion spawned, halo blood-red, eventual
cleanup. dispose() cleans active orbs. Multiple simultaneous orbs.
Custom gestation/flight frame opts honored. Zero-alloc invariant
smoke test (6 orbs × 150 frames, no leaks).
**nodes.test.ts (EXTENDED, +197 lines, +10 tests, 42 total)**
addNode({isBirthRitual:true}) hides mesh/glow/label immediately,
stamps birthRitualPending sentinel with correct totalFrames +
targetScale, does NOT enqueue materialization. igniteNode flips
visibility + enqueues materialization. Idempotent — second call
no-op. Non-ritual nodes unaffected. Unknown id is safe no-op.
Position stored in positions map while invisible (force sim still
sees it). removeNode + late igniteNode is safe.
**events.test.ts (EXTENDED, +268 lines, +7 tests, 55 total)**
MemoryCreated → mesh hidden immediately, 2 birth-orb sprites added,
ZERO RingGeometry meshes and ZERO Points particles at spawn. Full
ritual drive → onArrive fires, node visible + materializing, sentinel
cleared. Newton's Cradle: target mesh scale exactly 0.001 * 1.8 right
after arrival. Dual shockwave: exactly 2 Ring meshes added. Re-read
live position on arrival — force-sim motion during ritual → burst
lands at the NEW position. Sanhedrin abort path → rainbow burst,
shockwave, ripple wave are NEVER called (vi.spyOn).
**three-mock.ts (EXTENDED)**
Added Color.setRGB — production Three.js has it, the Sanhedrin-
Shatter path in effects.ts uses it. Two write-agents independently
monkey-patched the mock inline; consolidated as a 5-line mock
addition so tests stay clean.
**e2e/pulse-toast.spec.ts (NEW, 235 lines, 6 Playwright tests)**
Navigate /dashboard/settings → click Preview Pulse → assert first
toast appears within 500ms → assert >= 2 toasts visible at peak.
Click-to-dismiss removes clicked toast (matched by aria-label).
Hover survives >8s past the 5.5s dwell. Keyboard Enter dismisses
focused toast. CSS animation-play-state:paused on .toast-progress-
fill while hovered, running on mouseleave. Screenshots attached to
HTML report. Zero backend dependency (fireDemoSequence is purely
client-side).
**e2e/birth-ritual.spec.ts (NEW, 199 lines, 3 Playwright tests)**
Canvas mounts on /dashboard/graph (gracefully test.fixme if MCP
backend absent). Settings button injection + SPA route to /graph
→ screenshot timeline at t=0/500/1200/2000/2400/3000ms attached
to HTML report. pageerror + console-error listeners catch any
crash (would re-surface FATAL 6 if reintroduced). Three back-to-
back births — no errors, canvas still dispatches clicks.
Run commands:
cd apps/dashboard && npm test # 361/361 pass, ~600ms
cd apps/dashboard && npx playwright test # 9/9 pass, ~25s
Typecheck: 0 errors, 0 warnings. Build: clean adapter-static.
* fix(graph): default /api/graph to newest-memory center, add sort param
memory_timeline PR #37 exposed the same class of bug in the graph
endpoint: the dashboard Graph page (and the /api/graph endpoint it
hits) defaulted to centering on the most-connected memory, ran BFS at
depth 3, and capped the subgraph at 150 nodes. On a mature corpus this
clustered the visualization around a historical hotspot and hid freshly
ingested memories that hadn't accumulated edges yet. User-visible
symptom: TimeSlider on /graph showing "Feb 21 → Mar 1 2026" when the
database actually contains memories through today (Apr 20).
**Backend (`crates/vestige-mcp/src/dashboard/handlers.rs`):**
- `GraphParams` gains `sort: Option<String>` (accepted: "recent" |
"connected", unknown falls back to "recent").
- New internal `GraphSort` enum + case-insensitive `parse()`.
- Extracted `default_center_id(storage, sort)` so handler logic and
tests share the same branching. Recent path picks `get_all_nodes(1,
0)` (ORDER BY created_at DESC). Connected path picks
`get_most_connected_memory`, degrading gracefully to recent if the
DB has no edges yet.
- Default behaviour flipped from "connected" to "recent" — matches
user expectation of "show me my recent stuff".
**Dashboard (`apps/dashboard`):**
- `api.graph()` accepts `sort?: 'recent' | 'connected'` with JSDoc
explaining the rationale.
- `/graph/+page.svelte` passes `sort: 'recent'` when no query or
center_id is active. Query / center_id paths unchanged — they
already carry their own centering intent.
**Tests:** 6 new unit tests in `handlers::tests`:
- `graph_sort_parse_defaults_to_recent` (None, empty, garbage,
"recent", "Recent", "RECENT")
- `graph_sort_parse_accepts_connected_case_insensitive`
- `default_center_id_recent_returns_newest_node` — ingest 3 nodes,
assert newest is picked
- `default_center_id_connected_prefers_hub_over_newest` — wire a hub
node with 3 spokes, then ingest a newer "lonely" node; assert the
hub wins in Connected mode even though it's older
- `default_center_id_connected_falls_back_to_recent_when_no_edges`
— fresh DB with no connections still returns newest, not 404
- `default_center_id_returns_not_found_on_empty_db` — both modes
return 404 cleanly on empty storage
Build + test:
- cargo test -p vestige-mcp --lib handlers:: → 6/6 pass
- cargo test --workspace --lib → 830/830 pass, 0 failed
- cargo clippy -p vestige-core -p vestige-mcp --lib -- -D warnings →
clean
- npm run check → 0 errors, 0 warnings
- npm test → 361/361 pass
Binary already installed at ~/.local/bin/vestige-mcp (copied from
cargo build --release -p vestige-mcp). New Claude Desktop / Code
sessions will pick it up automatically when they respawn their MCP
subprocess. The dashboard HTTP server on port 3927 needs a manual
relaunch from a terminal with the usual pattern:
nohup bash -c 'tail -f /dev/null | \
VESTIGE_DASHBOARD_ENABLED=true ~/.local/bin/vestige-mcp' \
> /tmp/vestige-mcp.log 2>&1 & disown
* feat(v2.4): UI expansion — 8 new surfaces exposing the cognitive engine
Sam asked: "Build EVERY SINGLE MISSING UI PIECE." 10 parallel agents shipped
10 new viewports over the existing Rust backend, then 11 audit agents
line-by-line reviewed each one, extracted pure-logic helpers, fixed ~30
bugs, and shipped 549 new unit tests. Everything wired through the layout
with single-key shortcuts and a live theme toggle.
**Eight new routes**
- `/reasoning` — Reasoning Theater: Cmd+K ask palette → animated 8-stage
deep_reference pipeline + FSRS-trust-scored evidence cards +
contradiction arcs rendered as live SVG between evidence nodes
- `/duplicates` — threshold-driven cluster detector with winner selection,
Merge/Review/Dismiss actions, debounced slider
- `/dreams` — Dream Cinema: trigger dream + scrubbable 5-stage replay
(Replay → Cross-reference → Strengthen → Prune → Transfer) + insight
cards with novelty glow
- `/schedule` — FSRS Review Calendar: 6×7 grid with urgency color
bands (overdue/today/week/future), retention sparkline, expand-day list
- `/importance` — 4-channel radar (Novelty/Arousal/Reward/Attention) with
composite score + top-important list
- `/activation` — live spreading-activation view: search → SVG concentric
rings with decay animation + live-mode event feed
- `/contradictions` — 2D cosmic constellation of conflicting memories,
arcs colored by severity, tooltips with previews
- `/patterns` — cross-project pattern transfer heatmap with category
filters, top-transferred sidebar
**Three layout additions**
- `AmbientAwarenessStrip` — slim top band with retention vitals, at-risk
count, active intentions, recent dream, activity sparkline, dreaming
indicator, Sanhedrin-watch flash. Pure `$derived` over existing stores.
- `ThemeToggle` — dark/light/auto cycle with matchMedia listener,
localStorage persistence, SSR-safe, reduced-motion-aware. Rendered in
sidebar footer next to the connection dot.
- `MemoryAuditTrail` — per-memory Sources panel integrated as a
Content/Audit tab into the existing /memories expansion.
**Pure-logic helper modules extracted (for testability + reuse)**
reasoning-helpers, duplicates-helpers, dream-helpers, schedule-helpers,
audit-trail-helpers, awareness-helpers, contradiction-helpers,
activation-helpers, patterns-helpers, importance-helpers.
**Bugs fixed during audit (not exhaustive)**
- Trust-color inconsistency between EvidenceCard and the page confidence
ring (0.75 boundary split emerald vs amber)
- `new Date('garbage').toLocaleDateString()` returned literal "Invalid Date"
in 3 components — all now return em-dash or raw string
- NaN propagation in `Math.max(0, Math.min(1, NaN))` across clamps
- Off-by-one PRNG in audit-trail seeded mock (seed === UINT32_MAX yielded
rand() === 1.0 → index out of bounds)
- Duplicates dismissals keyed by array index broke on re-fetch; now keyed
by sorted cluster member IDs with stale-dismissal pruning
- Empty-cluster crash in DuplicateCluster.pickWinner
- Undefined tags crash in DuplicateCluster.safeTags
- Debounce timer leak in threshold slider (missing onDestroy cleanup)
- Schedule day-vs-hour granularity mismatch between calendar cell and
sidebar list ("today" in one, "in 1d" in the other)
- Schedule 500-memory hard cap silently truncated; bumped to 2000 + banner
- Schedule DST boundary bug in daysBetween (wall-clock math vs
startOfDay-normalized)
- Dream stage clamp now handles NaN/Infinity/floats
- Dream double-click debounce via `if (dreaming) return`
- Theme setTheme runtime validation; initTheme idempotence (listener +
style-element dedup on repeat calls)
- ContradictionArcs node radius unclamped (trust < 0 or > 1 rendered
invalid sizes); tooltip position clamp (could push off-canvas)
- ContradictionArcs $state closure capture (width/height weren't reactive
in the derived layout block)
- Activation route was MISSING from the repo — audit agent created it
with identity-based event filtering and proper RAF cleanup
- Layout: ThemeToggle was imported but never rendered — now in sidebar
footer; sidebar overflow-y-auto added for the 16-entry nav
**Tests — 549 new, 910 total passing (0 failures)**
ReasoningChain 42 | EvidenceCard 50
DuplicateCluster 64 | DreamStageReplay 19
DreamInsightCard 43 | FSRSCalendar 32
MemoryAuditTrail 45 | AmbientAwareness 60
theme (store) 31 | ContradictionArcs 43
ActivationNetwork 54 | PatternTransfer 31
ImportanceRadar 35 | + existing 361 tests still green
**Gates passed**
- `npm run check`: 0 errors, 0 warnings across 623 files
- `npm test`: 910/910 passing, 22 test files
- `npm run build`: clean adapter-static output
**Layout wiring**
- Nav array expanded 8 → 16 entries (existing 8 + 8 new routes)
- Single-key shortcuts added: R/A/D/C/P/U/X/N (no conflicts with
existing G/M/T/F/E/I/S/,)
- Cmd+K palette search works across all 16
- Mobile nav = top 5 (Graph, Reasoning, Memories, Timeline, Feed)
- AmbientAwarenessStrip mounted as first child of <main>
- ThemeToggle rendered in sidebar footer (was imported-but-unmounted)
- Theme initTheme() + teardown wired into onMount cleanup chain
Net branch delta: 47 files changed, +13,756 insertions, -6 deletions
* chore(release): v2.0.8 "Pulse"
Bundled release: Reasoning Theater wired to the 8-stage deep_reference
cognitive pipeline, Pulse InsightToast, Memory Birth Ritual (v2.3
Terrarium), 7 new dashboard surfaces (/duplicates, /dreams, /schedule,
/importance, /activation, /contradictions, /patterns), 3D graph
brightness system with auto distance-compensation + user slider, and
contradiction-detection + primary-selection hardening in the
cross_reference tool. Intel Mac (x86_64-apple-darwin) also flows through
to the release matrix from PR #43.
Added:
- POST /api/deep_reference — HTTP surface for the 8-stage pipeline
- DeepReferenceCompleted WebSocket event (primary + supporting +
contradicting memory IDs for downstream graph animation)
- /reasoning route, full UI + Cmd+K Ask palette
- 7 new dashboard surfaces exposing the cognitive engine
- Graph brightness slider + localStorage persistence + distance-based
emissive compensation so nodes don't disappear into fog at zoom-out
Fixed:
- Contradiction-detection false positives: adjacent-domain memories no
longer flagged as conflicts (NEGATION_PAIRS wildcards removed,
shared-words floor 2 → 4, topic-sim floor 0.15 → 0.55, STAGE 5
overlap floor 0.15 → 0.4)
- Primary-memory selection: unified composite 0.5 × relevance + 0.2 ×
trust + 0.3 × term_presence with hard topic-term filter, closing the
class of bug where off-topic high-trust memories won queries about
specific subjects
- Graph default-load fallback from sort=recent to sort=connected when
the newest memory is isolated, both backend and client
Changed:
- Reasoning page information hierarchy: chain renders first as hero,
confidence meter + Primary Source citation footer below
- Cargo feature split: embeddings code-only + ort-download | ort-dynamic
backends; defaults preserve identical behavior for existing consumers
- CI release-build now gates PRs too so multi-platform regressions
surface pre-merge
2026-04-23 02:21:11 -05:00
|
|
|
|
if (options.isBirthRitual) {
|
|
|
|
|
|
// v2.3 Birth Ritual: reserve the physics slot but don't show
|
|
|
|
|
|
// anything until the orb docks. Hiding via .visible keeps the
|
|
|
|
|
|
// force simulation + positions map fully active, so getTargetPos()
|
|
|
|
|
|
// can still resolve the live destination for the orb. `igniteNode`
|
|
|
|
|
|
// below flips visibility and kicks off the materialization anim.
|
|
|
|
|
|
mesh.visible = false;
|
|
|
|
|
|
glow.visible = false;
|
|
|
|
|
|
label.visible = false;
|
|
|
|
|
|
mesh.userData.birthRitualPending = {
|
|
|
|
|
|
totalFrames: 30,
|
|
|
|
|
|
targetScale: 0.5 + node.retention * 2,
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.materializingNodes.push({
|
|
|
|
|
|
id: node.id,
|
|
|
|
|
|
frame: 0,
|
|
|
|
|
|
totalFrames: 30,
|
|
|
|
|
|
mesh,
|
|
|
|
|
|
glow,
|
|
|
|
|
|
label,
|
|
|
|
|
|
targetScale: 0.5 + node.retention * 2,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return pos;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* v2.3 Birth Ritual docking. Flip visibility and hand the node over to
|
|
|
|
|
|
* the materialization queue so it springs up via easeOutElastic at the
|
|
|
|
|
|
* exact moment the orb hits. No-op if the node wasn't created with
|
|
|
|
|
|
* `isBirthRitual:true` or was already ignited.
|
|
|
|
|
|
*/
|
|
|
|
|
|
igniteNode(id: string) {
|
|
|
|
|
|
const mesh = this.meshMap.get(id);
|
|
|
|
|
|
const glow = this.glowMap.get(id);
|
|
|
|
|
|
const label = this.labelSprites.get(id);
|
|
|
|
|
|
if (!mesh || !glow || !label) return;
|
|
|
|
|
|
const pending = mesh.userData.birthRitualPending as
|
|
|
|
|
|
| { totalFrames: number; targetScale: number }
|
|
|
|
|
|
| undefined;
|
|
|
|
|
|
if (!pending) return;
|
|
|
|
|
|
mesh.visible = true;
|
|
|
|
|
|
glow.visible = true;
|
|
|
|
|
|
label.visible = true;
|
|
|
|
|
|
delete mesh.userData.birthRitualPending;
|
2026-03-03 14:04:31 -06:00
|
|
|
|
this.materializingNodes.push({
|
v2.0.8 Pulse — Reasoning Theater + Pulse Toast + Terrarium + Intel Mac (#44)
* docs: anti-hedging banner + master state-and-plan + UI roadmap
Project-level CLAUDE.md line 1 is now the anti-hedging banner referencing
the global rule at /Users/entity002/CLAUDE.md. When Sam commits to an
execution path (content cadence, shipping velocity, outreach, launch),
forecasts compute as if the commitment is already met — no discounting by
execution probability.
VESTIGE_STATE_AND_PLAN.md (1135 lines, 14 parts): single-page reference
any AI agent can read to plan the next phase without re-reading the repo.
Covers current state of every crate, the 24 MCP tools, the 19 WebSocket
event types, the 8 IDE integrations, and v2.1 through v3.0 roadmap.
UI_ROADMAP_v2.1_v2.2.md: compiled UI-gap analysis from 4 parallel research
agents (April 19). 10 critical UI gaps + 2026 bleeding-edge patterns +
unclaimed territory (menu bar widget, voice-first, AR/VR, time-scrubber).
* feat(v2.2-pulse): InsightToast + multi-process STORAGE docs
Two independent ship items landing together on the v2.2 branch ahead of
the Tuesday launch — a new UI surface that makes Vestige's cognitive
events visible in real time, and honest documentation of the multi-process
safety story that underpins the Stigmergic Swarm narrative.
**InsightToast** (apps/dashboard/src/lib/components/InsightToast.svelte,
apps/dashboard/src/lib/stores/toast.ts):
The dashboard already had a working WebSocket event stream on
ws://localhost:3927/ws that broadcast every cognitive event (dream
completions, consolidation sweeps, memory promotions/demotions, active-
forgetting suppression and Rac1 cascades, bridge discoveries). None of
that was surfaced to a user looking at anything other than the raw feed
view. InsightToast subscribes to the existing eventFeed derived store,
filters the spammy lifecycle events (Heartbeat, SearchPerformed,
RetentionDecayed, ActivationSpread, ImportanceScored, MemoryCreated),
and translates the narrative events into ephemeral toasts with a
bioluminescent colored accent matching EVENT_TYPE_COLORS.
Design notes:
- Rate-limited ConnectionDiscovered at 1.5s intervals (dreams emit many).
- Max 4 visible toasts, auto-dismiss at 4.5-7s depending on event weight.
- Click or Enter/Space to dismiss early.
- Bottom-right on desktop, top-banner on mobile.
- Reduced-motion honored via prefers-reduced-motion.
- Zero new websocket subscriptions — everything piggybacks on the
existing derived store.
Also added a "Preview Pulse" button to Settings -> Cognitive Operations
that fires a synthetic sequence of four toasts (DreamCompleted,
ConnectionDiscovered, MemorySuppressed, ConsolidationCompleted) so
the animation is demoable without waiting for real cognitive activity.
**Multi-Process Safety section in docs/STORAGE.md**:
Grounds the Stigmergic Swarm story with concrete tables of what the
current WAL + 5s busy_timeout configuration actually supports vs what
remains experimental. Key honest points:
- Shared --data-dir + ONE vestige-mcp + N clients is the shipping
pattern for multi-agent coordination.
- Two vestige-mcp processes writing the same file is experimental —
documented with the lsof + pkill recovery path.
- Roadmap lists the three items that would promote it to "supported":
advisory file lock, retry-with-jitter on SQLITE_BUSY, and a
concurrent-writer load test.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm run build: clean static build, adapter-static succeeds
* feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
* fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
* test(v2.3): full e2e + integration coverage for Pulse + Birth Ritual
Post-ship verification pass — five parallel write-agents produced 229 new
tests across vitest units, vitest integration, and Playwright browser e2e.
Net suite: 361 vitest pass (up from 251, +110) and 9/9 Playwright pass on
back-to-back runs.
**toast.test.ts (NEW, 661 lines, 42 tests)**
Silent-lobotomy batch walk proven (multi-event tick processes ALL, not
just newest, oldest-first ordering preserved). Hover-panic pause/resume
with remaining-ms math. All 9 event type translations asserted, all 11
noise types asserted silent. ConnectionDiscovered 1500ms throttle.
MAX_VISIBLE=4 eviction. clear() tears down all timers. fireDemoSequence
staggers 4 toasts at 800ms intervals. vi.useFakeTimers + vi.mock of
eventFeed; vi.resetModules in beforeEach for module-singleton isolation.
**websocket.test.ts (NEW, 247 lines, 30 tests)**
injectEvent adds to front, respects MAX_EVENTS=200 with FIFO eviction,
triggers eventFeed emissions. All 6 derived stores (isConnected,
heartbeat, memoryCount, avgRetention, suppressedCount, uptimeSeconds)
verified — defaults, post-heartbeat values, clearEvents preserves
lastHeartbeat. 13 formatUptime boundary cases (0/59/60/3599/3600/
86399/86400 seconds + negative / NaN / ±Infinity).
**effects.test.ts (EXTENDED, +501 lines, +21 tests, 51 total)**
createBirthOrb full lifecycle — sprite count (halo + core), cosmic
center via camera.quaternion, gestation phase (position lock, opacity
rise, scale easing, color tint), flight Bezier arc above linear
midpoint at t=0.5, dynamic mid-flight target redirect. onArrive fires
exactly once at frame 139. Post-arrival fade + disposal cleans scene
children. Sanhedrin Shatter: target goes undefined mid-flight →
onArrive NEVER called, implosion spawned, halo blood-red, eventual
cleanup. dispose() cleans active orbs. Multiple simultaneous orbs.
Custom gestation/flight frame opts honored. Zero-alloc invariant
smoke test (6 orbs × 150 frames, no leaks).
**nodes.test.ts (EXTENDED, +197 lines, +10 tests, 42 total)**
addNode({isBirthRitual:true}) hides mesh/glow/label immediately,
stamps birthRitualPending sentinel with correct totalFrames +
targetScale, does NOT enqueue materialization. igniteNode flips
visibility + enqueues materialization. Idempotent — second call
no-op. Non-ritual nodes unaffected. Unknown id is safe no-op.
Position stored in positions map while invisible (force sim still
sees it). removeNode + late igniteNode is safe.
**events.test.ts (EXTENDED, +268 lines, +7 tests, 55 total)**
MemoryCreated → mesh hidden immediately, 2 birth-orb sprites added,
ZERO RingGeometry meshes and ZERO Points particles at spawn. Full
ritual drive → onArrive fires, node visible + materializing, sentinel
cleared. Newton's Cradle: target mesh scale exactly 0.001 * 1.8 right
after arrival. Dual shockwave: exactly 2 Ring meshes added. Re-read
live position on arrival — force-sim motion during ritual → burst
lands at the NEW position. Sanhedrin abort path → rainbow burst,
shockwave, ripple wave are NEVER called (vi.spyOn).
**three-mock.ts (EXTENDED)**
Added Color.setRGB — production Three.js has it, the Sanhedrin-
Shatter path in effects.ts uses it. Two write-agents independently
monkey-patched the mock inline; consolidated as a 5-line mock
addition so tests stay clean.
**e2e/pulse-toast.spec.ts (NEW, 235 lines, 6 Playwright tests)**
Navigate /dashboard/settings → click Preview Pulse → assert first
toast appears within 500ms → assert >= 2 toasts visible at peak.
Click-to-dismiss removes clicked toast (matched by aria-label).
Hover survives >8s past the 5.5s dwell. Keyboard Enter dismisses
focused toast. CSS animation-play-state:paused on .toast-progress-
fill while hovered, running on mouseleave. Screenshots attached to
HTML report. Zero backend dependency (fireDemoSequence is purely
client-side).
**e2e/birth-ritual.spec.ts (NEW, 199 lines, 3 Playwright tests)**
Canvas mounts on /dashboard/graph (gracefully test.fixme if MCP
backend absent). Settings button injection + SPA route to /graph
→ screenshot timeline at t=0/500/1200/2000/2400/3000ms attached
to HTML report. pageerror + console-error listeners catch any
crash (would re-surface FATAL 6 if reintroduced). Three back-to-
back births — no errors, canvas still dispatches clicks.
Run commands:
cd apps/dashboard && npm test # 361/361 pass, ~600ms
cd apps/dashboard && npx playwright test # 9/9 pass, ~25s
Typecheck: 0 errors, 0 warnings. Build: clean adapter-static.
* fix(graph): default /api/graph to newest-memory center, add sort param
memory_timeline PR #37 exposed the same class of bug in the graph
endpoint: the dashboard Graph page (and the /api/graph endpoint it
hits) defaulted to centering on the most-connected memory, ran BFS at
depth 3, and capped the subgraph at 150 nodes. On a mature corpus this
clustered the visualization around a historical hotspot and hid freshly
ingested memories that hadn't accumulated edges yet. User-visible
symptom: TimeSlider on /graph showing "Feb 21 → Mar 1 2026" when the
database actually contains memories through today (Apr 20).
**Backend (`crates/vestige-mcp/src/dashboard/handlers.rs`):**
- `GraphParams` gains `sort: Option<String>` (accepted: "recent" |
"connected", unknown falls back to "recent").
- New internal `GraphSort` enum + case-insensitive `parse()`.
- Extracted `default_center_id(storage, sort)` so handler logic and
tests share the same branching. Recent path picks `get_all_nodes(1,
0)` (ORDER BY created_at DESC). Connected path picks
`get_most_connected_memory`, degrading gracefully to recent if the
DB has no edges yet.
- Default behaviour flipped from "connected" to "recent" — matches
user expectation of "show me my recent stuff".
**Dashboard (`apps/dashboard`):**
- `api.graph()` accepts `sort?: 'recent' | 'connected'` with JSDoc
explaining the rationale.
- `/graph/+page.svelte` passes `sort: 'recent'` when no query or
center_id is active. Query / center_id paths unchanged — they
already carry their own centering intent.
**Tests:** 6 new unit tests in `handlers::tests`:
- `graph_sort_parse_defaults_to_recent` (None, empty, garbage,
"recent", "Recent", "RECENT")
- `graph_sort_parse_accepts_connected_case_insensitive`
- `default_center_id_recent_returns_newest_node` — ingest 3 nodes,
assert newest is picked
- `default_center_id_connected_prefers_hub_over_newest` — wire a hub
node with 3 spokes, then ingest a newer "lonely" node; assert the
hub wins in Connected mode even though it's older
- `default_center_id_connected_falls_back_to_recent_when_no_edges`
— fresh DB with no connections still returns newest, not 404
- `default_center_id_returns_not_found_on_empty_db` — both modes
return 404 cleanly on empty storage
Build + test:
- cargo test -p vestige-mcp --lib handlers:: → 6/6 pass
- cargo test --workspace --lib → 830/830 pass, 0 failed
- cargo clippy -p vestige-core -p vestige-mcp --lib -- -D warnings →
clean
- npm run check → 0 errors, 0 warnings
- npm test → 361/361 pass
Binary already installed at ~/.local/bin/vestige-mcp (copied from
cargo build --release -p vestige-mcp). New Claude Desktop / Code
sessions will pick it up automatically when they respawn their MCP
subprocess. The dashboard HTTP server on port 3927 needs a manual
relaunch from a terminal with the usual pattern:
nohup bash -c 'tail -f /dev/null | \
VESTIGE_DASHBOARD_ENABLED=true ~/.local/bin/vestige-mcp' \
> /tmp/vestige-mcp.log 2>&1 & disown
* feat(v2.4): UI expansion — 8 new surfaces exposing the cognitive engine
Sam asked: "Build EVERY SINGLE MISSING UI PIECE." 10 parallel agents shipped
10 new viewports over the existing Rust backend, then 11 audit agents
line-by-line reviewed each one, extracted pure-logic helpers, fixed ~30
bugs, and shipped 549 new unit tests. Everything wired through the layout
with single-key shortcuts and a live theme toggle.
**Eight new routes**
- `/reasoning` — Reasoning Theater: Cmd+K ask palette → animated 8-stage
deep_reference pipeline + FSRS-trust-scored evidence cards +
contradiction arcs rendered as live SVG between evidence nodes
- `/duplicates` — threshold-driven cluster detector with winner selection,
Merge/Review/Dismiss actions, debounced slider
- `/dreams` — Dream Cinema: trigger dream + scrubbable 5-stage replay
(Replay → Cross-reference → Strengthen → Prune → Transfer) + insight
cards with novelty glow
- `/schedule` — FSRS Review Calendar: 6×7 grid with urgency color
bands (overdue/today/week/future), retention sparkline, expand-day list
- `/importance` — 4-channel radar (Novelty/Arousal/Reward/Attention) with
composite score + top-important list
- `/activation` — live spreading-activation view: search → SVG concentric
rings with decay animation + live-mode event feed
- `/contradictions` — 2D cosmic constellation of conflicting memories,
arcs colored by severity, tooltips with previews
- `/patterns` — cross-project pattern transfer heatmap with category
filters, top-transferred sidebar
**Three layout additions**
- `AmbientAwarenessStrip` — slim top band with retention vitals, at-risk
count, active intentions, recent dream, activity sparkline, dreaming
indicator, Sanhedrin-watch flash. Pure `$derived` over existing stores.
- `ThemeToggle` — dark/light/auto cycle with matchMedia listener,
localStorage persistence, SSR-safe, reduced-motion-aware. Rendered in
sidebar footer next to the connection dot.
- `MemoryAuditTrail` — per-memory Sources panel integrated as a
Content/Audit tab into the existing /memories expansion.
**Pure-logic helper modules extracted (for testability + reuse)**
reasoning-helpers, duplicates-helpers, dream-helpers, schedule-helpers,
audit-trail-helpers, awareness-helpers, contradiction-helpers,
activation-helpers, patterns-helpers, importance-helpers.
**Bugs fixed during audit (not exhaustive)**
- Trust-color inconsistency between EvidenceCard and the page confidence
ring (0.75 boundary split emerald vs amber)
- `new Date('garbage').toLocaleDateString()` returned literal "Invalid Date"
in 3 components — all now return em-dash or raw string
- NaN propagation in `Math.max(0, Math.min(1, NaN))` across clamps
- Off-by-one PRNG in audit-trail seeded mock (seed === UINT32_MAX yielded
rand() === 1.0 → index out of bounds)
- Duplicates dismissals keyed by array index broke on re-fetch; now keyed
by sorted cluster member IDs with stale-dismissal pruning
- Empty-cluster crash in DuplicateCluster.pickWinner
- Undefined tags crash in DuplicateCluster.safeTags
- Debounce timer leak in threshold slider (missing onDestroy cleanup)
- Schedule day-vs-hour granularity mismatch between calendar cell and
sidebar list ("today" in one, "in 1d" in the other)
- Schedule 500-memory hard cap silently truncated; bumped to 2000 + banner
- Schedule DST boundary bug in daysBetween (wall-clock math vs
startOfDay-normalized)
- Dream stage clamp now handles NaN/Infinity/floats
- Dream double-click debounce via `if (dreaming) return`
- Theme setTheme runtime validation; initTheme idempotence (listener +
style-element dedup on repeat calls)
- ContradictionArcs node radius unclamped (trust < 0 or > 1 rendered
invalid sizes); tooltip position clamp (could push off-canvas)
- ContradictionArcs $state closure capture (width/height weren't reactive
in the derived layout block)
- Activation route was MISSING from the repo — audit agent created it
with identity-based event filtering and proper RAF cleanup
- Layout: ThemeToggle was imported but never rendered — now in sidebar
footer; sidebar overflow-y-auto added for the 16-entry nav
**Tests — 549 new, 910 total passing (0 failures)**
ReasoningChain 42 | EvidenceCard 50
DuplicateCluster 64 | DreamStageReplay 19
DreamInsightCard 43 | FSRSCalendar 32
MemoryAuditTrail 45 | AmbientAwareness 60
theme (store) 31 | ContradictionArcs 43
ActivationNetwork 54 | PatternTransfer 31
ImportanceRadar 35 | + existing 361 tests still green
**Gates passed**
- `npm run check`: 0 errors, 0 warnings across 623 files
- `npm test`: 910/910 passing, 22 test files
- `npm run build`: clean adapter-static output
**Layout wiring**
- Nav array expanded 8 → 16 entries (existing 8 + 8 new routes)
- Single-key shortcuts added: R/A/D/C/P/U/X/N (no conflicts with
existing G/M/T/F/E/I/S/,)
- Cmd+K palette search works across all 16
- Mobile nav = top 5 (Graph, Reasoning, Memories, Timeline, Feed)
- AmbientAwarenessStrip mounted as first child of <main>
- ThemeToggle rendered in sidebar footer (was imported-but-unmounted)
- Theme initTheme() + teardown wired into onMount cleanup chain
Net branch delta: 47 files changed, +13,756 insertions, -6 deletions
* chore(release): v2.0.8 "Pulse"
Bundled release: Reasoning Theater wired to the 8-stage deep_reference
cognitive pipeline, Pulse InsightToast, Memory Birth Ritual (v2.3
Terrarium), 7 new dashboard surfaces (/duplicates, /dreams, /schedule,
/importance, /activation, /contradictions, /patterns), 3D graph
brightness system with auto distance-compensation + user slider, and
contradiction-detection + primary-selection hardening in the
cross_reference tool. Intel Mac (x86_64-apple-darwin) also flows through
to the release matrix from PR #43.
Added:
- POST /api/deep_reference — HTTP surface for the 8-stage pipeline
- DeepReferenceCompleted WebSocket event (primary + supporting +
contradicting memory IDs for downstream graph animation)
- /reasoning route, full UI + Cmd+K Ask palette
- 7 new dashboard surfaces exposing the cognitive engine
- Graph brightness slider + localStorage persistence + distance-based
emissive compensation so nodes don't disappear into fog at zoom-out
Fixed:
- Contradiction-detection false positives: adjacent-domain memories no
longer flagged as conflicts (NEGATION_PAIRS wildcards removed,
shared-words floor 2 → 4, topic-sim floor 0.15 → 0.55, STAGE 5
overlap floor 0.15 → 0.4)
- Primary-memory selection: unified composite 0.5 × relevance + 0.2 ×
trust + 0.3 × term_presence with hard topic-term filter, closing the
class of bug where off-topic high-trust memories won queries about
specific subjects
- Graph default-load fallback from sort=recent to sort=connected when
the newest memory is isolated, both backend and client
Changed:
- Reasoning page information hierarchy: chain renders first as hero,
confidence meter + Primary Source citation footer below
- Cargo feature split: embeddings code-only + ort-download | ort-dynamic
backends; defaults preserve identical behavior for existing consumers
- CI release-build now gates PRs too so multi-platform regressions
surface pre-merge
2026-04-23 02:21:11 -05:00
|
|
|
|
id,
|
2026-03-03 14:04:31 -06:00
|
|
|
|
frame: 0,
|
v2.0.8 Pulse — Reasoning Theater + Pulse Toast + Terrarium + Intel Mac (#44)
* docs: anti-hedging banner + master state-and-plan + UI roadmap
Project-level CLAUDE.md line 1 is now the anti-hedging banner referencing
the global rule at /Users/entity002/CLAUDE.md. When Sam commits to an
execution path (content cadence, shipping velocity, outreach, launch),
forecasts compute as if the commitment is already met — no discounting by
execution probability.
VESTIGE_STATE_AND_PLAN.md (1135 lines, 14 parts): single-page reference
any AI agent can read to plan the next phase without re-reading the repo.
Covers current state of every crate, the 24 MCP tools, the 19 WebSocket
event types, the 8 IDE integrations, and v2.1 through v3.0 roadmap.
UI_ROADMAP_v2.1_v2.2.md: compiled UI-gap analysis from 4 parallel research
agents (April 19). 10 critical UI gaps + 2026 bleeding-edge patterns +
unclaimed territory (menu bar widget, voice-first, AR/VR, time-scrubber).
* feat(v2.2-pulse): InsightToast + multi-process STORAGE docs
Two independent ship items landing together on the v2.2 branch ahead of
the Tuesday launch — a new UI surface that makes Vestige's cognitive
events visible in real time, and honest documentation of the multi-process
safety story that underpins the Stigmergic Swarm narrative.
**InsightToast** (apps/dashboard/src/lib/components/InsightToast.svelte,
apps/dashboard/src/lib/stores/toast.ts):
The dashboard already had a working WebSocket event stream on
ws://localhost:3927/ws that broadcast every cognitive event (dream
completions, consolidation sweeps, memory promotions/demotions, active-
forgetting suppression and Rac1 cascades, bridge discoveries). None of
that was surfaced to a user looking at anything other than the raw feed
view. InsightToast subscribes to the existing eventFeed derived store,
filters the spammy lifecycle events (Heartbeat, SearchPerformed,
RetentionDecayed, ActivationSpread, ImportanceScored, MemoryCreated),
and translates the narrative events into ephemeral toasts with a
bioluminescent colored accent matching EVENT_TYPE_COLORS.
Design notes:
- Rate-limited ConnectionDiscovered at 1.5s intervals (dreams emit many).
- Max 4 visible toasts, auto-dismiss at 4.5-7s depending on event weight.
- Click or Enter/Space to dismiss early.
- Bottom-right on desktop, top-banner on mobile.
- Reduced-motion honored via prefers-reduced-motion.
- Zero new websocket subscriptions — everything piggybacks on the
existing derived store.
Also added a "Preview Pulse" button to Settings -> Cognitive Operations
that fires a synthetic sequence of four toasts (DreamCompleted,
ConnectionDiscovered, MemorySuppressed, ConsolidationCompleted) so
the animation is demoable without waiting for real cognitive activity.
**Multi-Process Safety section in docs/STORAGE.md**:
Grounds the Stigmergic Swarm story with concrete tables of what the
current WAL + 5s busy_timeout configuration actually supports vs what
remains experimental. Key honest points:
- Shared --data-dir + ONE vestige-mcp + N clients is the shipping
pattern for multi-agent coordination.
- Two vestige-mcp processes writing the same file is experimental —
documented with the lsof + pkill recovery path.
- Roadmap lists the three items that would promote it to "supported":
advisory file lock, retry-with-jitter on SQLITE_BUSY, and a
concurrent-writer load test.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm run build: clean static build, adapter-static succeeds
* feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
* fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
* test(v2.3): full e2e + integration coverage for Pulse + Birth Ritual
Post-ship verification pass — five parallel write-agents produced 229 new
tests across vitest units, vitest integration, and Playwright browser e2e.
Net suite: 361 vitest pass (up from 251, +110) and 9/9 Playwright pass on
back-to-back runs.
**toast.test.ts (NEW, 661 lines, 42 tests)**
Silent-lobotomy batch walk proven (multi-event tick processes ALL, not
just newest, oldest-first ordering preserved). Hover-panic pause/resume
with remaining-ms math. All 9 event type translations asserted, all 11
noise types asserted silent. ConnectionDiscovered 1500ms throttle.
MAX_VISIBLE=4 eviction. clear() tears down all timers. fireDemoSequence
staggers 4 toasts at 800ms intervals. vi.useFakeTimers + vi.mock of
eventFeed; vi.resetModules in beforeEach for module-singleton isolation.
**websocket.test.ts (NEW, 247 lines, 30 tests)**
injectEvent adds to front, respects MAX_EVENTS=200 with FIFO eviction,
triggers eventFeed emissions. All 6 derived stores (isConnected,
heartbeat, memoryCount, avgRetention, suppressedCount, uptimeSeconds)
verified — defaults, post-heartbeat values, clearEvents preserves
lastHeartbeat. 13 formatUptime boundary cases (0/59/60/3599/3600/
86399/86400 seconds + negative / NaN / ±Infinity).
**effects.test.ts (EXTENDED, +501 lines, +21 tests, 51 total)**
createBirthOrb full lifecycle — sprite count (halo + core), cosmic
center via camera.quaternion, gestation phase (position lock, opacity
rise, scale easing, color tint), flight Bezier arc above linear
midpoint at t=0.5, dynamic mid-flight target redirect. onArrive fires
exactly once at frame 139. Post-arrival fade + disposal cleans scene
children. Sanhedrin Shatter: target goes undefined mid-flight →
onArrive NEVER called, implosion spawned, halo blood-red, eventual
cleanup. dispose() cleans active orbs. Multiple simultaneous orbs.
Custom gestation/flight frame opts honored. Zero-alloc invariant
smoke test (6 orbs × 150 frames, no leaks).
**nodes.test.ts (EXTENDED, +197 lines, +10 tests, 42 total)**
addNode({isBirthRitual:true}) hides mesh/glow/label immediately,
stamps birthRitualPending sentinel with correct totalFrames +
targetScale, does NOT enqueue materialization. igniteNode flips
visibility + enqueues materialization. Idempotent — second call
no-op. Non-ritual nodes unaffected. Unknown id is safe no-op.
Position stored in positions map while invisible (force sim still
sees it). removeNode + late igniteNode is safe.
**events.test.ts (EXTENDED, +268 lines, +7 tests, 55 total)**
MemoryCreated → mesh hidden immediately, 2 birth-orb sprites added,
ZERO RingGeometry meshes and ZERO Points particles at spawn. Full
ritual drive → onArrive fires, node visible + materializing, sentinel
cleared. Newton's Cradle: target mesh scale exactly 0.001 * 1.8 right
after arrival. Dual shockwave: exactly 2 Ring meshes added. Re-read
live position on arrival — force-sim motion during ritual → burst
lands at the NEW position. Sanhedrin abort path → rainbow burst,
shockwave, ripple wave are NEVER called (vi.spyOn).
**three-mock.ts (EXTENDED)**
Added Color.setRGB — production Three.js has it, the Sanhedrin-
Shatter path in effects.ts uses it. Two write-agents independently
monkey-patched the mock inline; consolidated as a 5-line mock
addition so tests stay clean.
**e2e/pulse-toast.spec.ts (NEW, 235 lines, 6 Playwright tests)**
Navigate /dashboard/settings → click Preview Pulse → assert first
toast appears within 500ms → assert >= 2 toasts visible at peak.
Click-to-dismiss removes clicked toast (matched by aria-label).
Hover survives >8s past the 5.5s dwell. Keyboard Enter dismisses
focused toast. CSS animation-play-state:paused on .toast-progress-
fill while hovered, running on mouseleave. Screenshots attached to
HTML report. Zero backend dependency (fireDemoSequence is purely
client-side).
**e2e/birth-ritual.spec.ts (NEW, 199 lines, 3 Playwright tests)**
Canvas mounts on /dashboard/graph (gracefully test.fixme if MCP
backend absent). Settings button injection + SPA route to /graph
→ screenshot timeline at t=0/500/1200/2000/2400/3000ms attached
to HTML report. pageerror + console-error listeners catch any
crash (would re-surface FATAL 6 if reintroduced). Three back-to-
back births — no errors, canvas still dispatches clicks.
Run commands:
cd apps/dashboard && npm test # 361/361 pass, ~600ms
cd apps/dashboard && npx playwright test # 9/9 pass, ~25s
Typecheck: 0 errors, 0 warnings. Build: clean adapter-static.
* fix(graph): default /api/graph to newest-memory center, add sort param
memory_timeline PR #37 exposed the same class of bug in the graph
endpoint: the dashboard Graph page (and the /api/graph endpoint it
hits) defaulted to centering on the most-connected memory, ran BFS at
depth 3, and capped the subgraph at 150 nodes. On a mature corpus this
clustered the visualization around a historical hotspot and hid freshly
ingested memories that hadn't accumulated edges yet. User-visible
symptom: TimeSlider on /graph showing "Feb 21 → Mar 1 2026" when the
database actually contains memories through today (Apr 20).
**Backend (`crates/vestige-mcp/src/dashboard/handlers.rs`):**
- `GraphParams` gains `sort: Option<String>` (accepted: "recent" |
"connected", unknown falls back to "recent").
- New internal `GraphSort` enum + case-insensitive `parse()`.
- Extracted `default_center_id(storage, sort)` so handler logic and
tests share the same branching. Recent path picks `get_all_nodes(1,
0)` (ORDER BY created_at DESC). Connected path picks
`get_most_connected_memory`, degrading gracefully to recent if the
DB has no edges yet.
- Default behaviour flipped from "connected" to "recent" — matches
user expectation of "show me my recent stuff".
**Dashboard (`apps/dashboard`):**
- `api.graph()` accepts `sort?: 'recent' | 'connected'` with JSDoc
explaining the rationale.
- `/graph/+page.svelte` passes `sort: 'recent'` when no query or
center_id is active. Query / center_id paths unchanged — they
already carry their own centering intent.
**Tests:** 6 new unit tests in `handlers::tests`:
- `graph_sort_parse_defaults_to_recent` (None, empty, garbage,
"recent", "Recent", "RECENT")
- `graph_sort_parse_accepts_connected_case_insensitive`
- `default_center_id_recent_returns_newest_node` — ingest 3 nodes,
assert newest is picked
- `default_center_id_connected_prefers_hub_over_newest` — wire a hub
node with 3 spokes, then ingest a newer "lonely" node; assert the
hub wins in Connected mode even though it's older
- `default_center_id_connected_falls_back_to_recent_when_no_edges`
— fresh DB with no connections still returns newest, not 404
- `default_center_id_returns_not_found_on_empty_db` — both modes
return 404 cleanly on empty storage
Build + test:
- cargo test -p vestige-mcp --lib handlers:: → 6/6 pass
- cargo test --workspace --lib → 830/830 pass, 0 failed
- cargo clippy -p vestige-core -p vestige-mcp --lib -- -D warnings →
clean
- npm run check → 0 errors, 0 warnings
- npm test → 361/361 pass
Binary already installed at ~/.local/bin/vestige-mcp (copied from
cargo build --release -p vestige-mcp). New Claude Desktop / Code
sessions will pick it up automatically when they respawn their MCP
subprocess. The dashboard HTTP server on port 3927 needs a manual
relaunch from a terminal with the usual pattern:
nohup bash -c 'tail -f /dev/null | \
VESTIGE_DASHBOARD_ENABLED=true ~/.local/bin/vestige-mcp' \
> /tmp/vestige-mcp.log 2>&1 & disown
* feat(v2.4): UI expansion — 8 new surfaces exposing the cognitive engine
Sam asked: "Build EVERY SINGLE MISSING UI PIECE." 10 parallel agents shipped
10 new viewports over the existing Rust backend, then 11 audit agents
line-by-line reviewed each one, extracted pure-logic helpers, fixed ~30
bugs, and shipped 549 new unit tests. Everything wired through the layout
with single-key shortcuts and a live theme toggle.
**Eight new routes**
- `/reasoning` — Reasoning Theater: Cmd+K ask palette → animated 8-stage
deep_reference pipeline + FSRS-trust-scored evidence cards +
contradiction arcs rendered as live SVG between evidence nodes
- `/duplicates` — threshold-driven cluster detector with winner selection,
Merge/Review/Dismiss actions, debounced slider
- `/dreams` — Dream Cinema: trigger dream + scrubbable 5-stage replay
(Replay → Cross-reference → Strengthen → Prune → Transfer) + insight
cards with novelty glow
- `/schedule` — FSRS Review Calendar: 6×7 grid with urgency color
bands (overdue/today/week/future), retention sparkline, expand-day list
- `/importance` — 4-channel radar (Novelty/Arousal/Reward/Attention) with
composite score + top-important list
- `/activation` — live spreading-activation view: search → SVG concentric
rings with decay animation + live-mode event feed
- `/contradictions` — 2D cosmic constellation of conflicting memories,
arcs colored by severity, tooltips with previews
- `/patterns` — cross-project pattern transfer heatmap with category
filters, top-transferred sidebar
**Three layout additions**
- `AmbientAwarenessStrip` — slim top band with retention vitals, at-risk
count, active intentions, recent dream, activity sparkline, dreaming
indicator, Sanhedrin-watch flash. Pure `$derived` over existing stores.
- `ThemeToggle` — dark/light/auto cycle with matchMedia listener,
localStorage persistence, SSR-safe, reduced-motion-aware. Rendered in
sidebar footer next to the connection dot.
- `MemoryAuditTrail` — per-memory Sources panel integrated as a
Content/Audit tab into the existing /memories expansion.
**Pure-logic helper modules extracted (for testability + reuse)**
reasoning-helpers, duplicates-helpers, dream-helpers, schedule-helpers,
audit-trail-helpers, awareness-helpers, contradiction-helpers,
activation-helpers, patterns-helpers, importance-helpers.
**Bugs fixed during audit (not exhaustive)**
- Trust-color inconsistency between EvidenceCard and the page confidence
ring (0.75 boundary split emerald vs amber)
- `new Date('garbage').toLocaleDateString()` returned literal "Invalid Date"
in 3 components — all now return em-dash or raw string
- NaN propagation in `Math.max(0, Math.min(1, NaN))` across clamps
- Off-by-one PRNG in audit-trail seeded mock (seed === UINT32_MAX yielded
rand() === 1.0 → index out of bounds)
- Duplicates dismissals keyed by array index broke on re-fetch; now keyed
by sorted cluster member IDs with stale-dismissal pruning
- Empty-cluster crash in DuplicateCluster.pickWinner
- Undefined tags crash in DuplicateCluster.safeTags
- Debounce timer leak in threshold slider (missing onDestroy cleanup)
- Schedule day-vs-hour granularity mismatch between calendar cell and
sidebar list ("today" in one, "in 1d" in the other)
- Schedule 500-memory hard cap silently truncated; bumped to 2000 + banner
- Schedule DST boundary bug in daysBetween (wall-clock math vs
startOfDay-normalized)
- Dream stage clamp now handles NaN/Infinity/floats
- Dream double-click debounce via `if (dreaming) return`
- Theme setTheme runtime validation; initTheme idempotence (listener +
style-element dedup on repeat calls)
- ContradictionArcs node radius unclamped (trust < 0 or > 1 rendered
invalid sizes); tooltip position clamp (could push off-canvas)
- ContradictionArcs $state closure capture (width/height weren't reactive
in the derived layout block)
- Activation route was MISSING from the repo — audit agent created it
with identity-based event filtering and proper RAF cleanup
- Layout: ThemeToggle was imported but never rendered — now in sidebar
footer; sidebar overflow-y-auto added for the 16-entry nav
**Tests — 549 new, 910 total passing (0 failures)**
ReasoningChain 42 | EvidenceCard 50
DuplicateCluster 64 | DreamStageReplay 19
DreamInsightCard 43 | FSRSCalendar 32
MemoryAuditTrail 45 | AmbientAwareness 60
theme (store) 31 | ContradictionArcs 43
ActivationNetwork 54 | PatternTransfer 31
ImportanceRadar 35 | + existing 361 tests still green
**Gates passed**
- `npm run check`: 0 errors, 0 warnings across 623 files
- `npm test`: 910/910 passing, 22 test files
- `npm run build`: clean adapter-static output
**Layout wiring**
- Nav array expanded 8 → 16 entries (existing 8 + 8 new routes)
- Single-key shortcuts added: R/A/D/C/P/U/X/N (no conflicts with
existing G/M/T/F/E/I/S/,)
- Cmd+K palette search works across all 16
- Mobile nav = top 5 (Graph, Reasoning, Memories, Timeline, Feed)
- AmbientAwarenessStrip mounted as first child of <main>
- ThemeToggle rendered in sidebar footer (was imported-but-unmounted)
- Theme initTheme() + teardown wired into onMount cleanup chain
Net branch delta: 47 files changed, +13,756 insertions, -6 deletions
* chore(release): v2.0.8 "Pulse"
Bundled release: Reasoning Theater wired to the 8-stage deep_reference
cognitive pipeline, Pulse InsightToast, Memory Birth Ritual (v2.3
Terrarium), 7 new dashboard surfaces (/duplicates, /dreams, /schedule,
/importance, /activation, /contradictions, /patterns), 3D graph
brightness system with auto distance-compensation + user slider, and
contradiction-detection + primary-selection hardening in the
cross_reference tool. Intel Mac (x86_64-apple-darwin) also flows through
to the release matrix from PR #43.
Added:
- POST /api/deep_reference — HTTP surface for the 8-stage pipeline
- DeepReferenceCompleted WebSocket event (primary + supporting +
contradicting memory IDs for downstream graph animation)
- /reasoning route, full UI + Cmd+K Ask palette
- 7 new dashboard surfaces exposing the cognitive engine
- Graph brightness slider + localStorage persistence + distance-based
emissive compensation so nodes don't disappear into fog at zoom-out
Fixed:
- Contradiction-detection false positives: adjacent-domain memories no
longer flagged as conflicts (NEGATION_PAIRS wildcards removed,
shared-words floor 2 → 4, topic-sim floor 0.15 → 0.55, STAGE 5
overlap floor 0.15 → 0.4)
- Primary-memory selection: unified composite 0.5 × relevance + 0.2 ×
trust + 0.3 × term_presence with hard topic-term filter, closing the
class of bug where off-topic high-trust memories won queries about
specific subjects
- Graph default-load fallback from sort=recent to sort=connected when
the newest memory is isolated, both backend and client
Changed:
- Reasoning page information hierarchy: chain renders first as hero,
confidence meter + Primary Source citation footer below
- Cargo feature split: embeddings code-only + ort-download | ort-dynamic
backends; defaults preserve identical behavior for existing consumers
- CI release-build now gates PRs too so multi-platform regressions
surface pre-merge
2026-04-23 02:21:11 -05:00
|
|
|
|
totalFrames: pending.totalFrames,
|
2026-03-03 14:04:31 -06:00
|
|
|
|
mesh,
|
|
|
|
|
|
glow,
|
|
|
|
|
|
label,
|
v2.0.8 Pulse — Reasoning Theater + Pulse Toast + Terrarium + Intel Mac (#44)
* docs: anti-hedging banner + master state-and-plan + UI roadmap
Project-level CLAUDE.md line 1 is now the anti-hedging banner referencing
the global rule at /Users/entity002/CLAUDE.md. When Sam commits to an
execution path (content cadence, shipping velocity, outreach, launch),
forecasts compute as if the commitment is already met — no discounting by
execution probability.
VESTIGE_STATE_AND_PLAN.md (1135 lines, 14 parts): single-page reference
any AI agent can read to plan the next phase without re-reading the repo.
Covers current state of every crate, the 24 MCP tools, the 19 WebSocket
event types, the 8 IDE integrations, and v2.1 through v3.0 roadmap.
UI_ROADMAP_v2.1_v2.2.md: compiled UI-gap analysis from 4 parallel research
agents (April 19). 10 critical UI gaps + 2026 bleeding-edge patterns +
unclaimed territory (menu bar widget, voice-first, AR/VR, time-scrubber).
* feat(v2.2-pulse): InsightToast + multi-process STORAGE docs
Two independent ship items landing together on the v2.2 branch ahead of
the Tuesday launch — a new UI surface that makes Vestige's cognitive
events visible in real time, and honest documentation of the multi-process
safety story that underpins the Stigmergic Swarm narrative.
**InsightToast** (apps/dashboard/src/lib/components/InsightToast.svelte,
apps/dashboard/src/lib/stores/toast.ts):
The dashboard already had a working WebSocket event stream on
ws://localhost:3927/ws that broadcast every cognitive event (dream
completions, consolidation sweeps, memory promotions/demotions, active-
forgetting suppression and Rac1 cascades, bridge discoveries). None of
that was surfaced to a user looking at anything other than the raw feed
view. InsightToast subscribes to the existing eventFeed derived store,
filters the spammy lifecycle events (Heartbeat, SearchPerformed,
RetentionDecayed, ActivationSpread, ImportanceScored, MemoryCreated),
and translates the narrative events into ephemeral toasts with a
bioluminescent colored accent matching EVENT_TYPE_COLORS.
Design notes:
- Rate-limited ConnectionDiscovered at 1.5s intervals (dreams emit many).
- Max 4 visible toasts, auto-dismiss at 4.5-7s depending on event weight.
- Click or Enter/Space to dismiss early.
- Bottom-right on desktop, top-banner on mobile.
- Reduced-motion honored via prefers-reduced-motion.
- Zero new websocket subscriptions — everything piggybacks on the
existing derived store.
Also added a "Preview Pulse" button to Settings -> Cognitive Operations
that fires a synthetic sequence of four toasts (DreamCompleted,
ConnectionDiscovered, MemorySuppressed, ConsolidationCompleted) so
the animation is demoable without waiting for real cognitive activity.
**Multi-Process Safety section in docs/STORAGE.md**:
Grounds the Stigmergic Swarm story with concrete tables of what the
current WAL + 5s busy_timeout configuration actually supports vs what
remains experimental. Key honest points:
- Shared --data-dir + ONE vestige-mcp + N clients is the shipping
pattern for multi-agent coordination.
- Two vestige-mcp processes writing the same file is experimental —
documented with the lsof + pkill recovery path.
- Roadmap lists the three items that would promote it to "supported":
advisory file lock, retry-with-jitter on SQLITE_BUSY, and a
concurrent-writer load test.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm run build: clean static build, adapter-static succeeds
* feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
* fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
* test(v2.3): full e2e + integration coverage for Pulse + Birth Ritual
Post-ship verification pass — five parallel write-agents produced 229 new
tests across vitest units, vitest integration, and Playwright browser e2e.
Net suite: 361 vitest pass (up from 251, +110) and 9/9 Playwright pass on
back-to-back runs.
**toast.test.ts (NEW, 661 lines, 42 tests)**
Silent-lobotomy batch walk proven (multi-event tick processes ALL, not
just newest, oldest-first ordering preserved). Hover-panic pause/resume
with remaining-ms math. All 9 event type translations asserted, all 11
noise types asserted silent. ConnectionDiscovered 1500ms throttle.
MAX_VISIBLE=4 eviction. clear() tears down all timers. fireDemoSequence
staggers 4 toasts at 800ms intervals. vi.useFakeTimers + vi.mock of
eventFeed; vi.resetModules in beforeEach for module-singleton isolation.
**websocket.test.ts (NEW, 247 lines, 30 tests)**
injectEvent adds to front, respects MAX_EVENTS=200 with FIFO eviction,
triggers eventFeed emissions. All 6 derived stores (isConnected,
heartbeat, memoryCount, avgRetention, suppressedCount, uptimeSeconds)
verified — defaults, post-heartbeat values, clearEvents preserves
lastHeartbeat. 13 formatUptime boundary cases (0/59/60/3599/3600/
86399/86400 seconds + negative / NaN / ±Infinity).
**effects.test.ts (EXTENDED, +501 lines, +21 tests, 51 total)**
createBirthOrb full lifecycle — sprite count (halo + core), cosmic
center via camera.quaternion, gestation phase (position lock, opacity
rise, scale easing, color tint), flight Bezier arc above linear
midpoint at t=0.5, dynamic mid-flight target redirect. onArrive fires
exactly once at frame 139. Post-arrival fade + disposal cleans scene
children. Sanhedrin Shatter: target goes undefined mid-flight →
onArrive NEVER called, implosion spawned, halo blood-red, eventual
cleanup. dispose() cleans active orbs. Multiple simultaneous orbs.
Custom gestation/flight frame opts honored. Zero-alloc invariant
smoke test (6 orbs × 150 frames, no leaks).
**nodes.test.ts (EXTENDED, +197 lines, +10 tests, 42 total)**
addNode({isBirthRitual:true}) hides mesh/glow/label immediately,
stamps birthRitualPending sentinel with correct totalFrames +
targetScale, does NOT enqueue materialization. igniteNode flips
visibility + enqueues materialization. Idempotent — second call
no-op. Non-ritual nodes unaffected. Unknown id is safe no-op.
Position stored in positions map while invisible (force sim still
sees it). removeNode + late igniteNode is safe.
**events.test.ts (EXTENDED, +268 lines, +7 tests, 55 total)**
MemoryCreated → mesh hidden immediately, 2 birth-orb sprites added,
ZERO RingGeometry meshes and ZERO Points particles at spawn. Full
ritual drive → onArrive fires, node visible + materializing, sentinel
cleared. Newton's Cradle: target mesh scale exactly 0.001 * 1.8 right
after arrival. Dual shockwave: exactly 2 Ring meshes added. Re-read
live position on arrival — force-sim motion during ritual → burst
lands at the NEW position. Sanhedrin abort path → rainbow burst,
shockwave, ripple wave are NEVER called (vi.spyOn).
**three-mock.ts (EXTENDED)**
Added Color.setRGB — production Three.js has it, the Sanhedrin-
Shatter path in effects.ts uses it. Two write-agents independently
monkey-patched the mock inline; consolidated as a 5-line mock
addition so tests stay clean.
**e2e/pulse-toast.spec.ts (NEW, 235 lines, 6 Playwright tests)**
Navigate /dashboard/settings → click Preview Pulse → assert first
toast appears within 500ms → assert >= 2 toasts visible at peak.
Click-to-dismiss removes clicked toast (matched by aria-label).
Hover survives >8s past the 5.5s dwell. Keyboard Enter dismisses
focused toast. CSS animation-play-state:paused on .toast-progress-
fill while hovered, running on mouseleave. Screenshots attached to
HTML report. Zero backend dependency (fireDemoSequence is purely
client-side).
**e2e/birth-ritual.spec.ts (NEW, 199 lines, 3 Playwright tests)**
Canvas mounts on /dashboard/graph (gracefully test.fixme if MCP
backend absent). Settings button injection + SPA route to /graph
→ screenshot timeline at t=0/500/1200/2000/2400/3000ms attached
to HTML report. pageerror + console-error listeners catch any
crash (would re-surface FATAL 6 if reintroduced). Three back-to-
back births — no errors, canvas still dispatches clicks.
Run commands:
cd apps/dashboard && npm test # 361/361 pass, ~600ms
cd apps/dashboard && npx playwright test # 9/9 pass, ~25s
Typecheck: 0 errors, 0 warnings. Build: clean adapter-static.
* fix(graph): default /api/graph to newest-memory center, add sort param
memory_timeline PR #37 exposed the same class of bug in the graph
endpoint: the dashboard Graph page (and the /api/graph endpoint it
hits) defaulted to centering on the most-connected memory, ran BFS at
depth 3, and capped the subgraph at 150 nodes. On a mature corpus this
clustered the visualization around a historical hotspot and hid freshly
ingested memories that hadn't accumulated edges yet. User-visible
symptom: TimeSlider on /graph showing "Feb 21 → Mar 1 2026" when the
database actually contains memories through today (Apr 20).
**Backend (`crates/vestige-mcp/src/dashboard/handlers.rs`):**
- `GraphParams` gains `sort: Option<String>` (accepted: "recent" |
"connected", unknown falls back to "recent").
- New internal `GraphSort` enum + case-insensitive `parse()`.
- Extracted `default_center_id(storage, sort)` so handler logic and
tests share the same branching. Recent path picks `get_all_nodes(1,
0)` (ORDER BY created_at DESC). Connected path picks
`get_most_connected_memory`, degrading gracefully to recent if the
DB has no edges yet.
- Default behaviour flipped from "connected" to "recent" — matches
user expectation of "show me my recent stuff".
**Dashboard (`apps/dashboard`):**
- `api.graph()` accepts `sort?: 'recent' | 'connected'` with JSDoc
explaining the rationale.
- `/graph/+page.svelte` passes `sort: 'recent'` when no query or
center_id is active. Query / center_id paths unchanged — they
already carry their own centering intent.
**Tests:** 6 new unit tests in `handlers::tests`:
- `graph_sort_parse_defaults_to_recent` (None, empty, garbage,
"recent", "Recent", "RECENT")
- `graph_sort_parse_accepts_connected_case_insensitive`
- `default_center_id_recent_returns_newest_node` — ingest 3 nodes,
assert newest is picked
- `default_center_id_connected_prefers_hub_over_newest` — wire a hub
node with 3 spokes, then ingest a newer "lonely" node; assert the
hub wins in Connected mode even though it's older
- `default_center_id_connected_falls_back_to_recent_when_no_edges`
— fresh DB with no connections still returns newest, not 404
- `default_center_id_returns_not_found_on_empty_db` — both modes
return 404 cleanly on empty storage
Build + test:
- cargo test -p vestige-mcp --lib handlers:: → 6/6 pass
- cargo test --workspace --lib → 830/830 pass, 0 failed
- cargo clippy -p vestige-core -p vestige-mcp --lib -- -D warnings →
clean
- npm run check → 0 errors, 0 warnings
- npm test → 361/361 pass
Binary already installed at ~/.local/bin/vestige-mcp (copied from
cargo build --release -p vestige-mcp). New Claude Desktop / Code
sessions will pick it up automatically when they respawn their MCP
subprocess. The dashboard HTTP server on port 3927 needs a manual
relaunch from a terminal with the usual pattern:
nohup bash -c 'tail -f /dev/null | \
VESTIGE_DASHBOARD_ENABLED=true ~/.local/bin/vestige-mcp' \
> /tmp/vestige-mcp.log 2>&1 & disown
* feat(v2.4): UI expansion — 8 new surfaces exposing the cognitive engine
Sam asked: "Build EVERY SINGLE MISSING UI PIECE." 10 parallel agents shipped
10 new viewports over the existing Rust backend, then 11 audit agents
line-by-line reviewed each one, extracted pure-logic helpers, fixed ~30
bugs, and shipped 549 new unit tests. Everything wired through the layout
with single-key shortcuts and a live theme toggle.
**Eight new routes**
- `/reasoning` — Reasoning Theater: Cmd+K ask palette → animated 8-stage
deep_reference pipeline + FSRS-trust-scored evidence cards +
contradiction arcs rendered as live SVG between evidence nodes
- `/duplicates` — threshold-driven cluster detector with winner selection,
Merge/Review/Dismiss actions, debounced slider
- `/dreams` — Dream Cinema: trigger dream + scrubbable 5-stage replay
(Replay → Cross-reference → Strengthen → Prune → Transfer) + insight
cards with novelty glow
- `/schedule` — FSRS Review Calendar: 6×7 grid with urgency color
bands (overdue/today/week/future), retention sparkline, expand-day list
- `/importance` — 4-channel radar (Novelty/Arousal/Reward/Attention) with
composite score + top-important list
- `/activation` — live spreading-activation view: search → SVG concentric
rings with decay animation + live-mode event feed
- `/contradictions` — 2D cosmic constellation of conflicting memories,
arcs colored by severity, tooltips with previews
- `/patterns` — cross-project pattern transfer heatmap with category
filters, top-transferred sidebar
**Three layout additions**
- `AmbientAwarenessStrip` — slim top band with retention vitals, at-risk
count, active intentions, recent dream, activity sparkline, dreaming
indicator, Sanhedrin-watch flash. Pure `$derived` over existing stores.
- `ThemeToggle` — dark/light/auto cycle with matchMedia listener,
localStorage persistence, SSR-safe, reduced-motion-aware. Rendered in
sidebar footer next to the connection dot.
- `MemoryAuditTrail` — per-memory Sources panel integrated as a
Content/Audit tab into the existing /memories expansion.
**Pure-logic helper modules extracted (for testability + reuse)**
reasoning-helpers, duplicates-helpers, dream-helpers, schedule-helpers,
audit-trail-helpers, awareness-helpers, contradiction-helpers,
activation-helpers, patterns-helpers, importance-helpers.
**Bugs fixed during audit (not exhaustive)**
- Trust-color inconsistency between EvidenceCard and the page confidence
ring (0.75 boundary split emerald vs amber)
- `new Date('garbage').toLocaleDateString()` returned literal "Invalid Date"
in 3 components — all now return em-dash or raw string
- NaN propagation in `Math.max(0, Math.min(1, NaN))` across clamps
- Off-by-one PRNG in audit-trail seeded mock (seed === UINT32_MAX yielded
rand() === 1.0 → index out of bounds)
- Duplicates dismissals keyed by array index broke on re-fetch; now keyed
by sorted cluster member IDs with stale-dismissal pruning
- Empty-cluster crash in DuplicateCluster.pickWinner
- Undefined tags crash in DuplicateCluster.safeTags
- Debounce timer leak in threshold slider (missing onDestroy cleanup)
- Schedule day-vs-hour granularity mismatch between calendar cell and
sidebar list ("today" in one, "in 1d" in the other)
- Schedule 500-memory hard cap silently truncated; bumped to 2000 + banner
- Schedule DST boundary bug in daysBetween (wall-clock math vs
startOfDay-normalized)
- Dream stage clamp now handles NaN/Infinity/floats
- Dream double-click debounce via `if (dreaming) return`
- Theme setTheme runtime validation; initTheme idempotence (listener +
style-element dedup on repeat calls)
- ContradictionArcs node radius unclamped (trust < 0 or > 1 rendered
invalid sizes); tooltip position clamp (could push off-canvas)
- ContradictionArcs $state closure capture (width/height weren't reactive
in the derived layout block)
- Activation route was MISSING from the repo — audit agent created it
with identity-based event filtering and proper RAF cleanup
- Layout: ThemeToggle was imported but never rendered — now in sidebar
footer; sidebar overflow-y-auto added for the 16-entry nav
**Tests — 549 new, 910 total passing (0 failures)**
ReasoningChain 42 | EvidenceCard 50
DuplicateCluster 64 | DreamStageReplay 19
DreamInsightCard 43 | FSRSCalendar 32
MemoryAuditTrail 45 | AmbientAwareness 60
theme (store) 31 | ContradictionArcs 43
ActivationNetwork 54 | PatternTransfer 31
ImportanceRadar 35 | + existing 361 tests still green
**Gates passed**
- `npm run check`: 0 errors, 0 warnings across 623 files
- `npm test`: 910/910 passing, 22 test files
- `npm run build`: clean adapter-static output
**Layout wiring**
- Nav array expanded 8 → 16 entries (existing 8 + 8 new routes)
- Single-key shortcuts added: R/A/D/C/P/U/X/N (no conflicts with
existing G/M/T/F/E/I/S/,)
- Cmd+K palette search works across all 16
- Mobile nav = top 5 (Graph, Reasoning, Memories, Timeline, Feed)
- AmbientAwarenessStrip mounted as first child of <main>
- ThemeToggle rendered in sidebar footer (was imported-but-unmounted)
- Theme initTheme() + teardown wired into onMount cleanup chain
Net branch delta: 47 files changed, +13,756 insertions, -6 deletions
* chore(release): v2.0.8 "Pulse"
Bundled release: Reasoning Theater wired to the 8-stage deep_reference
cognitive pipeline, Pulse InsightToast, Memory Birth Ritual (v2.3
Terrarium), 7 new dashboard surfaces (/duplicates, /dreams, /schedule,
/importance, /activation, /contradictions, /patterns), 3D graph
brightness system with auto distance-compensation + user slider, and
contradiction-detection + primary-selection hardening in the
cross_reference tool. Intel Mac (x86_64-apple-darwin) also flows through
to the release matrix from PR #43.
Added:
- POST /api/deep_reference — HTTP surface for the 8-stage pipeline
- DeepReferenceCompleted WebSocket event (primary + supporting +
contradicting memory IDs for downstream graph animation)
- /reasoning route, full UI + Cmd+K Ask palette
- 7 new dashboard surfaces exposing the cognitive engine
- Graph brightness slider + localStorage persistence + distance-based
emissive compensation so nodes don't disappear into fog at zoom-out
Fixed:
- Contradiction-detection false positives: adjacent-domain memories no
longer flagged as conflicts (NEGATION_PAIRS wildcards removed,
shared-words floor 2 → 4, topic-sim floor 0.15 → 0.55, STAGE 5
overlap floor 0.15 → 0.4)
- Primary-memory selection: unified composite 0.5 × relevance + 0.2 ×
trust + 0.3 × term_presence with hard topic-term filter, closing the
class of bug where off-topic high-trust memories won queries about
specific subjects
- Graph default-load fallback from sort=recent to sort=connected when
the newest memory is isolated, both backend and client
Changed:
- Reasoning page information hierarchy: chain renders first as hero,
confidence meter + Primary Source citation footer below
- Cargo feature split: embeddings code-only + ort-download | ort-dynamic
backends; defaults preserve identical behavior for existing consumers
- CI release-build now gates PRs too so multi-platform regressions
surface pre-merge
2026-04-23 02:21:11 -05:00
|
|
|
|
targetScale: pending.targetScale,
|
2026-03-03 14:04:31 -06:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
removeNode(id: string) {
|
|
|
|
|
|
const mesh = this.meshMap.get(id);
|
|
|
|
|
|
const glow = this.glowMap.get(id);
|
|
|
|
|
|
const label = this.labelSprites.get(id);
|
|
|
|
|
|
if (!mesh || !glow || !label) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Cancel any active materialization
|
|
|
|
|
|
this.materializingNodes = this.materializingNodes.filter((m) => m.id !== id);
|
|
|
|
|
|
|
|
|
|
|
|
this.dissolvingNodes.push({
|
|
|
|
|
|
id,
|
|
|
|
|
|
frame: 0,
|
|
|
|
|
|
totalFrames: 60,
|
|
|
|
|
|
mesh,
|
|
|
|
|
|
glow,
|
|
|
|
|
|
label,
|
|
|
|
|
|
originalScale: mesh.scale.x,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
growNode(id: string, newRetention: number) {
|
|
|
|
|
|
const mesh = this.meshMap.get(id);
|
|
|
|
|
|
if (!mesh) return;
|
|
|
|
|
|
|
|
|
|
|
|
const currentScale = mesh.scale.x;
|
|
|
|
|
|
const targetScale = 0.5 + newRetention * 2;
|
|
|
|
|
|
mesh.userData.retention = newRetention;
|
|
|
|
|
|
|
|
|
|
|
|
this.growingNodes.push({
|
|
|
|
|
|
id,
|
|
|
|
|
|
frame: 0,
|
|
|
|
|
|
totalFrames: 30,
|
|
|
|
|
|
startScale: currentScale,
|
|
|
|
|
|
targetScale,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-19 21:52:14 -05:00
|
|
|
|
/// Render a label as a dark rounded "pill" with dim slate text.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// The scene runs an UnrealBloomPass with threshold 0.2, so any bright
|
|
|
|
|
|
/// canvas pixels get smeared into a halo. Previously the labels were
|
|
|
|
|
|
/// near-white (#e2e8f0) text on a transparent background, which bloomed
|
2026-05-06 02:22:24 -05:00
|
|
|
|
/// into unreadable white blobs (issue filed 2026-04-19). The fix:
|
2026-04-19 21:52:14 -05:00
|
|
|
|
///
|
|
|
|
|
|
/// 1. A ~85%-opaque dark pill under the text so the background is
|
|
|
|
|
|
/// well below the bloom threshold, stopping the halo before it
|
|
|
|
|
|
/// spreads past the label bounds.
|
|
|
|
|
|
/// 2. Mid-luminance slate text (#94a3b8 by default) — still legible
|
|
|
|
|
|
/// but dim enough that bloom only adds a soft glow, not a blast.
|
|
|
|
|
|
/// 3. Smaller font (22px) and tighter sprite scale (9×1.2) so labels
|
|
|
|
|
|
/// don't visually compete with the node spheres they annotate.
|
2026-03-01 21:24:10 -06:00
|
|
|
|
private createTextSprite(text: string, color: string): THREE.Sprite {
|
|
|
|
|
|
const canvas = document.createElement('canvas');
|
2026-04-09 17:06:24 -05:00
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
|
|
if (!ctx) {
|
|
|
|
|
|
const tex = new THREE.Texture();
|
|
|
|
|
|
return new THREE.Sprite(new THREE.SpriteMaterial({ map: tex, transparent: true, opacity: 0 }));
|
|
|
|
|
|
}
|
2026-03-01 21:24:10 -06:00
|
|
|
|
canvas.width = 512;
|
|
|
|
|
|
canvas.height = 64;
|
|
|
|
|
|
|
|
|
|
|
|
const label = text.length > 40 ? text.slice(0, 37) + '...' : text;
|
|
|
|
|
|
|
|
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
2026-04-19 21:52:14 -05:00
|
|
|
|
|
|
|
|
|
|
// Measure the label so the backing pill hugs the text instead of
|
|
|
|
|
|
// spanning the full canvas width (which would leave a giant empty
|
|
|
|
|
|
// dark bar on short labels like "fact" or "note").
|
|
|
|
|
|
ctx.font = '600 22px -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif';
|
|
|
|
|
|
const metrics = ctx.measureText(label);
|
|
|
|
|
|
const textWidth = metrics.width;
|
|
|
|
|
|
const padX = 14;
|
|
|
|
|
|
const padY = 9;
|
|
|
|
|
|
const pillW = Math.min(textWidth + padX * 2, canvas.width - 4);
|
|
|
|
|
|
const pillH = 40;
|
|
|
|
|
|
const pillX = (canvas.width - pillW) / 2;
|
|
|
|
|
|
const pillY = (canvas.height - pillH) / 2;
|
|
|
|
|
|
const radius = pillH / 2;
|
|
|
|
|
|
|
|
|
|
|
|
// Dark glass pill — low enough luminance that UnrealBloomPass at
|
|
|
|
|
|
// threshold 0.2 does not amplify its pixels.
|
|
|
|
|
|
ctx.fillStyle = 'rgba(10, 16, 28, 0.82)';
|
|
|
|
|
|
ctx.beginPath();
|
|
|
|
|
|
ctx.moveTo(pillX + radius, pillY);
|
|
|
|
|
|
ctx.lineTo(pillX + pillW - radius, pillY);
|
|
|
|
|
|
ctx.quadraticCurveTo(pillX + pillW, pillY, pillX + pillW, pillY + radius);
|
|
|
|
|
|
ctx.lineTo(pillX + pillW, pillY + pillH - radius);
|
|
|
|
|
|
ctx.quadraticCurveTo(
|
|
|
|
|
|
pillX + pillW,
|
|
|
|
|
|
pillY + pillH,
|
|
|
|
|
|
pillX + pillW - radius,
|
|
|
|
|
|
pillY + pillH
|
|
|
|
|
|
);
|
|
|
|
|
|
ctx.lineTo(pillX + radius, pillY + pillH);
|
|
|
|
|
|
ctx.quadraticCurveTo(pillX, pillY + pillH, pillX, pillY + pillH - radius);
|
|
|
|
|
|
ctx.lineTo(pillX, pillY + radius);
|
|
|
|
|
|
ctx.quadraticCurveTo(pillX, pillY, pillX + radius, pillY);
|
|
|
|
|
|
ctx.closePath();
|
|
|
|
|
|
ctx.fill();
|
|
|
|
|
|
|
|
|
|
|
|
// Hairline stroke for definition at small camera distances.
|
|
|
|
|
|
ctx.strokeStyle = 'rgba(148, 163, 184, 0.18)';
|
|
|
|
|
|
ctx.lineWidth = 1;
|
|
|
|
|
|
ctx.stroke();
|
|
|
|
|
|
|
2026-03-01 21:24:10 -06:00
|
|
|
|
ctx.textAlign = 'center';
|
|
|
|
|
|
ctx.textBaseline = 'middle';
|
|
|
|
|
|
ctx.fillStyle = color;
|
2026-04-19 21:52:14 -05:00
|
|
|
|
ctx.fillText(label, canvas.width / 2, canvas.height / 2 + 1);
|
2026-03-01 21:24:10 -06:00
|
|
|
|
|
|
|
|
|
|
const texture = new THREE.CanvasTexture(canvas);
|
|
|
|
|
|
texture.needsUpdate = true;
|
|
|
|
|
|
|
|
|
|
|
|
const mat = new THREE.SpriteMaterial({
|
|
|
|
|
|
map: texture,
|
|
|
|
|
|
transparent: true,
|
|
|
|
|
|
opacity: 0,
|
|
|
|
|
|
depthTest: false,
|
|
|
|
|
|
sizeAttenuation: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const sprite = new THREE.Sprite(mat);
|
2026-04-19 21:52:14 -05:00
|
|
|
|
sprite.scale.set(9, 1.2, 1);
|
2026-03-01 21:24:10 -06:00
|
|
|
|
return sprite;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updatePositions() {
|
|
|
|
|
|
this.group.children.forEach((child) => {
|
|
|
|
|
|
if (child.userData.nodeId) {
|
|
|
|
|
|
const pos = this.positions.get(child.userData.nodeId);
|
|
|
|
|
|
if (!pos) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (child.userData.isGlow) {
|
|
|
|
|
|
child.position.copy(pos);
|
|
|
|
|
|
} else if (child.userData.isLabel) {
|
|
|
|
|
|
child.position.copy(pos);
|
|
|
|
|
|
child.position.y += child.userData.offset;
|
|
|
|
|
|
} else if (child instanceof THREE.Mesh) {
|
|
|
|
|
|
child.position.copy(pos);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
v2.0.8 Pulse — Reasoning Theater + Pulse Toast + Terrarium + Intel Mac (#44)
* docs: anti-hedging banner + master state-and-plan + UI roadmap
Project-level CLAUDE.md line 1 is now the anti-hedging banner referencing
the global rule at /Users/entity002/CLAUDE.md. When Sam commits to an
execution path (content cadence, shipping velocity, outreach, launch),
forecasts compute as if the commitment is already met — no discounting by
execution probability.
VESTIGE_STATE_AND_PLAN.md (1135 lines, 14 parts): single-page reference
any AI agent can read to plan the next phase without re-reading the repo.
Covers current state of every crate, the 24 MCP tools, the 19 WebSocket
event types, the 8 IDE integrations, and v2.1 through v3.0 roadmap.
UI_ROADMAP_v2.1_v2.2.md: compiled UI-gap analysis from 4 parallel research
agents (April 19). 10 critical UI gaps + 2026 bleeding-edge patterns +
unclaimed territory (menu bar widget, voice-first, AR/VR, time-scrubber).
* feat(v2.2-pulse): InsightToast + multi-process STORAGE docs
Two independent ship items landing together on the v2.2 branch ahead of
the Tuesday launch — a new UI surface that makes Vestige's cognitive
events visible in real time, and honest documentation of the multi-process
safety story that underpins the Stigmergic Swarm narrative.
**InsightToast** (apps/dashboard/src/lib/components/InsightToast.svelte,
apps/dashboard/src/lib/stores/toast.ts):
The dashboard already had a working WebSocket event stream on
ws://localhost:3927/ws that broadcast every cognitive event (dream
completions, consolidation sweeps, memory promotions/demotions, active-
forgetting suppression and Rac1 cascades, bridge discoveries). None of
that was surfaced to a user looking at anything other than the raw feed
view. InsightToast subscribes to the existing eventFeed derived store,
filters the spammy lifecycle events (Heartbeat, SearchPerformed,
RetentionDecayed, ActivationSpread, ImportanceScored, MemoryCreated),
and translates the narrative events into ephemeral toasts with a
bioluminescent colored accent matching EVENT_TYPE_COLORS.
Design notes:
- Rate-limited ConnectionDiscovered at 1.5s intervals (dreams emit many).
- Max 4 visible toasts, auto-dismiss at 4.5-7s depending on event weight.
- Click or Enter/Space to dismiss early.
- Bottom-right on desktop, top-banner on mobile.
- Reduced-motion honored via prefers-reduced-motion.
- Zero new websocket subscriptions — everything piggybacks on the
existing derived store.
Also added a "Preview Pulse" button to Settings -> Cognitive Operations
that fires a synthetic sequence of four toasts (DreamCompleted,
ConnectionDiscovered, MemorySuppressed, ConsolidationCompleted) so
the animation is demoable without waiting for real cognitive activity.
**Multi-Process Safety section in docs/STORAGE.md**:
Grounds the Stigmergic Swarm story with concrete tables of what the
current WAL + 5s busy_timeout configuration actually supports vs what
remains experimental. Key honest points:
- Shared --data-dir + ONE vestige-mcp + N clients is the shipping
pattern for multi-agent coordination.
- Two vestige-mcp processes writing the same file is experimental —
documented with the lsof + pkill recovery path.
- Roadmap lists the three items that would promote it to "supported":
advisory file lock, retry-with-jitter on SQLITE_BUSY, and a
concurrent-writer load test.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm run build: clean static build, adapter-static succeeds
* feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
* fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
* test(v2.3): full e2e + integration coverage for Pulse + Birth Ritual
Post-ship verification pass — five parallel write-agents produced 229 new
tests across vitest units, vitest integration, and Playwright browser e2e.
Net suite: 361 vitest pass (up from 251, +110) and 9/9 Playwright pass on
back-to-back runs.
**toast.test.ts (NEW, 661 lines, 42 tests)**
Silent-lobotomy batch walk proven (multi-event tick processes ALL, not
just newest, oldest-first ordering preserved). Hover-panic pause/resume
with remaining-ms math. All 9 event type translations asserted, all 11
noise types asserted silent. ConnectionDiscovered 1500ms throttle.
MAX_VISIBLE=4 eviction. clear() tears down all timers. fireDemoSequence
staggers 4 toasts at 800ms intervals. vi.useFakeTimers + vi.mock of
eventFeed; vi.resetModules in beforeEach for module-singleton isolation.
**websocket.test.ts (NEW, 247 lines, 30 tests)**
injectEvent adds to front, respects MAX_EVENTS=200 with FIFO eviction,
triggers eventFeed emissions. All 6 derived stores (isConnected,
heartbeat, memoryCount, avgRetention, suppressedCount, uptimeSeconds)
verified — defaults, post-heartbeat values, clearEvents preserves
lastHeartbeat. 13 formatUptime boundary cases (0/59/60/3599/3600/
86399/86400 seconds + negative / NaN / ±Infinity).
**effects.test.ts (EXTENDED, +501 lines, +21 tests, 51 total)**
createBirthOrb full lifecycle — sprite count (halo + core), cosmic
center via camera.quaternion, gestation phase (position lock, opacity
rise, scale easing, color tint), flight Bezier arc above linear
midpoint at t=0.5, dynamic mid-flight target redirect. onArrive fires
exactly once at frame 139. Post-arrival fade + disposal cleans scene
children. Sanhedrin Shatter: target goes undefined mid-flight →
onArrive NEVER called, implosion spawned, halo blood-red, eventual
cleanup. dispose() cleans active orbs. Multiple simultaneous orbs.
Custom gestation/flight frame opts honored. Zero-alloc invariant
smoke test (6 orbs × 150 frames, no leaks).
**nodes.test.ts (EXTENDED, +197 lines, +10 tests, 42 total)**
addNode({isBirthRitual:true}) hides mesh/glow/label immediately,
stamps birthRitualPending sentinel with correct totalFrames +
targetScale, does NOT enqueue materialization. igniteNode flips
visibility + enqueues materialization. Idempotent — second call
no-op. Non-ritual nodes unaffected. Unknown id is safe no-op.
Position stored in positions map while invisible (force sim still
sees it). removeNode + late igniteNode is safe.
**events.test.ts (EXTENDED, +268 lines, +7 tests, 55 total)**
MemoryCreated → mesh hidden immediately, 2 birth-orb sprites added,
ZERO RingGeometry meshes and ZERO Points particles at spawn. Full
ritual drive → onArrive fires, node visible + materializing, sentinel
cleared. Newton's Cradle: target mesh scale exactly 0.001 * 1.8 right
after arrival. Dual shockwave: exactly 2 Ring meshes added. Re-read
live position on arrival — force-sim motion during ritual → burst
lands at the NEW position. Sanhedrin abort path → rainbow burst,
shockwave, ripple wave are NEVER called (vi.spyOn).
**three-mock.ts (EXTENDED)**
Added Color.setRGB — production Three.js has it, the Sanhedrin-
Shatter path in effects.ts uses it. Two write-agents independently
monkey-patched the mock inline; consolidated as a 5-line mock
addition so tests stay clean.
**e2e/pulse-toast.spec.ts (NEW, 235 lines, 6 Playwright tests)**
Navigate /dashboard/settings → click Preview Pulse → assert first
toast appears within 500ms → assert >= 2 toasts visible at peak.
Click-to-dismiss removes clicked toast (matched by aria-label).
Hover survives >8s past the 5.5s dwell. Keyboard Enter dismisses
focused toast. CSS animation-play-state:paused on .toast-progress-
fill while hovered, running on mouseleave. Screenshots attached to
HTML report. Zero backend dependency (fireDemoSequence is purely
client-side).
**e2e/birth-ritual.spec.ts (NEW, 199 lines, 3 Playwright tests)**
Canvas mounts on /dashboard/graph (gracefully test.fixme if MCP
backend absent). Settings button injection + SPA route to /graph
→ screenshot timeline at t=0/500/1200/2000/2400/3000ms attached
to HTML report. pageerror + console-error listeners catch any
crash (would re-surface FATAL 6 if reintroduced). Three back-to-
back births — no errors, canvas still dispatches clicks.
Run commands:
cd apps/dashboard && npm test # 361/361 pass, ~600ms
cd apps/dashboard && npx playwright test # 9/9 pass, ~25s
Typecheck: 0 errors, 0 warnings. Build: clean adapter-static.
* fix(graph): default /api/graph to newest-memory center, add sort param
memory_timeline PR #37 exposed the same class of bug in the graph
endpoint: the dashboard Graph page (and the /api/graph endpoint it
hits) defaulted to centering on the most-connected memory, ran BFS at
depth 3, and capped the subgraph at 150 nodes. On a mature corpus this
clustered the visualization around a historical hotspot and hid freshly
ingested memories that hadn't accumulated edges yet. User-visible
symptom: TimeSlider on /graph showing "Feb 21 → Mar 1 2026" when the
database actually contains memories through today (Apr 20).
**Backend (`crates/vestige-mcp/src/dashboard/handlers.rs`):**
- `GraphParams` gains `sort: Option<String>` (accepted: "recent" |
"connected", unknown falls back to "recent").
- New internal `GraphSort` enum + case-insensitive `parse()`.
- Extracted `default_center_id(storage, sort)` so handler logic and
tests share the same branching. Recent path picks `get_all_nodes(1,
0)` (ORDER BY created_at DESC). Connected path picks
`get_most_connected_memory`, degrading gracefully to recent if the
DB has no edges yet.
- Default behaviour flipped from "connected" to "recent" — matches
user expectation of "show me my recent stuff".
**Dashboard (`apps/dashboard`):**
- `api.graph()` accepts `sort?: 'recent' | 'connected'` with JSDoc
explaining the rationale.
- `/graph/+page.svelte` passes `sort: 'recent'` when no query or
center_id is active. Query / center_id paths unchanged — they
already carry their own centering intent.
**Tests:** 6 new unit tests in `handlers::tests`:
- `graph_sort_parse_defaults_to_recent` (None, empty, garbage,
"recent", "Recent", "RECENT")
- `graph_sort_parse_accepts_connected_case_insensitive`
- `default_center_id_recent_returns_newest_node` — ingest 3 nodes,
assert newest is picked
- `default_center_id_connected_prefers_hub_over_newest` — wire a hub
node with 3 spokes, then ingest a newer "lonely" node; assert the
hub wins in Connected mode even though it's older
- `default_center_id_connected_falls_back_to_recent_when_no_edges`
— fresh DB with no connections still returns newest, not 404
- `default_center_id_returns_not_found_on_empty_db` — both modes
return 404 cleanly on empty storage
Build + test:
- cargo test -p vestige-mcp --lib handlers:: → 6/6 pass
- cargo test --workspace --lib → 830/830 pass, 0 failed
- cargo clippy -p vestige-core -p vestige-mcp --lib -- -D warnings →
clean
- npm run check → 0 errors, 0 warnings
- npm test → 361/361 pass
Binary already installed at ~/.local/bin/vestige-mcp (copied from
cargo build --release -p vestige-mcp). New Claude Desktop / Code
sessions will pick it up automatically when they respawn their MCP
subprocess. The dashboard HTTP server on port 3927 needs a manual
relaunch from a terminal with the usual pattern:
nohup bash -c 'tail -f /dev/null | \
VESTIGE_DASHBOARD_ENABLED=true ~/.local/bin/vestige-mcp' \
> /tmp/vestige-mcp.log 2>&1 & disown
* feat(v2.4): UI expansion — 8 new surfaces exposing the cognitive engine
Sam asked: "Build EVERY SINGLE MISSING UI PIECE." 10 parallel agents shipped
10 new viewports over the existing Rust backend, then 11 audit agents
line-by-line reviewed each one, extracted pure-logic helpers, fixed ~30
bugs, and shipped 549 new unit tests. Everything wired through the layout
with single-key shortcuts and a live theme toggle.
**Eight new routes**
- `/reasoning` — Reasoning Theater: Cmd+K ask palette → animated 8-stage
deep_reference pipeline + FSRS-trust-scored evidence cards +
contradiction arcs rendered as live SVG between evidence nodes
- `/duplicates` — threshold-driven cluster detector with winner selection,
Merge/Review/Dismiss actions, debounced slider
- `/dreams` — Dream Cinema: trigger dream + scrubbable 5-stage replay
(Replay → Cross-reference → Strengthen → Prune → Transfer) + insight
cards with novelty glow
- `/schedule` — FSRS Review Calendar: 6×7 grid with urgency color
bands (overdue/today/week/future), retention sparkline, expand-day list
- `/importance` — 4-channel radar (Novelty/Arousal/Reward/Attention) with
composite score + top-important list
- `/activation` — live spreading-activation view: search → SVG concentric
rings with decay animation + live-mode event feed
- `/contradictions` — 2D cosmic constellation of conflicting memories,
arcs colored by severity, tooltips with previews
- `/patterns` — cross-project pattern transfer heatmap with category
filters, top-transferred sidebar
**Three layout additions**
- `AmbientAwarenessStrip` — slim top band with retention vitals, at-risk
count, active intentions, recent dream, activity sparkline, dreaming
indicator, Sanhedrin-watch flash. Pure `$derived` over existing stores.
- `ThemeToggle` — dark/light/auto cycle with matchMedia listener,
localStorage persistence, SSR-safe, reduced-motion-aware. Rendered in
sidebar footer next to the connection dot.
- `MemoryAuditTrail` — per-memory Sources panel integrated as a
Content/Audit tab into the existing /memories expansion.
**Pure-logic helper modules extracted (for testability + reuse)**
reasoning-helpers, duplicates-helpers, dream-helpers, schedule-helpers,
audit-trail-helpers, awareness-helpers, contradiction-helpers,
activation-helpers, patterns-helpers, importance-helpers.
**Bugs fixed during audit (not exhaustive)**
- Trust-color inconsistency between EvidenceCard and the page confidence
ring (0.75 boundary split emerald vs amber)
- `new Date('garbage').toLocaleDateString()` returned literal "Invalid Date"
in 3 components — all now return em-dash or raw string
- NaN propagation in `Math.max(0, Math.min(1, NaN))` across clamps
- Off-by-one PRNG in audit-trail seeded mock (seed === UINT32_MAX yielded
rand() === 1.0 → index out of bounds)
- Duplicates dismissals keyed by array index broke on re-fetch; now keyed
by sorted cluster member IDs with stale-dismissal pruning
- Empty-cluster crash in DuplicateCluster.pickWinner
- Undefined tags crash in DuplicateCluster.safeTags
- Debounce timer leak in threshold slider (missing onDestroy cleanup)
- Schedule day-vs-hour granularity mismatch between calendar cell and
sidebar list ("today" in one, "in 1d" in the other)
- Schedule 500-memory hard cap silently truncated; bumped to 2000 + banner
- Schedule DST boundary bug in daysBetween (wall-clock math vs
startOfDay-normalized)
- Dream stage clamp now handles NaN/Infinity/floats
- Dream double-click debounce via `if (dreaming) return`
- Theme setTheme runtime validation; initTheme idempotence (listener +
style-element dedup on repeat calls)
- ContradictionArcs node radius unclamped (trust < 0 or > 1 rendered
invalid sizes); tooltip position clamp (could push off-canvas)
- ContradictionArcs $state closure capture (width/height weren't reactive
in the derived layout block)
- Activation route was MISSING from the repo — audit agent created it
with identity-based event filtering and proper RAF cleanup
- Layout: ThemeToggle was imported but never rendered — now in sidebar
footer; sidebar overflow-y-auto added for the 16-entry nav
**Tests — 549 new, 910 total passing (0 failures)**
ReasoningChain 42 | EvidenceCard 50
DuplicateCluster 64 | DreamStageReplay 19
DreamInsightCard 43 | FSRSCalendar 32
MemoryAuditTrail 45 | AmbientAwareness 60
theme (store) 31 | ContradictionArcs 43
ActivationNetwork 54 | PatternTransfer 31
ImportanceRadar 35 | + existing 361 tests still green
**Gates passed**
- `npm run check`: 0 errors, 0 warnings across 623 files
- `npm test`: 910/910 passing, 22 test files
- `npm run build`: clean adapter-static output
**Layout wiring**
- Nav array expanded 8 → 16 entries (existing 8 + 8 new routes)
- Single-key shortcuts added: R/A/D/C/P/U/X/N (no conflicts with
existing G/M/T/F/E/I/S/,)
- Cmd+K palette search works across all 16
- Mobile nav = top 5 (Graph, Reasoning, Memories, Timeline, Feed)
- AmbientAwarenessStrip mounted as first child of <main>
- ThemeToggle rendered in sidebar footer (was imported-but-unmounted)
- Theme initTheme() + teardown wired into onMount cleanup chain
Net branch delta: 47 files changed, +13,756 insertions, -6 deletions
* chore(release): v2.0.8 "Pulse"
Bundled release: Reasoning Theater wired to the 8-stage deep_reference
cognitive pipeline, Pulse InsightToast, Memory Birth Ritual (v2.3
Terrarium), 7 new dashboard surfaces (/duplicates, /dreams, /schedule,
/importance, /activation, /contradictions, /patterns), 3D graph
brightness system with auto distance-compensation + user slider, and
contradiction-detection + primary-selection hardening in the
cross_reference tool. Intel Mac (x86_64-apple-darwin) also flows through
to the release matrix from PR #43.
Added:
- POST /api/deep_reference — HTTP surface for the 8-stage pipeline
- DeepReferenceCompleted WebSocket event (primary + supporting +
contradicting memory IDs for downstream graph animation)
- /reasoning route, full UI + Cmd+K Ask palette
- 7 new dashboard surfaces exposing the cognitive engine
- Graph brightness slider + localStorage persistence + distance-based
emissive compensation so nodes don't disappear into fog at zoom-out
Fixed:
- Contradiction-detection false positives: adjacent-domain memories no
longer flagged as conflicts (NEGATION_PAIRS wildcards removed,
shared-words floor 2 → 4, topic-sim floor 0.15 → 0.55, STAGE 5
overlap floor 0.15 → 0.4)
- Primary-memory selection: unified composite 0.5 × relevance + 0.2 ×
trust + 0.3 × term_presence with hard topic-term filter, closing the
class of bug where off-topic high-trust memories won queries about
specific subjects
- Graph default-load fallback from sort=recent to sort=connected when
the newest memory is isolated, both backend and client
Changed:
- Reasoning page information hierarchy: chain renders first as hero,
confidence meter + Primary Source citation footer below
- Cargo feature split: embeddings code-only + ort-download | ort-dynamic
backends; defaults preserve identical behavior for existing consumers
- CI release-build now gates PRs too so multi-platform regressions
surface pre-merge
2026-04-23 02:21:11 -05:00
|
|
|
|
animate(
|
|
|
|
|
|
time: number,
|
|
|
|
|
|
nodes: GraphNode[],
|
|
|
|
|
|
camera: THREE.PerspectiveCamera,
|
|
|
|
|
|
brightness: number = 1.0
|
|
|
|
|
|
) {
|
2026-03-03 14:04:31 -06:00
|
|
|
|
// Materialization animations — elastic scale-up from 0
|
|
|
|
|
|
for (let i = this.materializingNodes.length - 1; i >= 0; i--) {
|
|
|
|
|
|
const mn = this.materializingNodes[i];
|
|
|
|
|
|
mn.frame++;
|
|
|
|
|
|
const t = Math.min(mn.frame / mn.totalFrames, 1);
|
|
|
|
|
|
const scale = easeOutElastic(t);
|
|
|
|
|
|
|
|
|
|
|
|
// Mesh scales up with elastic spring
|
|
|
|
|
|
mn.mesh.scale.setScalar(Math.max(0.001, scale));
|
|
|
|
|
|
|
|
|
|
|
|
// Glow fades in between frames 5-10
|
|
|
|
|
|
if (mn.frame >= 5) {
|
|
|
|
|
|
const glowT = Math.min((mn.frame - 5) / 5, 1);
|
|
|
|
|
|
const glowMat = mn.glow.material as THREE.SpriteMaterial;
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
glowMat.opacity = glowT * 0.4;
|
|
|
|
|
|
const glowSize = mn.targetScale * 6 * scale;
|
2026-03-03 14:04:31 -06:00
|
|
|
|
mn.glow.scale.set(glowSize, glowSize, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Label fades in after frame 40 (10 frames after mesh finishes)
|
|
|
|
|
|
if (mn.frame >= 40) {
|
|
|
|
|
|
const labelT = Math.min((mn.frame - 40) / 20, 1);
|
|
|
|
|
|
(mn.label.material as THREE.SpriteMaterial).opacity = labelT * 0.9;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (mn.frame >= 60) {
|
|
|
|
|
|
this.materializingNodes.splice(i, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Dissolution animations — easeInBack shrink
|
|
|
|
|
|
for (let i = this.dissolvingNodes.length - 1; i >= 0; i--) {
|
|
|
|
|
|
const dn = this.dissolvingNodes[i];
|
|
|
|
|
|
dn.frame++;
|
|
|
|
|
|
const t = Math.min(dn.frame / dn.totalFrames, 1);
|
|
|
|
|
|
const shrink = 1 - easeInBack(t);
|
|
|
|
|
|
const scale = Math.max(0.001, dn.originalScale * shrink);
|
|
|
|
|
|
|
|
|
|
|
|
dn.mesh.scale.setScalar(scale);
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
const glowScale = scale * 6;
|
2026-03-03 14:04:31 -06:00
|
|
|
|
dn.glow.scale.set(glowScale, glowScale, 1);
|
|
|
|
|
|
|
|
|
|
|
|
// Fade opacity
|
|
|
|
|
|
const mat = dn.mesh.material as THREE.MeshStandardMaterial;
|
|
|
|
|
|
mat.opacity *= 0.97;
|
|
|
|
|
|
(dn.glow.material as THREE.SpriteMaterial).opacity *= 0.95;
|
|
|
|
|
|
(dn.label.material as THREE.SpriteMaterial).opacity *= 0.93;
|
|
|
|
|
|
|
|
|
|
|
|
if (dn.frame >= dn.totalFrames) {
|
|
|
|
|
|
// Clean up
|
|
|
|
|
|
this.group.remove(dn.mesh);
|
|
|
|
|
|
this.group.remove(dn.glow);
|
|
|
|
|
|
this.group.remove(dn.label);
|
|
|
|
|
|
dn.mesh.geometry.dispose();
|
|
|
|
|
|
(dn.mesh.material as THREE.Material).dispose();
|
|
|
|
|
|
(dn.glow.material as THREE.SpriteMaterial).map?.dispose();
|
|
|
|
|
|
(dn.glow.material as THREE.Material).dispose();
|
|
|
|
|
|
(dn.label.material as THREE.SpriteMaterial).map?.dispose();
|
|
|
|
|
|
(dn.label.material as THREE.Material).dispose();
|
|
|
|
|
|
|
|
|
|
|
|
this.meshMap.delete(dn.id);
|
|
|
|
|
|
this.glowMap.delete(dn.id);
|
|
|
|
|
|
this.labelSprites.delete(dn.id);
|
|
|
|
|
|
this.positions.delete(dn.id);
|
|
|
|
|
|
|
|
|
|
|
|
this.dissolvingNodes.splice(i, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Growth animations — smooth scale transition for promoted nodes
|
|
|
|
|
|
for (let i = this.growingNodes.length - 1; i >= 0; i--) {
|
|
|
|
|
|
const gn = this.growingNodes[i];
|
|
|
|
|
|
gn.frame++;
|
|
|
|
|
|
const t = Math.min(gn.frame / gn.totalFrames, 1);
|
|
|
|
|
|
const scale = gn.startScale + (gn.targetScale - gn.startScale) * easeOutElastic(t);
|
|
|
|
|
|
|
|
|
|
|
|
const mesh = this.meshMap.get(gn.id);
|
|
|
|
|
|
if (mesh) mesh.scale.setScalar(scale);
|
|
|
|
|
|
|
|
|
|
|
|
const glow = this.glowMap.get(gn.id);
|
|
|
|
|
|
if (glow) {
|
feat(v2.0.5): Intentional Amnesia — active forgetting via top-down inhibitory control
First AI memory system to model forgetting as a neuroscience-grounded
PROCESS rather than passive decay. Adds the `suppress` MCP tool (#24),
Rac1 cascade worker, migration V10, and dashboard forgetting indicators.
Based on:
- Anderson, Hanslmayr & Quaegebeur (2025), Nat Rev Neurosci — right
lateral PFC as the domain-general inhibitory controller; SIF
compounds with each stopping attempt.
- Cervantes-Sandoval et al. (2020), Front Cell Neurosci PMC7477079 —
Rac1 GTPase as the active synaptic destabilization mechanism.
What's new:
* `suppress` MCP tool — each call compounds `suppression_count` and
subtracts a `0.15 × count` penalty (saturating at 80%) from
retrieval scores during hybrid search. Distinct from delete
(removes) and demote (one-shot).
* Rac1 cascade worker — background sweep piggybacks the 6h
consolidation loop, walks `memory_connections` edges from
recently-suppressed seeds, applies attenuated FSRS decay to
co-activated neighbors. You don't just forget Jake — you fade
the café, the roommate, the birthday.
* 24h labile window — reversible via `suppress({id, reverse: true})`
within 24 hours. Matches Nader reconsolidation semantics.
* Migration V10 — additive-only (`suppression_count`, `suppressed_at`
+ partial indices). All v2.0.x DBs upgrade seamlessly on first launch.
* Dashboard: `ForgettingIndicator.svelte` pulses when suppressions
are active. 3D graph nodes dim to 20% opacity when suppressed.
New WebSocket events: `MemorySuppressed`, `MemoryUnsuppressed`,
`Rac1CascadeSwept`. Heartbeat carries `suppressed_count`.
* Search pipeline: SIF penalty inserted into the accessibility stage
so it stacks on top of passive FSRS decay.
* Tool count bumped 23 → 24. Cognitive modules 29 → 30.
Memories persist — they are INHIBITED, not erased. `memory.get(id)`
returns full content through any number of suppressions. The 24h
labile window is a grace period for regret.
Also fixes issue #31 (dashboard graph view buggy) as a companion UI
bug discovered during the v2.0.5 audit cycle:
* Root cause: node glow `SpriteMaterial` had no `map`, so
`THREE.Sprite` rendered as a solid-coloured 1×1 plane. Additive
blending + `UnrealBloomPass(0.8, 0.4, 0.85)` amplified the square
edges into hard-edged glowing cubes.
* Fix: shared 128×128 radial-gradient `CanvasTexture` singleton used
as the sprite map. Retuned bloom to `(0.55, 0.6, 0.2)`. Halved fog
density (0.008 → 0.0035). Edges bumped from dark navy `0x4a4a7a`
to brand violet `0x8b5cf6` with higher opacity. Added explicit
`scene.background` and a 2000-point starfield for depth.
* 21 regression tests added in `ui-fixes.test.ts` locking every
invariant in (shared texture singleton, depthWrite:false, scale
×6, bloom magic numbers via source regex, starfield presence).
Tests: 1,284 Rust (+47) + 171 Vitest (+21) = 1,455 total, 0 failed
Clippy: clean across all targets, zero warnings
Release binary: 22.6MB, `cargo build --release -p vestige-mcp` green
Versions: workspace aligned at 2.0.5 across all 6 crates/packages
Closes #31
2026-04-14 17:30:30 -05:00
|
|
|
|
const glowSize = scale * 6;
|
2026-03-03 14:04:31 -06:00
|
|
|
|
glow.scale.set(glowSize, glowSize, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (gn.frame >= gn.totalFrames) {
|
|
|
|
|
|
this.growingNodes.splice(i, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Node breathing (skip nodes being animated)
|
|
|
|
|
|
const animatingIds = new Set([
|
|
|
|
|
|
...this.materializingNodes.map((m) => m.id),
|
|
|
|
|
|
...this.dissolvingNodes.map((d) => d.id),
|
|
|
|
|
|
...this.growingNodes.map((g) => g.id),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
2026-03-01 21:24:10 -06:00
|
|
|
|
this.meshMap.forEach((mesh, id) => {
|
2026-03-03 14:04:31 -06:00
|
|
|
|
if (animatingIds.has(id)) return;
|
2026-03-01 21:24:10 -06:00
|
|
|
|
const node = nodes.find((n) => n.id === id);
|
|
|
|
|
|
if (!node) return;
|
|
|
|
|
|
const breathe =
|
|
|
|
|
|
1 + Math.sin(time * 1.5 + nodes.indexOf(node) * 0.5) * 0.15 * node.retention;
|
|
|
|
|
|
mesh.scale.setScalar(breathe);
|
|
|
|
|
|
|
v2.0.8 Pulse — Reasoning Theater + Pulse Toast + Terrarium + Intel Mac (#44)
* docs: anti-hedging banner + master state-and-plan + UI roadmap
Project-level CLAUDE.md line 1 is now the anti-hedging banner referencing
the global rule at /Users/entity002/CLAUDE.md. When Sam commits to an
execution path (content cadence, shipping velocity, outreach, launch),
forecasts compute as if the commitment is already met — no discounting by
execution probability.
VESTIGE_STATE_AND_PLAN.md (1135 lines, 14 parts): single-page reference
any AI agent can read to plan the next phase without re-reading the repo.
Covers current state of every crate, the 24 MCP tools, the 19 WebSocket
event types, the 8 IDE integrations, and v2.1 through v3.0 roadmap.
UI_ROADMAP_v2.1_v2.2.md: compiled UI-gap analysis from 4 parallel research
agents (April 19). 10 critical UI gaps + 2026 bleeding-edge patterns +
unclaimed territory (menu bar widget, voice-first, AR/VR, time-scrubber).
* feat(v2.2-pulse): InsightToast + multi-process STORAGE docs
Two independent ship items landing together on the v2.2 branch ahead of
the Tuesday launch — a new UI surface that makes Vestige's cognitive
events visible in real time, and honest documentation of the multi-process
safety story that underpins the Stigmergic Swarm narrative.
**InsightToast** (apps/dashboard/src/lib/components/InsightToast.svelte,
apps/dashboard/src/lib/stores/toast.ts):
The dashboard already had a working WebSocket event stream on
ws://localhost:3927/ws that broadcast every cognitive event (dream
completions, consolidation sweeps, memory promotions/demotions, active-
forgetting suppression and Rac1 cascades, bridge discoveries). None of
that was surfaced to a user looking at anything other than the raw feed
view. InsightToast subscribes to the existing eventFeed derived store,
filters the spammy lifecycle events (Heartbeat, SearchPerformed,
RetentionDecayed, ActivationSpread, ImportanceScored, MemoryCreated),
and translates the narrative events into ephemeral toasts with a
bioluminescent colored accent matching EVENT_TYPE_COLORS.
Design notes:
- Rate-limited ConnectionDiscovered at 1.5s intervals (dreams emit many).
- Max 4 visible toasts, auto-dismiss at 4.5-7s depending on event weight.
- Click or Enter/Space to dismiss early.
- Bottom-right on desktop, top-banner on mobile.
- Reduced-motion honored via prefers-reduced-motion.
- Zero new websocket subscriptions — everything piggybacks on the
existing derived store.
Also added a "Preview Pulse" button to Settings -> Cognitive Operations
that fires a synthetic sequence of four toasts (DreamCompleted,
ConnectionDiscovered, MemorySuppressed, ConsolidationCompleted) so
the animation is demoable without waiting for real cognitive activity.
**Multi-Process Safety section in docs/STORAGE.md**:
Grounds the Stigmergic Swarm story with concrete tables of what the
current WAL + 5s busy_timeout configuration actually supports vs what
remains experimental. Key honest points:
- Shared --data-dir + ONE vestige-mcp + N clients is the shipping
pattern for multi-agent coordination.
- Two vestige-mcp processes writing the same file is experimental —
documented with the lsof + pkill recovery path.
- Roadmap lists the three items that would promote it to "supported":
advisory file lock, retry-with-jitter on SQLITE_BUSY, and a
concurrent-writer load test.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm run build: clean static build, adapter-static succeeds
* feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
* fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
* test(v2.3): full e2e + integration coverage for Pulse + Birth Ritual
Post-ship verification pass — five parallel write-agents produced 229 new
tests across vitest units, vitest integration, and Playwright browser e2e.
Net suite: 361 vitest pass (up from 251, +110) and 9/9 Playwright pass on
back-to-back runs.
**toast.test.ts (NEW, 661 lines, 42 tests)**
Silent-lobotomy batch walk proven (multi-event tick processes ALL, not
just newest, oldest-first ordering preserved). Hover-panic pause/resume
with remaining-ms math. All 9 event type translations asserted, all 11
noise types asserted silent. ConnectionDiscovered 1500ms throttle.
MAX_VISIBLE=4 eviction. clear() tears down all timers. fireDemoSequence
staggers 4 toasts at 800ms intervals. vi.useFakeTimers + vi.mock of
eventFeed; vi.resetModules in beforeEach for module-singleton isolation.
**websocket.test.ts (NEW, 247 lines, 30 tests)**
injectEvent adds to front, respects MAX_EVENTS=200 with FIFO eviction,
triggers eventFeed emissions. All 6 derived stores (isConnected,
heartbeat, memoryCount, avgRetention, suppressedCount, uptimeSeconds)
verified — defaults, post-heartbeat values, clearEvents preserves
lastHeartbeat. 13 formatUptime boundary cases (0/59/60/3599/3600/
86399/86400 seconds + negative / NaN / ±Infinity).
**effects.test.ts (EXTENDED, +501 lines, +21 tests, 51 total)**
createBirthOrb full lifecycle — sprite count (halo + core), cosmic
center via camera.quaternion, gestation phase (position lock, opacity
rise, scale easing, color tint), flight Bezier arc above linear
midpoint at t=0.5, dynamic mid-flight target redirect. onArrive fires
exactly once at frame 139. Post-arrival fade + disposal cleans scene
children. Sanhedrin Shatter: target goes undefined mid-flight →
onArrive NEVER called, implosion spawned, halo blood-red, eventual
cleanup. dispose() cleans active orbs. Multiple simultaneous orbs.
Custom gestation/flight frame opts honored. Zero-alloc invariant
smoke test (6 orbs × 150 frames, no leaks).
**nodes.test.ts (EXTENDED, +197 lines, +10 tests, 42 total)**
addNode({isBirthRitual:true}) hides mesh/glow/label immediately,
stamps birthRitualPending sentinel with correct totalFrames +
targetScale, does NOT enqueue materialization. igniteNode flips
visibility + enqueues materialization. Idempotent — second call
no-op. Non-ritual nodes unaffected. Unknown id is safe no-op.
Position stored in positions map while invisible (force sim still
sees it). removeNode + late igniteNode is safe.
**events.test.ts (EXTENDED, +268 lines, +7 tests, 55 total)**
MemoryCreated → mesh hidden immediately, 2 birth-orb sprites added,
ZERO RingGeometry meshes and ZERO Points particles at spawn. Full
ritual drive → onArrive fires, node visible + materializing, sentinel
cleared. Newton's Cradle: target mesh scale exactly 0.001 * 1.8 right
after arrival. Dual shockwave: exactly 2 Ring meshes added. Re-read
live position on arrival — force-sim motion during ritual → burst
lands at the NEW position. Sanhedrin abort path → rainbow burst,
shockwave, ripple wave are NEVER called (vi.spyOn).
**three-mock.ts (EXTENDED)**
Added Color.setRGB — production Three.js has it, the Sanhedrin-
Shatter path in effects.ts uses it. Two write-agents independently
monkey-patched the mock inline; consolidated as a 5-line mock
addition so tests stay clean.
**e2e/pulse-toast.spec.ts (NEW, 235 lines, 6 Playwright tests)**
Navigate /dashboard/settings → click Preview Pulse → assert first
toast appears within 500ms → assert >= 2 toasts visible at peak.
Click-to-dismiss removes clicked toast (matched by aria-label).
Hover survives >8s past the 5.5s dwell. Keyboard Enter dismisses
focused toast. CSS animation-play-state:paused on .toast-progress-
fill while hovered, running on mouseleave. Screenshots attached to
HTML report. Zero backend dependency (fireDemoSequence is purely
client-side).
**e2e/birth-ritual.spec.ts (NEW, 199 lines, 3 Playwright tests)**
Canvas mounts on /dashboard/graph (gracefully test.fixme if MCP
backend absent). Settings button injection + SPA route to /graph
→ screenshot timeline at t=0/500/1200/2000/2400/3000ms attached
to HTML report. pageerror + console-error listeners catch any
crash (would re-surface FATAL 6 if reintroduced). Three back-to-
back births — no errors, canvas still dispatches clicks.
Run commands:
cd apps/dashboard && npm test # 361/361 pass, ~600ms
cd apps/dashboard && npx playwright test # 9/9 pass, ~25s
Typecheck: 0 errors, 0 warnings. Build: clean adapter-static.
* fix(graph): default /api/graph to newest-memory center, add sort param
memory_timeline PR #37 exposed the same class of bug in the graph
endpoint: the dashboard Graph page (and the /api/graph endpoint it
hits) defaulted to centering on the most-connected memory, ran BFS at
depth 3, and capped the subgraph at 150 nodes. On a mature corpus this
clustered the visualization around a historical hotspot and hid freshly
ingested memories that hadn't accumulated edges yet. User-visible
symptom: TimeSlider on /graph showing "Feb 21 → Mar 1 2026" when the
database actually contains memories through today (Apr 20).
**Backend (`crates/vestige-mcp/src/dashboard/handlers.rs`):**
- `GraphParams` gains `sort: Option<String>` (accepted: "recent" |
"connected", unknown falls back to "recent").
- New internal `GraphSort` enum + case-insensitive `parse()`.
- Extracted `default_center_id(storage, sort)` so handler logic and
tests share the same branching. Recent path picks `get_all_nodes(1,
0)` (ORDER BY created_at DESC). Connected path picks
`get_most_connected_memory`, degrading gracefully to recent if the
DB has no edges yet.
- Default behaviour flipped from "connected" to "recent" — matches
user expectation of "show me my recent stuff".
**Dashboard (`apps/dashboard`):**
- `api.graph()` accepts `sort?: 'recent' | 'connected'` with JSDoc
explaining the rationale.
- `/graph/+page.svelte` passes `sort: 'recent'` when no query or
center_id is active. Query / center_id paths unchanged — they
already carry their own centering intent.
**Tests:** 6 new unit tests in `handlers::tests`:
- `graph_sort_parse_defaults_to_recent` (None, empty, garbage,
"recent", "Recent", "RECENT")
- `graph_sort_parse_accepts_connected_case_insensitive`
- `default_center_id_recent_returns_newest_node` — ingest 3 nodes,
assert newest is picked
- `default_center_id_connected_prefers_hub_over_newest` — wire a hub
node with 3 spokes, then ingest a newer "lonely" node; assert the
hub wins in Connected mode even though it's older
- `default_center_id_connected_falls_back_to_recent_when_no_edges`
— fresh DB with no connections still returns newest, not 404
- `default_center_id_returns_not_found_on_empty_db` — both modes
return 404 cleanly on empty storage
Build + test:
- cargo test -p vestige-mcp --lib handlers:: → 6/6 pass
- cargo test --workspace --lib → 830/830 pass, 0 failed
- cargo clippy -p vestige-core -p vestige-mcp --lib -- -D warnings →
clean
- npm run check → 0 errors, 0 warnings
- npm test → 361/361 pass
Binary already installed at ~/.local/bin/vestige-mcp (copied from
cargo build --release -p vestige-mcp). New Claude Desktop / Code
sessions will pick it up automatically when they respawn their MCP
subprocess. The dashboard HTTP server on port 3927 needs a manual
relaunch from a terminal with the usual pattern:
nohup bash -c 'tail -f /dev/null | \
VESTIGE_DASHBOARD_ENABLED=true ~/.local/bin/vestige-mcp' \
> /tmp/vestige-mcp.log 2>&1 & disown
* feat(v2.4): UI expansion — 8 new surfaces exposing the cognitive engine
Sam asked: "Build EVERY SINGLE MISSING UI PIECE." 10 parallel agents shipped
10 new viewports over the existing Rust backend, then 11 audit agents
line-by-line reviewed each one, extracted pure-logic helpers, fixed ~30
bugs, and shipped 549 new unit tests. Everything wired through the layout
with single-key shortcuts and a live theme toggle.
**Eight new routes**
- `/reasoning` — Reasoning Theater: Cmd+K ask palette → animated 8-stage
deep_reference pipeline + FSRS-trust-scored evidence cards +
contradiction arcs rendered as live SVG between evidence nodes
- `/duplicates` — threshold-driven cluster detector with winner selection,
Merge/Review/Dismiss actions, debounced slider
- `/dreams` — Dream Cinema: trigger dream + scrubbable 5-stage replay
(Replay → Cross-reference → Strengthen → Prune → Transfer) + insight
cards with novelty glow
- `/schedule` — FSRS Review Calendar: 6×7 grid with urgency color
bands (overdue/today/week/future), retention sparkline, expand-day list
- `/importance` — 4-channel radar (Novelty/Arousal/Reward/Attention) with
composite score + top-important list
- `/activation` — live spreading-activation view: search → SVG concentric
rings with decay animation + live-mode event feed
- `/contradictions` — 2D cosmic constellation of conflicting memories,
arcs colored by severity, tooltips with previews
- `/patterns` — cross-project pattern transfer heatmap with category
filters, top-transferred sidebar
**Three layout additions**
- `AmbientAwarenessStrip` — slim top band with retention vitals, at-risk
count, active intentions, recent dream, activity sparkline, dreaming
indicator, Sanhedrin-watch flash. Pure `$derived` over existing stores.
- `ThemeToggle` — dark/light/auto cycle with matchMedia listener,
localStorage persistence, SSR-safe, reduced-motion-aware. Rendered in
sidebar footer next to the connection dot.
- `MemoryAuditTrail` — per-memory Sources panel integrated as a
Content/Audit tab into the existing /memories expansion.
**Pure-logic helper modules extracted (for testability + reuse)**
reasoning-helpers, duplicates-helpers, dream-helpers, schedule-helpers,
audit-trail-helpers, awareness-helpers, contradiction-helpers,
activation-helpers, patterns-helpers, importance-helpers.
**Bugs fixed during audit (not exhaustive)**
- Trust-color inconsistency between EvidenceCard and the page confidence
ring (0.75 boundary split emerald vs amber)
- `new Date('garbage').toLocaleDateString()` returned literal "Invalid Date"
in 3 components — all now return em-dash or raw string
- NaN propagation in `Math.max(0, Math.min(1, NaN))` across clamps
- Off-by-one PRNG in audit-trail seeded mock (seed === UINT32_MAX yielded
rand() === 1.0 → index out of bounds)
- Duplicates dismissals keyed by array index broke on re-fetch; now keyed
by sorted cluster member IDs with stale-dismissal pruning
- Empty-cluster crash in DuplicateCluster.pickWinner
- Undefined tags crash in DuplicateCluster.safeTags
- Debounce timer leak in threshold slider (missing onDestroy cleanup)
- Schedule day-vs-hour granularity mismatch between calendar cell and
sidebar list ("today" in one, "in 1d" in the other)
- Schedule 500-memory hard cap silently truncated; bumped to 2000 + banner
- Schedule DST boundary bug in daysBetween (wall-clock math vs
startOfDay-normalized)
- Dream stage clamp now handles NaN/Infinity/floats
- Dream double-click debounce via `if (dreaming) return`
- Theme setTheme runtime validation; initTheme idempotence (listener +
style-element dedup on repeat calls)
- ContradictionArcs node radius unclamped (trust < 0 or > 1 rendered
invalid sizes); tooltip position clamp (could push off-canvas)
- ContradictionArcs $state closure capture (width/height weren't reactive
in the derived layout block)
- Activation route was MISSING from the repo — audit agent created it
with identity-based event filtering and proper RAF cleanup
- Layout: ThemeToggle was imported but never rendered — now in sidebar
footer; sidebar overflow-y-auto added for the 16-entry nav
**Tests — 549 new, 910 total passing (0 failures)**
ReasoningChain 42 | EvidenceCard 50
DuplicateCluster 64 | DreamStageReplay 19
DreamInsightCard 43 | FSRSCalendar 32
MemoryAuditTrail 45 | AmbientAwareness 60
theme (store) 31 | ContradictionArcs 43
ActivationNetwork 54 | PatternTransfer 31
ImportanceRadar 35 | + existing 361 tests still green
**Gates passed**
- `npm run check`: 0 errors, 0 warnings across 623 files
- `npm test`: 910/910 passing, 22 test files
- `npm run build`: clean adapter-static output
**Layout wiring**
- Nav array expanded 8 → 16 entries (existing 8 + 8 new routes)
- Single-key shortcuts added: R/A/D/C/P/U/X/N (no conflicts with
existing G/M/T/F/E/I/S/,)
- Cmd+K palette search works across all 16
- Mobile nav = top 5 (Graph, Reasoning, Memories, Timeline, Feed)
- AmbientAwarenessStrip mounted as first child of <main>
- ThemeToggle rendered in sidebar footer (was imported-but-unmounted)
- Theme initTheme() + teardown wired into onMount cleanup chain
Net branch delta: 47 files changed, +13,756 insertions, -6 deletions
* chore(release): v2.0.8 "Pulse"
Bundled release: Reasoning Theater wired to the 8-stage deep_reference
cognitive pipeline, Pulse InsightToast, Memory Birth Ritual (v2.3
Terrarium), 7 new dashboard surfaces (/duplicates, /dreams, /schedule,
/importance, /activation, /contradictions, /patterns), 3D graph
brightness system with auto distance-compensation + user slider, and
contradiction-detection + primary-selection hardening in the
cross_reference tool. Intel Mac (x86_64-apple-darwin) also flows through
to the release matrix from PR #43.
Added:
- POST /api/deep_reference — HTTP surface for the 8-stage pipeline
- DeepReferenceCompleted WebSocket event (primary + supporting +
contradicting memory IDs for downstream graph animation)
- /reasoning route, full UI + Cmd+K Ask palette
- 7 new dashboard surfaces exposing the cognitive engine
- Graph brightness slider + localStorage persistence + distance-based
emissive compensation so nodes don't disappear into fog at zoom-out
Fixed:
- Contradiction-detection false positives: adjacent-domain memories no
longer flagged as conflicts (NEGATION_PAIRS wildcards removed,
shared-words floor 2 → 4, topic-sim floor 0.15 → 0.55, STAGE 5
overlap floor 0.15 → 0.4)
- Primary-memory selection: unified composite 0.5 × relevance + 0.2 ×
trust + 0.3 × term_presence with hard topic-term filter, closing the
class of bug where off-topic high-trust memories won queries about
specific subjects
- Graph default-load fallback from sort=recent to sort=connected when
the newest memory is isolated, both backend and client
Changed:
- Reasoning page information hierarchy: chain renders first as hero,
confidence meter + Primary Source citation footer below
- Cargo feature split: embeddings code-only + ort-download | ort-dynamic
backends; defaults preserve identical behavior for existing consumers
- CI release-build now gates PRs too so multi-platform regressions
surface pre-merge
2026-04-23 02:21:11 -05:00
|
|
|
|
// Distance compensation: FogExp2 attenuates exponentially with camera
|
|
|
|
|
|
// distance, so nodes past ~80 units go nearly black unless we push
|
|
|
|
|
|
// emissive harder. Boost runs 1.0x at <60 units → ~2.4x at 200 units.
|
|
|
|
|
|
// Combined with the user brightness multiplier this gives a visible
|
|
|
|
|
|
// floor at every zoom level without blowing out close-up highlights.
|
|
|
|
|
|
const pos = this.positions.get(id);
|
|
|
|
|
|
const dist = pos ? camera.position.distanceTo(pos) : 0;
|
|
|
|
|
|
const distanceBoost = 1 + Math.min(1.4, Math.max(0, (dist - 60) / 100));
|
|
|
|
|
|
|
2026-03-01 21:24:10 -06:00
|
|
|
|
const mat = mesh.material as THREE.MeshStandardMaterial;
|
|
|
|
|
|
if (id === this.hoveredNode) {
|
v2.0.8 Pulse — Reasoning Theater + Pulse Toast + Terrarium + Intel Mac (#44)
* docs: anti-hedging banner + master state-and-plan + UI roadmap
Project-level CLAUDE.md line 1 is now the anti-hedging banner referencing
the global rule at /Users/entity002/CLAUDE.md. When Sam commits to an
execution path (content cadence, shipping velocity, outreach, launch),
forecasts compute as if the commitment is already met — no discounting by
execution probability.
VESTIGE_STATE_AND_PLAN.md (1135 lines, 14 parts): single-page reference
any AI agent can read to plan the next phase without re-reading the repo.
Covers current state of every crate, the 24 MCP tools, the 19 WebSocket
event types, the 8 IDE integrations, and v2.1 through v3.0 roadmap.
UI_ROADMAP_v2.1_v2.2.md: compiled UI-gap analysis from 4 parallel research
agents (April 19). 10 critical UI gaps + 2026 bleeding-edge patterns +
unclaimed territory (menu bar widget, voice-first, AR/VR, time-scrubber).
* feat(v2.2-pulse): InsightToast + multi-process STORAGE docs
Two independent ship items landing together on the v2.2 branch ahead of
the Tuesday launch — a new UI surface that makes Vestige's cognitive
events visible in real time, and honest documentation of the multi-process
safety story that underpins the Stigmergic Swarm narrative.
**InsightToast** (apps/dashboard/src/lib/components/InsightToast.svelte,
apps/dashboard/src/lib/stores/toast.ts):
The dashboard already had a working WebSocket event stream on
ws://localhost:3927/ws that broadcast every cognitive event (dream
completions, consolidation sweeps, memory promotions/demotions, active-
forgetting suppression and Rac1 cascades, bridge discoveries). None of
that was surfaced to a user looking at anything other than the raw feed
view. InsightToast subscribes to the existing eventFeed derived store,
filters the spammy lifecycle events (Heartbeat, SearchPerformed,
RetentionDecayed, ActivationSpread, ImportanceScored, MemoryCreated),
and translates the narrative events into ephemeral toasts with a
bioluminescent colored accent matching EVENT_TYPE_COLORS.
Design notes:
- Rate-limited ConnectionDiscovered at 1.5s intervals (dreams emit many).
- Max 4 visible toasts, auto-dismiss at 4.5-7s depending on event weight.
- Click or Enter/Space to dismiss early.
- Bottom-right on desktop, top-banner on mobile.
- Reduced-motion honored via prefers-reduced-motion.
- Zero new websocket subscriptions — everything piggybacks on the
existing derived store.
Also added a "Preview Pulse" button to Settings -> Cognitive Operations
that fires a synthetic sequence of four toasts (DreamCompleted,
ConnectionDiscovered, MemorySuppressed, ConsolidationCompleted) so
the animation is demoable without waiting for real cognitive activity.
**Multi-Process Safety section in docs/STORAGE.md**:
Grounds the Stigmergic Swarm story with concrete tables of what the
current WAL + 5s busy_timeout configuration actually supports vs what
remains experimental. Key honest points:
- Shared --data-dir + ONE vestige-mcp + N clients is the shipping
pattern for multi-agent coordination.
- Two vestige-mcp processes writing the same file is experimental —
documented with the lsof + pkill recovery path.
- Roadmap lists the three items that would promote it to "supported":
advisory file lock, retry-with-jitter on SQLITE_BUSY, and a
concurrent-writer load test.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm run build: clean static build, adapter-static succeeds
* feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
* fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
* test(v2.3): full e2e + integration coverage for Pulse + Birth Ritual
Post-ship verification pass — five parallel write-agents produced 229 new
tests across vitest units, vitest integration, and Playwright browser e2e.
Net suite: 361 vitest pass (up from 251, +110) and 9/9 Playwright pass on
back-to-back runs.
**toast.test.ts (NEW, 661 lines, 42 tests)**
Silent-lobotomy batch walk proven (multi-event tick processes ALL, not
just newest, oldest-first ordering preserved). Hover-panic pause/resume
with remaining-ms math. All 9 event type translations asserted, all 11
noise types asserted silent. ConnectionDiscovered 1500ms throttle.
MAX_VISIBLE=4 eviction. clear() tears down all timers. fireDemoSequence
staggers 4 toasts at 800ms intervals. vi.useFakeTimers + vi.mock of
eventFeed; vi.resetModules in beforeEach for module-singleton isolation.
**websocket.test.ts (NEW, 247 lines, 30 tests)**
injectEvent adds to front, respects MAX_EVENTS=200 with FIFO eviction,
triggers eventFeed emissions. All 6 derived stores (isConnected,
heartbeat, memoryCount, avgRetention, suppressedCount, uptimeSeconds)
verified — defaults, post-heartbeat values, clearEvents preserves
lastHeartbeat. 13 formatUptime boundary cases (0/59/60/3599/3600/
86399/86400 seconds + negative / NaN / ±Infinity).
**effects.test.ts (EXTENDED, +501 lines, +21 tests, 51 total)**
createBirthOrb full lifecycle — sprite count (halo + core), cosmic
center via camera.quaternion, gestation phase (position lock, opacity
rise, scale easing, color tint), flight Bezier arc above linear
midpoint at t=0.5, dynamic mid-flight target redirect. onArrive fires
exactly once at frame 139. Post-arrival fade + disposal cleans scene
children. Sanhedrin Shatter: target goes undefined mid-flight →
onArrive NEVER called, implosion spawned, halo blood-red, eventual
cleanup. dispose() cleans active orbs. Multiple simultaneous orbs.
Custom gestation/flight frame opts honored. Zero-alloc invariant
smoke test (6 orbs × 150 frames, no leaks).
**nodes.test.ts (EXTENDED, +197 lines, +10 tests, 42 total)**
addNode({isBirthRitual:true}) hides mesh/glow/label immediately,
stamps birthRitualPending sentinel with correct totalFrames +
targetScale, does NOT enqueue materialization. igniteNode flips
visibility + enqueues materialization. Idempotent — second call
no-op. Non-ritual nodes unaffected. Unknown id is safe no-op.
Position stored in positions map while invisible (force sim still
sees it). removeNode + late igniteNode is safe.
**events.test.ts (EXTENDED, +268 lines, +7 tests, 55 total)**
MemoryCreated → mesh hidden immediately, 2 birth-orb sprites added,
ZERO RingGeometry meshes and ZERO Points particles at spawn. Full
ritual drive → onArrive fires, node visible + materializing, sentinel
cleared. Newton's Cradle: target mesh scale exactly 0.001 * 1.8 right
after arrival. Dual shockwave: exactly 2 Ring meshes added. Re-read
live position on arrival — force-sim motion during ritual → burst
lands at the NEW position. Sanhedrin abort path → rainbow burst,
shockwave, ripple wave are NEVER called (vi.spyOn).
**three-mock.ts (EXTENDED)**
Added Color.setRGB — production Three.js has it, the Sanhedrin-
Shatter path in effects.ts uses it. Two write-agents independently
monkey-patched the mock inline; consolidated as a 5-line mock
addition so tests stay clean.
**e2e/pulse-toast.spec.ts (NEW, 235 lines, 6 Playwright tests)**
Navigate /dashboard/settings → click Preview Pulse → assert first
toast appears within 500ms → assert >= 2 toasts visible at peak.
Click-to-dismiss removes clicked toast (matched by aria-label).
Hover survives >8s past the 5.5s dwell. Keyboard Enter dismisses
focused toast. CSS animation-play-state:paused on .toast-progress-
fill while hovered, running on mouseleave. Screenshots attached to
HTML report. Zero backend dependency (fireDemoSequence is purely
client-side).
**e2e/birth-ritual.spec.ts (NEW, 199 lines, 3 Playwright tests)**
Canvas mounts on /dashboard/graph (gracefully test.fixme if MCP
backend absent). Settings button injection + SPA route to /graph
→ screenshot timeline at t=0/500/1200/2000/2400/3000ms attached
to HTML report. pageerror + console-error listeners catch any
crash (would re-surface FATAL 6 if reintroduced). Three back-to-
back births — no errors, canvas still dispatches clicks.
Run commands:
cd apps/dashboard && npm test # 361/361 pass, ~600ms
cd apps/dashboard && npx playwright test # 9/9 pass, ~25s
Typecheck: 0 errors, 0 warnings. Build: clean adapter-static.
* fix(graph): default /api/graph to newest-memory center, add sort param
memory_timeline PR #37 exposed the same class of bug in the graph
endpoint: the dashboard Graph page (and the /api/graph endpoint it
hits) defaulted to centering on the most-connected memory, ran BFS at
depth 3, and capped the subgraph at 150 nodes. On a mature corpus this
clustered the visualization around a historical hotspot and hid freshly
ingested memories that hadn't accumulated edges yet. User-visible
symptom: TimeSlider on /graph showing "Feb 21 → Mar 1 2026" when the
database actually contains memories through today (Apr 20).
**Backend (`crates/vestige-mcp/src/dashboard/handlers.rs`):**
- `GraphParams` gains `sort: Option<String>` (accepted: "recent" |
"connected", unknown falls back to "recent").
- New internal `GraphSort` enum + case-insensitive `parse()`.
- Extracted `default_center_id(storage, sort)` so handler logic and
tests share the same branching. Recent path picks `get_all_nodes(1,
0)` (ORDER BY created_at DESC). Connected path picks
`get_most_connected_memory`, degrading gracefully to recent if the
DB has no edges yet.
- Default behaviour flipped from "connected" to "recent" — matches
user expectation of "show me my recent stuff".
**Dashboard (`apps/dashboard`):**
- `api.graph()` accepts `sort?: 'recent' | 'connected'` with JSDoc
explaining the rationale.
- `/graph/+page.svelte` passes `sort: 'recent'` when no query or
center_id is active. Query / center_id paths unchanged — they
already carry their own centering intent.
**Tests:** 6 new unit tests in `handlers::tests`:
- `graph_sort_parse_defaults_to_recent` (None, empty, garbage,
"recent", "Recent", "RECENT")
- `graph_sort_parse_accepts_connected_case_insensitive`
- `default_center_id_recent_returns_newest_node` — ingest 3 nodes,
assert newest is picked
- `default_center_id_connected_prefers_hub_over_newest` — wire a hub
node with 3 spokes, then ingest a newer "lonely" node; assert the
hub wins in Connected mode even though it's older
- `default_center_id_connected_falls_back_to_recent_when_no_edges`
— fresh DB with no connections still returns newest, not 404
- `default_center_id_returns_not_found_on_empty_db` — both modes
return 404 cleanly on empty storage
Build + test:
- cargo test -p vestige-mcp --lib handlers:: → 6/6 pass
- cargo test --workspace --lib → 830/830 pass, 0 failed
- cargo clippy -p vestige-core -p vestige-mcp --lib -- -D warnings →
clean
- npm run check → 0 errors, 0 warnings
- npm test → 361/361 pass
Binary already installed at ~/.local/bin/vestige-mcp (copied from
cargo build --release -p vestige-mcp). New Claude Desktop / Code
sessions will pick it up automatically when they respawn their MCP
subprocess. The dashboard HTTP server on port 3927 needs a manual
relaunch from a terminal with the usual pattern:
nohup bash -c 'tail -f /dev/null | \
VESTIGE_DASHBOARD_ENABLED=true ~/.local/bin/vestige-mcp' \
> /tmp/vestige-mcp.log 2>&1 & disown
* feat(v2.4): UI expansion — 8 new surfaces exposing the cognitive engine
Sam asked: "Build EVERY SINGLE MISSING UI PIECE." 10 parallel agents shipped
10 new viewports over the existing Rust backend, then 11 audit agents
line-by-line reviewed each one, extracted pure-logic helpers, fixed ~30
bugs, and shipped 549 new unit tests. Everything wired through the layout
with single-key shortcuts and a live theme toggle.
**Eight new routes**
- `/reasoning` — Reasoning Theater: Cmd+K ask palette → animated 8-stage
deep_reference pipeline + FSRS-trust-scored evidence cards +
contradiction arcs rendered as live SVG between evidence nodes
- `/duplicates` — threshold-driven cluster detector with winner selection,
Merge/Review/Dismiss actions, debounced slider
- `/dreams` — Dream Cinema: trigger dream + scrubbable 5-stage replay
(Replay → Cross-reference → Strengthen → Prune → Transfer) + insight
cards with novelty glow
- `/schedule` — FSRS Review Calendar: 6×7 grid with urgency color
bands (overdue/today/week/future), retention sparkline, expand-day list
- `/importance` — 4-channel radar (Novelty/Arousal/Reward/Attention) with
composite score + top-important list
- `/activation` — live spreading-activation view: search → SVG concentric
rings with decay animation + live-mode event feed
- `/contradictions` — 2D cosmic constellation of conflicting memories,
arcs colored by severity, tooltips with previews
- `/patterns` — cross-project pattern transfer heatmap with category
filters, top-transferred sidebar
**Three layout additions**
- `AmbientAwarenessStrip` — slim top band with retention vitals, at-risk
count, active intentions, recent dream, activity sparkline, dreaming
indicator, Sanhedrin-watch flash. Pure `$derived` over existing stores.
- `ThemeToggle` — dark/light/auto cycle with matchMedia listener,
localStorage persistence, SSR-safe, reduced-motion-aware. Rendered in
sidebar footer next to the connection dot.
- `MemoryAuditTrail` — per-memory Sources panel integrated as a
Content/Audit tab into the existing /memories expansion.
**Pure-logic helper modules extracted (for testability + reuse)**
reasoning-helpers, duplicates-helpers, dream-helpers, schedule-helpers,
audit-trail-helpers, awareness-helpers, contradiction-helpers,
activation-helpers, patterns-helpers, importance-helpers.
**Bugs fixed during audit (not exhaustive)**
- Trust-color inconsistency between EvidenceCard and the page confidence
ring (0.75 boundary split emerald vs amber)
- `new Date('garbage').toLocaleDateString()` returned literal "Invalid Date"
in 3 components — all now return em-dash or raw string
- NaN propagation in `Math.max(0, Math.min(1, NaN))` across clamps
- Off-by-one PRNG in audit-trail seeded mock (seed === UINT32_MAX yielded
rand() === 1.0 → index out of bounds)
- Duplicates dismissals keyed by array index broke on re-fetch; now keyed
by sorted cluster member IDs with stale-dismissal pruning
- Empty-cluster crash in DuplicateCluster.pickWinner
- Undefined tags crash in DuplicateCluster.safeTags
- Debounce timer leak in threshold slider (missing onDestroy cleanup)
- Schedule day-vs-hour granularity mismatch between calendar cell and
sidebar list ("today" in one, "in 1d" in the other)
- Schedule 500-memory hard cap silently truncated; bumped to 2000 + banner
- Schedule DST boundary bug in daysBetween (wall-clock math vs
startOfDay-normalized)
- Dream stage clamp now handles NaN/Infinity/floats
- Dream double-click debounce via `if (dreaming) return`
- Theme setTheme runtime validation; initTheme idempotence (listener +
style-element dedup on repeat calls)
- ContradictionArcs node radius unclamped (trust < 0 or > 1 rendered
invalid sizes); tooltip position clamp (could push off-canvas)
- ContradictionArcs $state closure capture (width/height weren't reactive
in the derived layout block)
- Activation route was MISSING from the repo — audit agent created it
with identity-based event filtering and proper RAF cleanup
- Layout: ThemeToggle was imported but never rendered — now in sidebar
footer; sidebar overflow-y-auto added for the 16-entry nav
**Tests — 549 new, 910 total passing (0 failures)**
ReasoningChain 42 | EvidenceCard 50
DuplicateCluster 64 | DreamStageReplay 19
DreamInsightCard 43 | FSRSCalendar 32
MemoryAuditTrail 45 | AmbientAwareness 60
theme (store) 31 | ContradictionArcs 43
ActivationNetwork 54 | PatternTransfer 31
ImportanceRadar 35 | + existing 361 tests still green
**Gates passed**
- `npm run check`: 0 errors, 0 warnings across 623 files
- `npm test`: 910/910 passing, 22 test files
- `npm run build`: clean adapter-static output
**Layout wiring**
- Nav array expanded 8 → 16 entries (existing 8 + 8 new routes)
- Single-key shortcuts added: R/A/D/C/P/U/X/N (no conflicts with
existing G/M/T/F/E/I/S/,)
- Cmd+K palette search works across all 16
- Mobile nav = top 5 (Graph, Reasoning, Memories, Timeline, Feed)
- AmbientAwarenessStrip mounted as first child of <main>
- ThemeToggle rendered in sidebar footer (was imported-but-unmounted)
- Theme initTheme() + teardown wired into onMount cleanup chain
Net branch delta: 47 files changed, +13,756 insertions, -6 deletions
* chore(release): v2.0.8 "Pulse"
Bundled release: Reasoning Theater wired to the 8-stage deep_reference
cognitive pipeline, Pulse InsightToast, Memory Birth Ritual (v2.3
Terrarium), 7 new dashboard surfaces (/duplicates, /dreams, /schedule,
/importance, /activation, /contradictions, /patterns), 3D graph
brightness system with auto distance-compensation + user slider, and
contradiction-detection + primary-selection hardening in the
cross_reference tool. Intel Mac (x86_64-apple-darwin) also flows through
to the release matrix from PR #43.
Added:
- POST /api/deep_reference — HTTP surface for the 8-stage pipeline
- DeepReferenceCompleted WebSocket event (primary + supporting +
contradicting memory IDs for downstream graph animation)
- /reasoning route, full UI + Cmd+K Ask palette
- 7 new dashboard surfaces exposing the cognitive engine
- Graph brightness slider + localStorage persistence + distance-based
emissive compensation so nodes don't disappear into fog at zoom-out
Fixed:
- Contradiction-detection false positives: adjacent-domain memories no
longer flagged as conflicts (NEGATION_PAIRS wildcards removed,
shared-words floor 2 → 4, topic-sim floor 0.15 → 0.55, STAGE 5
overlap floor 0.15 → 0.4)
- Primary-memory selection: unified composite 0.5 × relevance + 0.2 ×
trust + 0.3 × term_presence with hard topic-term filter, closing the
class of bug where off-topic high-trust memories won queries about
specific subjects
- Graph default-load fallback from sort=recent to sort=connected when
the newest memory is isolated, both backend and client
Changed:
- Reasoning page information hierarchy: chain renders first as hero,
confidence meter + Primary Source citation footer below
- Cargo feature split: embeddings code-only + ort-download | ort-dynamic
backends; defaults preserve identical behavior for existing consumers
- CI release-build now gates PRs too so multi-platform regressions
surface pre-merge
2026-04-23 02:21:11 -05:00
|
|
|
|
mat.emissiveIntensity = 1.0 * brightness;
|
2026-03-01 21:24:10 -06:00
|
|
|
|
} else if (id === this.selectedNode) {
|
v2.0.8 Pulse — Reasoning Theater + Pulse Toast + Terrarium + Intel Mac (#44)
* docs: anti-hedging banner + master state-and-plan + UI roadmap
Project-level CLAUDE.md line 1 is now the anti-hedging banner referencing
the global rule at /Users/entity002/CLAUDE.md. When Sam commits to an
execution path (content cadence, shipping velocity, outreach, launch),
forecasts compute as if the commitment is already met — no discounting by
execution probability.
VESTIGE_STATE_AND_PLAN.md (1135 lines, 14 parts): single-page reference
any AI agent can read to plan the next phase without re-reading the repo.
Covers current state of every crate, the 24 MCP tools, the 19 WebSocket
event types, the 8 IDE integrations, and v2.1 through v3.0 roadmap.
UI_ROADMAP_v2.1_v2.2.md: compiled UI-gap analysis from 4 parallel research
agents (April 19). 10 critical UI gaps + 2026 bleeding-edge patterns +
unclaimed territory (menu bar widget, voice-first, AR/VR, time-scrubber).
* feat(v2.2-pulse): InsightToast + multi-process STORAGE docs
Two independent ship items landing together on the v2.2 branch ahead of
the Tuesday launch — a new UI surface that makes Vestige's cognitive
events visible in real time, and honest documentation of the multi-process
safety story that underpins the Stigmergic Swarm narrative.
**InsightToast** (apps/dashboard/src/lib/components/InsightToast.svelte,
apps/dashboard/src/lib/stores/toast.ts):
The dashboard already had a working WebSocket event stream on
ws://localhost:3927/ws that broadcast every cognitive event (dream
completions, consolidation sweeps, memory promotions/demotions, active-
forgetting suppression and Rac1 cascades, bridge discoveries). None of
that was surfaced to a user looking at anything other than the raw feed
view. InsightToast subscribes to the existing eventFeed derived store,
filters the spammy lifecycle events (Heartbeat, SearchPerformed,
RetentionDecayed, ActivationSpread, ImportanceScored, MemoryCreated),
and translates the narrative events into ephemeral toasts with a
bioluminescent colored accent matching EVENT_TYPE_COLORS.
Design notes:
- Rate-limited ConnectionDiscovered at 1.5s intervals (dreams emit many).
- Max 4 visible toasts, auto-dismiss at 4.5-7s depending on event weight.
- Click or Enter/Space to dismiss early.
- Bottom-right on desktop, top-banner on mobile.
- Reduced-motion honored via prefers-reduced-motion.
- Zero new websocket subscriptions — everything piggybacks on the
existing derived store.
Also added a "Preview Pulse" button to Settings -> Cognitive Operations
that fires a synthetic sequence of four toasts (DreamCompleted,
ConnectionDiscovered, MemorySuppressed, ConsolidationCompleted) so
the animation is demoable without waiting for real cognitive activity.
**Multi-Process Safety section in docs/STORAGE.md**:
Grounds the Stigmergic Swarm story with concrete tables of what the
current WAL + 5s busy_timeout configuration actually supports vs what
remains experimental. Key honest points:
- Shared --data-dir + ONE vestige-mcp + N clients is the shipping
pattern for multi-agent coordination.
- Two vestige-mcp processes writing the same file is experimental —
documented with the lsof + pkill recovery path.
- Roadmap lists the three items that would promote it to "supported":
advisory file lock, retry-with-jitter on SQLITE_BUSY, and a
concurrent-writer load test.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm run build: clean static build, adapter-static succeeds
* feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
* fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
* test(v2.3): full e2e + integration coverage for Pulse + Birth Ritual
Post-ship verification pass — five parallel write-agents produced 229 new
tests across vitest units, vitest integration, and Playwright browser e2e.
Net suite: 361 vitest pass (up from 251, +110) and 9/9 Playwright pass on
back-to-back runs.
**toast.test.ts (NEW, 661 lines, 42 tests)**
Silent-lobotomy batch walk proven (multi-event tick processes ALL, not
just newest, oldest-first ordering preserved). Hover-panic pause/resume
with remaining-ms math. All 9 event type translations asserted, all 11
noise types asserted silent. ConnectionDiscovered 1500ms throttle.
MAX_VISIBLE=4 eviction. clear() tears down all timers. fireDemoSequence
staggers 4 toasts at 800ms intervals. vi.useFakeTimers + vi.mock of
eventFeed; vi.resetModules in beforeEach for module-singleton isolation.
**websocket.test.ts (NEW, 247 lines, 30 tests)**
injectEvent adds to front, respects MAX_EVENTS=200 with FIFO eviction,
triggers eventFeed emissions. All 6 derived stores (isConnected,
heartbeat, memoryCount, avgRetention, suppressedCount, uptimeSeconds)
verified — defaults, post-heartbeat values, clearEvents preserves
lastHeartbeat. 13 formatUptime boundary cases (0/59/60/3599/3600/
86399/86400 seconds + negative / NaN / ±Infinity).
**effects.test.ts (EXTENDED, +501 lines, +21 tests, 51 total)**
createBirthOrb full lifecycle — sprite count (halo + core), cosmic
center via camera.quaternion, gestation phase (position lock, opacity
rise, scale easing, color tint), flight Bezier arc above linear
midpoint at t=0.5, dynamic mid-flight target redirect. onArrive fires
exactly once at frame 139. Post-arrival fade + disposal cleans scene
children. Sanhedrin Shatter: target goes undefined mid-flight →
onArrive NEVER called, implosion spawned, halo blood-red, eventual
cleanup. dispose() cleans active orbs. Multiple simultaneous orbs.
Custom gestation/flight frame opts honored. Zero-alloc invariant
smoke test (6 orbs × 150 frames, no leaks).
**nodes.test.ts (EXTENDED, +197 lines, +10 tests, 42 total)**
addNode({isBirthRitual:true}) hides mesh/glow/label immediately,
stamps birthRitualPending sentinel with correct totalFrames +
targetScale, does NOT enqueue materialization. igniteNode flips
visibility + enqueues materialization. Idempotent — second call
no-op. Non-ritual nodes unaffected. Unknown id is safe no-op.
Position stored in positions map while invisible (force sim still
sees it). removeNode + late igniteNode is safe.
**events.test.ts (EXTENDED, +268 lines, +7 tests, 55 total)**
MemoryCreated → mesh hidden immediately, 2 birth-orb sprites added,
ZERO RingGeometry meshes and ZERO Points particles at spawn. Full
ritual drive → onArrive fires, node visible + materializing, sentinel
cleared. Newton's Cradle: target mesh scale exactly 0.001 * 1.8 right
after arrival. Dual shockwave: exactly 2 Ring meshes added. Re-read
live position on arrival — force-sim motion during ritual → burst
lands at the NEW position. Sanhedrin abort path → rainbow burst,
shockwave, ripple wave are NEVER called (vi.spyOn).
**three-mock.ts (EXTENDED)**
Added Color.setRGB — production Three.js has it, the Sanhedrin-
Shatter path in effects.ts uses it. Two write-agents independently
monkey-patched the mock inline; consolidated as a 5-line mock
addition so tests stay clean.
**e2e/pulse-toast.spec.ts (NEW, 235 lines, 6 Playwright tests)**
Navigate /dashboard/settings → click Preview Pulse → assert first
toast appears within 500ms → assert >= 2 toasts visible at peak.
Click-to-dismiss removes clicked toast (matched by aria-label).
Hover survives >8s past the 5.5s dwell. Keyboard Enter dismisses
focused toast. CSS animation-play-state:paused on .toast-progress-
fill while hovered, running on mouseleave. Screenshots attached to
HTML report. Zero backend dependency (fireDemoSequence is purely
client-side).
**e2e/birth-ritual.spec.ts (NEW, 199 lines, 3 Playwright tests)**
Canvas mounts on /dashboard/graph (gracefully test.fixme if MCP
backend absent). Settings button injection + SPA route to /graph
→ screenshot timeline at t=0/500/1200/2000/2400/3000ms attached
to HTML report. pageerror + console-error listeners catch any
crash (would re-surface FATAL 6 if reintroduced). Three back-to-
back births — no errors, canvas still dispatches clicks.
Run commands:
cd apps/dashboard && npm test # 361/361 pass, ~600ms
cd apps/dashboard && npx playwright test # 9/9 pass, ~25s
Typecheck: 0 errors, 0 warnings. Build: clean adapter-static.
* fix(graph): default /api/graph to newest-memory center, add sort param
memory_timeline PR #37 exposed the same class of bug in the graph
endpoint: the dashboard Graph page (and the /api/graph endpoint it
hits) defaulted to centering on the most-connected memory, ran BFS at
depth 3, and capped the subgraph at 150 nodes. On a mature corpus this
clustered the visualization around a historical hotspot and hid freshly
ingested memories that hadn't accumulated edges yet. User-visible
symptom: TimeSlider on /graph showing "Feb 21 → Mar 1 2026" when the
database actually contains memories through today (Apr 20).
**Backend (`crates/vestige-mcp/src/dashboard/handlers.rs`):**
- `GraphParams` gains `sort: Option<String>` (accepted: "recent" |
"connected", unknown falls back to "recent").
- New internal `GraphSort` enum + case-insensitive `parse()`.
- Extracted `default_center_id(storage, sort)` so handler logic and
tests share the same branching. Recent path picks `get_all_nodes(1,
0)` (ORDER BY created_at DESC). Connected path picks
`get_most_connected_memory`, degrading gracefully to recent if the
DB has no edges yet.
- Default behaviour flipped from "connected" to "recent" — matches
user expectation of "show me my recent stuff".
**Dashboard (`apps/dashboard`):**
- `api.graph()` accepts `sort?: 'recent' | 'connected'` with JSDoc
explaining the rationale.
- `/graph/+page.svelte` passes `sort: 'recent'` when no query or
center_id is active. Query / center_id paths unchanged — they
already carry their own centering intent.
**Tests:** 6 new unit tests in `handlers::tests`:
- `graph_sort_parse_defaults_to_recent` (None, empty, garbage,
"recent", "Recent", "RECENT")
- `graph_sort_parse_accepts_connected_case_insensitive`
- `default_center_id_recent_returns_newest_node` — ingest 3 nodes,
assert newest is picked
- `default_center_id_connected_prefers_hub_over_newest` — wire a hub
node with 3 spokes, then ingest a newer "lonely" node; assert the
hub wins in Connected mode even though it's older
- `default_center_id_connected_falls_back_to_recent_when_no_edges`
— fresh DB with no connections still returns newest, not 404
- `default_center_id_returns_not_found_on_empty_db` — both modes
return 404 cleanly on empty storage
Build + test:
- cargo test -p vestige-mcp --lib handlers:: → 6/6 pass
- cargo test --workspace --lib → 830/830 pass, 0 failed
- cargo clippy -p vestige-core -p vestige-mcp --lib -- -D warnings →
clean
- npm run check → 0 errors, 0 warnings
- npm test → 361/361 pass
Binary already installed at ~/.local/bin/vestige-mcp (copied from
cargo build --release -p vestige-mcp). New Claude Desktop / Code
sessions will pick it up automatically when they respawn their MCP
subprocess. The dashboard HTTP server on port 3927 needs a manual
relaunch from a terminal with the usual pattern:
nohup bash -c 'tail -f /dev/null | \
VESTIGE_DASHBOARD_ENABLED=true ~/.local/bin/vestige-mcp' \
> /tmp/vestige-mcp.log 2>&1 & disown
* feat(v2.4): UI expansion — 8 new surfaces exposing the cognitive engine
Sam asked: "Build EVERY SINGLE MISSING UI PIECE." 10 parallel agents shipped
10 new viewports over the existing Rust backend, then 11 audit agents
line-by-line reviewed each one, extracted pure-logic helpers, fixed ~30
bugs, and shipped 549 new unit tests. Everything wired through the layout
with single-key shortcuts and a live theme toggle.
**Eight new routes**
- `/reasoning` — Reasoning Theater: Cmd+K ask palette → animated 8-stage
deep_reference pipeline + FSRS-trust-scored evidence cards +
contradiction arcs rendered as live SVG between evidence nodes
- `/duplicates` — threshold-driven cluster detector with winner selection,
Merge/Review/Dismiss actions, debounced slider
- `/dreams` — Dream Cinema: trigger dream + scrubbable 5-stage replay
(Replay → Cross-reference → Strengthen → Prune → Transfer) + insight
cards with novelty glow
- `/schedule` — FSRS Review Calendar: 6×7 grid with urgency color
bands (overdue/today/week/future), retention sparkline, expand-day list
- `/importance` — 4-channel radar (Novelty/Arousal/Reward/Attention) with
composite score + top-important list
- `/activation` — live spreading-activation view: search → SVG concentric
rings with decay animation + live-mode event feed
- `/contradictions` — 2D cosmic constellation of conflicting memories,
arcs colored by severity, tooltips with previews
- `/patterns` — cross-project pattern transfer heatmap with category
filters, top-transferred sidebar
**Three layout additions**
- `AmbientAwarenessStrip` — slim top band with retention vitals, at-risk
count, active intentions, recent dream, activity sparkline, dreaming
indicator, Sanhedrin-watch flash. Pure `$derived` over existing stores.
- `ThemeToggle` — dark/light/auto cycle with matchMedia listener,
localStorage persistence, SSR-safe, reduced-motion-aware. Rendered in
sidebar footer next to the connection dot.
- `MemoryAuditTrail` — per-memory Sources panel integrated as a
Content/Audit tab into the existing /memories expansion.
**Pure-logic helper modules extracted (for testability + reuse)**
reasoning-helpers, duplicates-helpers, dream-helpers, schedule-helpers,
audit-trail-helpers, awareness-helpers, contradiction-helpers,
activation-helpers, patterns-helpers, importance-helpers.
**Bugs fixed during audit (not exhaustive)**
- Trust-color inconsistency between EvidenceCard and the page confidence
ring (0.75 boundary split emerald vs amber)
- `new Date('garbage').toLocaleDateString()` returned literal "Invalid Date"
in 3 components — all now return em-dash or raw string
- NaN propagation in `Math.max(0, Math.min(1, NaN))` across clamps
- Off-by-one PRNG in audit-trail seeded mock (seed === UINT32_MAX yielded
rand() === 1.0 → index out of bounds)
- Duplicates dismissals keyed by array index broke on re-fetch; now keyed
by sorted cluster member IDs with stale-dismissal pruning
- Empty-cluster crash in DuplicateCluster.pickWinner
- Undefined tags crash in DuplicateCluster.safeTags
- Debounce timer leak in threshold slider (missing onDestroy cleanup)
- Schedule day-vs-hour granularity mismatch between calendar cell and
sidebar list ("today" in one, "in 1d" in the other)
- Schedule 500-memory hard cap silently truncated; bumped to 2000 + banner
- Schedule DST boundary bug in daysBetween (wall-clock math vs
startOfDay-normalized)
- Dream stage clamp now handles NaN/Infinity/floats
- Dream double-click debounce via `if (dreaming) return`
- Theme setTheme runtime validation; initTheme idempotence (listener +
style-element dedup on repeat calls)
- ContradictionArcs node radius unclamped (trust < 0 or > 1 rendered
invalid sizes); tooltip position clamp (could push off-canvas)
- ContradictionArcs $state closure capture (width/height weren't reactive
in the derived layout block)
- Activation route was MISSING from the repo — audit agent created it
with identity-based event filtering and proper RAF cleanup
- Layout: ThemeToggle was imported but never rendered — now in sidebar
footer; sidebar overflow-y-auto added for the 16-entry nav
**Tests — 549 new, 910 total passing (0 failures)**
ReasoningChain 42 | EvidenceCard 50
DuplicateCluster 64 | DreamStageReplay 19
DreamInsightCard 43 | FSRSCalendar 32
MemoryAuditTrail 45 | AmbientAwareness 60
theme (store) 31 | ContradictionArcs 43
ActivationNetwork 54 | PatternTransfer 31
ImportanceRadar 35 | + existing 361 tests still green
**Gates passed**
- `npm run check`: 0 errors, 0 warnings across 623 files
- `npm test`: 910/910 passing, 22 test files
- `npm run build`: clean adapter-static output
**Layout wiring**
- Nav array expanded 8 → 16 entries (existing 8 + 8 new routes)
- Single-key shortcuts added: R/A/D/C/P/U/X/N (no conflicts with
existing G/M/T/F/E/I/S/,)
- Cmd+K palette search works across all 16
- Mobile nav = top 5 (Graph, Reasoning, Memories, Timeline, Feed)
- AmbientAwarenessStrip mounted as first child of <main>
- ThemeToggle rendered in sidebar footer (was imported-but-unmounted)
- Theme initTheme() + teardown wired into onMount cleanup chain
Net branch delta: 47 files changed, +13,756 insertions, -6 deletions
* chore(release): v2.0.8 "Pulse"
Bundled release: Reasoning Theater wired to the 8-stage deep_reference
cognitive pipeline, Pulse InsightToast, Memory Birth Ritual (v2.3
Terrarium), 7 new dashboard surfaces (/duplicates, /dreams, /schedule,
/importance, /activation, /contradictions, /patterns), 3D graph
brightness system with auto distance-compensation + user slider, and
contradiction-detection + primary-selection hardening in the
cross_reference tool. Intel Mac (x86_64-apple-darwin) also flows through
to the release matrix from PR #43.
Added:
- POST /api/deep_reference — HTTP surface for the 8-stage pipeline
- DeepReferenceCompleted WebSocket event (primary + supporting +
contradicting memory IDs for downstream graph animation)
- /reasoning route, full UI + Cmd+K Ask palette
- 7 new dashboard surfaces exposing the cognitive engine
- Graph brightness slider + localStorage persistence + distance-based
emissive compensation so nodes don't disappear into fog at zoom-out
Fixed:
- Contradiction-detection false positives: adjacent-domain memories no
longer flagged as conflicts (NEGATION_PAIRS wildcards removed,
shared-words floor 2 → 4, topic-sim floor 0.15 → 0.55, STAGE 5
overlap floor 0.15 → 0.4)
- Primary-memory selection: unified composite 0.5 × relevance + 0.2 ×
trust + 0.3 × term_presence with hard topic-term filter, closing the
class of bug where off-topic high-trust memories won queries about
specific subjects
- Graph default-load fallback from sort=recent to sort=connected when
the newest memory is isolated, both backend and client
Changed:
- Reasoning page information hierarchy: chain renders first as hero,
confidence meter + Primary Source citation footer below
- Cargo feature split: embeddings code-only + ort-download | ort-dynamic
backends; defaults preserve identical behavior for existing consumers
- CI release-build now gates PRs too so multi-platform regressions
surface pre-merge
2026-04-23 02:21:11 -05:00
|
|
|
|
mat.emissiveIntensity = 0.8 * brightness;
|
2026-03-01 21:24:10 -06:00
|
|
|
|
} else {
|
|
|
|
|
|
const baseIntensity = 0.3 + node.retention * 0.5;
|
|
|
|
|
|
const breatheIntensity =
|
|
|
|
|
|
baseIntensity + Math.sin(time * (0.8 + node.retention * 0.7)) * 0.1 * node.retention;
|
v2.0.8 Pulse — Reasoning Theater + Pulse Toast + Terrarium + Intel Mac (#44)
* docs: anti-hedging banner + master state-and-plan + UI roadmap
Project-level CLAUDE.md line 1 is now the anti-hedging banner referencing
the global rule at /Users/entity002/CLAUDE.md. When Sam commits to an
execution path (content cadence, shipping velocity, outreach, launch),
forecasts compute as if the commitment is already met — no discounting by
execution probability.
VESTIGE_STATE_AND_PLAN.md (1135 lines, 14 parts): single-page reference
any AI agent can read to plan the next phase without re-reading the repo.
Covers current state of every crate, the 24 MCP tools, the 19 WebSocket
event types, the 8 IDE integrations, and v2.1 through v3.0 roadmap.
UI_ROADMAP_v2.1_v2.2.md: compiled UI-gap analysis from 4 parallel research
agents (April 19). 10 critical UI gaps + 2026 bleeding-edge patterns +
unclaimed territory (menu bar widget, voice-first, AR/VR, time-scrubber).
* feat(v2.2-pulse): InsightToast + multi-process STORAGE docs
Two independent ship items landing together on the v2.2 branch ahead of
the Tuesday launch — a new UI surface that makes Vestige's cognitive
events visible in real time, and honest documentation of the multi-process
safety story that underpins the Stigmergic Swarm narrative.
**InsightToast** (apps/dashboard/src/lib/components/InsightToast.svelte,
apps/dashboard/src/lib/stores/toast.ts):
The dashboard already had a working WebSocket event stream on
ws://localhost:3927/ws that broadcast every cognitive event (dream
completions, consolidation sweeps, memory promotions/demotions, active-
forgetting suppression and Rac1 cascades, bridge discoveries). None of
that was surfaced to a user looking at anything other than the raw feed
view. InsightToast subscribes to the existing eventFeed derived store,
filters the spammy lifecycle events (Heartbeat, SearchPerformed,
RetentionDecayed, ActivationSpread, ImportanceScored, MemoryCreated),
and translates the narrative events into ephemeral toasts with a
bioluminescent colored accent matching EVENT_TYPE_COLORS.
Design notes:
- Rate-limited ConnectionDiscovered at 1.5s intervals (dreams emit many).
- Max 4 visible toasts, auto-dismiss at 4.5-7s depending on event weight.
- Click or Enter/Space to dismiss early.
- Bottom-right on desktop, top-banner on mobile.
- Reduced-motion honored via prefers-reduced-motion.
- Zero new websocket subscriptions — everything piggybacks on the
existing derived store.
Also added a "Preview Pulse" button to Settings -> Cognitive Operations
that fires a synthetic sequence of four toasts (DreamCompleted,
ConnectionDiscovered, MemorySuppressed, ConsolidationCompleted) so
the animation is demoable without waiting for real cognitive activity.
**Multi-Process Safety section in docs/STORAGE.md**:
Grounds the Stigmergic Swarm story with concrete tables of what the
current WAL + 5s busy_timeout configuration actually supports vs what
remains experimental. Key honest points:
- Shared --data-dir + ONE vestige-mcp + N clients is the shipping
pattern for multi-agent coordination.
- Two vestige-mcp processes writing the same file is experimental —
documented with the lsof + pkill recovery path.
- Roadmap lists the three items that would promote it to "supported":
advisory file lock, retry-with-jitter on SQLITE_BUSY, and a
concurrent-writer load test.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm run build: clean static build, adapter-static succeeds
* feat(v2.3-terrarium): Memory Birth Ritual + event pipeline fix
v2.3 "Terrarium" headline feature. When a MemoryCreated event arrives, a
glowing orb materialises in the cosmic center (camera-relative z=-40),
gestates for ~800ms growing from a tiny spark into a full orb, then arcs
along a dynamic quadratic Bezier curve to the live position of the real
node, and on arrival hands off to the existing RainbowBurst + Shockwave +
RippleWave cascade. The target position is re-resolved every frame so
the force simulation can move the destination during flight without the
orb losing its mark.
**New primitive — EffectManager.createBirthOrb()** (effects.ts):
Accepts a camera, a color, a live target-position getter, and an
arrival callback. Owns a sprite pair (outer halo + inner bright core),
both depthTest:false with renderOrder 999/1000 so the orb is always
visible through the starfield and the graph.
- Gestation phase: easeOutCubic growth + sinusoidal pulse, halo tints
from neutral to event color as the ritual charges.
- Flight phase: QuadraticBezierCurve3 with control point at midpoint
raised on Y by 30 + 15% of orb-to-target distance (shooting-star
arc). Sampled with easeInOutQuad. Orb shrinks ~35% approaching target.
- Arrival: fires onArrive callback once, then fades out over 8 frames
while expanding slightly (energy dispersal).
- Caller's onArrive triggers the burst cascade at arrivePos (NOT the
original spawnPos — the force sim may have moved the target during
the ritual, so we re-read nodeManager.positions on arrival).
- Dispose path integrated with existing EffectManager.dispose().
**Event pipeline fix — Graph3D.processEvents()**:
Previously tracked `processedEventCount` assuming APPEND order, but
websocket.ts PREPENDS new events (index 0) and caps the array at
MAX_EVENTS. Result: only the first MemoryCreated event after page
load fired correctly; subsequent ones reprocessed the oldest entry.
Fixed to walk from index 0 until hitting the last-processed event
by reference identity — correct regardless of array direction or
eviction pressure. Events are then processed oldest-first so causes
precede effects. Found while wiring the v2.3 demo button; would have
manifested as "first orb only" in production.
**Demo trigger** (Settings -> Birth Ritual Preview):
Button that calls websocket.injectEvent() with a synthetic
MemoryCreated event, cycling through node types (fact / concept /
pattern / decision / person / place) to showcase the type-color
mapping. Downstream consumers can't distinguish synthetic from real,
so this drives the full ritual end-to-end. Intended for demo clip
recording for the Wednesday launch.
**Test coverage:**
- events.test.ts now tests the v2.3 birth ritual path: spawns 2+
sprites in the scene immediately, and fires the full arrival
cascade after driving the effects.update() loop past the ritual
duration.
- three-mock.ts extended with Vector3.addVectors, Vector3.applyQuaternion,
Color.multiplyScalar, Quaternion, QuadraticBezierCurve3, Texture,
and Object3D.quaternion/renderOrder so production code runs unaltered
in tests.
Build + typecheck:
- npm run check: 0 errors, 0 warnings across 583 files
- npm test: 251/251 pass (net +0 from v2.2)
- npm run build: clean adapter-static output
The Sanhedrin Shatter (anti-birth ritual for hallucination veto) needs
server-side event plumbing and is deferred. Ship this as the Wednesday
visual mic-drop.
* fix(v2.3): 5 FATAL bugs + 4 god-tier upgrades from post-ship audit
Post-ship audit surfaced 6 FATALs and 4 upgrades. Shipping 5 of the 6 +
all 4 upgrades. FATAL 4 (VRAM hemorrhage from un-pooled label canvases
in createTextSprite) is pre-existing, not from this session, and scoped
separately for a proper texture-pool refactor.
**FATAL 1 — Toast Silent Lobotomy** (stores/toast.ts)
Subscriber tracked events[0] only. When Svelte batched multiple events
in one update tick (swarm firing DreamCompleted + ConnectionDiscovered
within the same millisecond), every event but the newest got silently
dropped. Fixed to walk from index 0 until hitting lastSeen — same
pattern as Graph3D.processEvents. Processes oldest-first to preserve
narrative order.
**FATAL 2 — Premature Birth** (graph/nodes.ts + graph/events.ts)
Orb flight is 138 frames; materialization was 30 frames. Node popped
fully grown ~100 frames before orb arrived — cheap UI glitch instead
of a biological birth. Added `addNode(..., { isBirthRitual: true })`
option that reserves the physics slot but hides mesh/glow/label and
skips the materializing queue. New `igniteNode(id)` flips visibility
and enqueues materialization. events.ts onArrive now calls igniteNode
at the exact docking moment, so the elastic spring-up peaks on impact.
**FATAL 3 — 120Hz ProMotion Time-Bomb** (components/Graph3D.svelte)
All physics + effect counters are frame-based. On a 120Hz display every
ritual ran at 2x speed. Added a `lastTime`-based governor in animate()
that early-returns if dt < 16ms, clamping effective rate to ~60fps.
`- (dt % 16)` carry avoids long-term drift. Zero API changes; tonight's
fast fix until physics is rewritten to use dt.
**FATAL 5 — Bezier GC Panic** (graph/effects.ts birth-orb update)
Flight phase allocated a new Vector3 (control point) and a new
QuadraticBezierCurve3 every frame per orb. With 3 orbs in flight that's
360 objects/sec for the GC to collect. Rewrote as inline algebraic
evaluation — zero allocations per frame, identical curve.
**FATAL 6 — Phantom Shockwave** (graph/events.ts)
A 166ms setTimeout fired the 2nd shockwave. If the user navigated
away during that window the scene was disposed, the timer still
fired, and .add() on a dead scene threw unhandled rejection. Dropped
the setTimeout entirely; both shockwaves fire immediately in onArrive
with different scales/colors for the same layered-crash feel.
**UPGRADE 1 — Sanhedrin Shatter** (graph/effects.ts birth-orb update)
If getTargetPos() returns undefined AFTER gestation (target node was
deleted mid-ritual — Stop hook sniping a hallucination), the orb
turns blood-red, triggers a violent implosion in place, and skips
the arrival cascade. Cognitive immune system made visible.
**UPGRADE 2 — Newton's Cradle** (graph/events.ts onArrive)
On docking the target mesh's scale gets bumped 1.8×, so the elastic
materialization + force-sim springs physically recoil instead of the
orb landing silently. The graph flinches when an idea is born into it.
**UPGRADE 3 — Hover Panic** (stores/toast.ts + InsightToast.svelte)
Paused dwell timer on mouseenter/focus, resume on mouseleave/blur.
Stored remaining ms at pause so resume schedules a correctly-sized
timer. CSS pairs via `animation-play-state: paused` on the progress
bar. A toast the user is reading no longer dismisses mid-sentence.
**UPGRADE 4 — Event Horizon Guard** (components/Graph3D.svelte)
If >MAX_EVENTS (200) events arrive in one tick, lastProcessedEvent
falls off the end of the array and the walk consumes ALL 200 entries
as "fresh" — GPU meltdown from 200 simultaneous births. Detect the
overflow and drop the batch with a console.warn, advancing the
high-water mark so next frame is normal.
Build + test:
- npm run check: 0 errors, 0 warnings
- npm test: 251/251 pass
- npm run build: clean static build
* test(v2.3): full e2e + integration coverage for Pulse + Birth Ritual
Post-ship verification pass — five parallel write-agents produced 229 new
tests across vitest units, vitest integration, and Playwright browser e2e.
Net suite: 361 vitest pass (up from 251, +110) and 9/9 Playwright pass on
back-to-back runs.
**toast.test.ts (NEW, 661 lines, 42 tests)**
Silent-lobotomy batch walk proven (multi-event tick processes ALL, not
just newest, oldest-first ordering preserved). Hover-panic pause/resume
with remaining-ms math. All 9 event type translations asserted, all 11
noise types asserted silent. ConnectionDiscovered 1500ms throttle.
MAX_VISIBLE=4 eviction. clear() tears down all timers. fireDemoSequence
staggers 4 toasts at 800ms intervals. vi.useFakeTimers + vi.mock of
eventFeed; vi.resetModules in beforeEach for module-singleton isolation.
**websocket.test.ts (NEW, 247 lines, 30 tests)**
injectEvent adds to front, respects MAX_EVENTS=200 with FIFO eviction,
triggers eventFeed emissions. All 6 derived stores (isConnected,
heartbeat, memoryCount, avgRetention, suppressedCount, uptimeSeconds)
verified — defaults, post-heartbeat values, clearEvents preserves
lastHeartbeat. 13 formatUptime boundary cases (0/59/60/3599/3600/
86399/86400 seconds + negative / NaN / ±Infinity).
**effects.test.ts (EXTENDED, +501 lines, +21 tests, 51 total)**
createBirthOrb full lifecycle — sprite count (halo + core), cosmic
center via camera.quaternion, gestation phase (position lock, opacity
rise, scale easing, color tint), flight Bezier arc above linear
midpoint at t=0.5, dynamic mid-flight target redirect. onArrive fires
exactly once at frame 139. Post-arrival fade + disposal cleans scene
children. Sanhedrin Shatter: target goes undefined mid-flight →
onArrive NEVER called, implosion spawned, halo blood-red, eventual
cleanup. dispose() cleans active orbs. Multiple simultaneous orbs.
Custom gestation/flight frame opts honored. Zero-alloc invariant
smoke test (6 orbs × 150 frames, no leaks).
**nodes.test.ts (EXTENDED, +197 lines, +10 tests, 42 total)**
addNode({isBirthRitual:true}) hides mesh/glow/label immediately,
stamps birthRitualPending sentinel with correct totalFrames +
targetScale, does NOT enqueue materialization. igniteNode flips
visibility + enqueues materialization. Idempotent — second call
no-op. Non-ritual nodes unaffected. Unknown id is safe no-op.
Position stored in positions map while invisible (force sim still
sees it). removeNode + late igniteNode is safe.
**events.test.ts (EXTENDED, +268 lines, +7 tests, 55 total)**
MemoryCreated → mesh hidden immediately, 2 birth-orb sprites added,
ZERO RingGeometry meshes and ZERO Points particles at spawn. Full
ritual drive → onArrive fires, node visible + materializing, sentinel
cleared. Newton's Cradle: target mesh scale exactly 0.001 * 1.8 right
after arrival. Dual shockwave: exactly 2 Ring meshes added. Re-read
live position on arrival — force-sim motion during ritual → burst
lands at the NEW position. Sanhedrin abort path → rainbow burst,
shockwave, ripple wave are NEVER called (vi.spyOn).
**three-mock.ts (EXTENDED)**
Added Color.setRGB — production Three.js has it, the Sanhedrin-
Shatter path in effects.ts uses it. Two write-agents independently
monkey-patched the mock inline; consolidated as a 5-line mock
addition so tests stay clean.
**e2e/pulse-toast.spec.ts (NEW, 235 lines, 6 Playwright tests)**
Navigate /dashboard/settings → click Preview Pulse → assert first
toast appears within 500ms → assert >= 2 toasts visible at peak.
Click-to-dismiss removes clicked toast (matched by aria-label).
Hover survives >8s past the 5.5s dwell. Keyboard Enter dismisses
focused toast. CSS animation-play-state:paused on .toast-progress-
fill while hovered, running on mouseleave. Screenshots attached to
HTML report. Zero backend dependency (fireDemoSequence is purely
client-side).
**e2e/birth-ritual.spec.ts (NEW, 199 lines, 3 Playwright tests)**
Canvas mounts on /dashboard/graph (gracefully test.fixme if MCP
backend absent). Settings button injection + SPA route to /graph
→ screenshot timeline at t=0/500/1200/2000/2400/3000ms attached
to HTML report. pageerror + console-error listeners catch any
crash (would re-surface FATAL 6 if reintroduced). Three back-to-
back births — no errors, canvas still dispatches clicks.
Run commands:
cd apps/dashboard && npm test # 361/361 pass, ~600ms
cd apps/dashboard && npx playwright test # 9/9 pass, ~25s
Typecheck: 0 errors, 0 warnings. Build: clean adapter-static.
* fix(graph): default /api/graph to newest-memory center, add sort param
memory_timeline PR #37 exposed the same class of bug in the graph
endpoint: the dashboard Graph page (and the /api/graph endpoint it
hits) defaulted to centering on the most-connected memory, ran BFS at
depth 3, and capped the subgraph at 150 nodes. On a mature corpus this
clustered the visualization around a historical hotspot and hid freshly
ingested memories that hadn't accumulated edges yet. User-visible
symptom: TimeSlider on /graph showing "Feb 21 → Mar 1 2026" when the
database actually contains memories through today (Apr 20).
**Backend (`crates/vestige-mcp/src/dashboard/handlers.rs`):**
- `GraphParams` gains `sort: Option<String>` (accepted: "recent" |
"connected", unknown falls back to "recent").
- New internal `GraphSort` enum + case-insensitive `parse()`.
- Extracted `default_center_id(storage, sort)` so handler logic and
tests share the same branching. Recent path picks `get_all_nodes(1,
0)` (ORDER BY created_at DESC). Connected path picks
`get_most_connected_memory`, degrading gracefully to recent if the
DB has no edges yet.
- Default behaviour flipped from "connected" to "recent" — matches
user expectation of "show me my recent stuff".
**Dashboard (`apps/dashboard`):**
- `api.graph()` accepts `sort?: 'recent' | 'connected'` with JSDoc
explaining the rationale.
- `/graph/+page.svelte` passes `sort: 'recent'` when no query or
center_id is active. Query / center_id paths unchanged — they
already carry their own centering intent.
**Tests:** 6 new unit tests in `handlers::tests`:
- `graph_sort_parse_defaults_to_recent` (None, empty, garbage,
"recent", "Recent", "RECENT")
- `graph_sort_parse_accepts_connected_case_insensitive`
- `default_center_id_recent_returns_newest_node` — ingest 3 nodes,
assert newest is picked
- `default_center_id_connected_prefers_hub_over_newest` — wire a hub
node with 3 spokes, then ingest a newer "lonely" node; assert the
hub wins in Connected mode even though it's older
- `default_center_id_connected_falls_back_to_recent_when_no_edges`
— fresh DB with no connections still returns newest, not 404
- `default_center_id_returns_not_found_on_empty_db` — both modes
return 404 cleanly on empty storage
Build + test:
- cargo test -p vestige-mcp --lib handlers:: → 6/6 pass
- cargo test --workspace --lib → 830/830 pass, 0 failed
- cargo clippy -p vestige-core -p vestige-mcp --lib -- -D warnings →
clean
- npm run check → 0 errors, 0 warnings
- npm test → 361/361 pass
Binary already installed at ~/.local/bin/vestige-mcp (copied from
cargo build --release -p vestige-mcp). New Claude Desktop / Code
sessions will pick it up automatically when they respawn their MCP
subprocess. The dashboard HTTP server on port 3927 needs a manual
relaunch from a terminal with the usual pattern:
nohup bash -c 'tail -f /dev/null | \
VESTIGE_DASHBOARD_ENABLED=true ~/.local/bin/vestige-mcp' \
> /tmp/vestige-mcp.log 2>&1 & disown
* feat(v2.4): UI expansion — 8 new surfaces exposing the cognitive engine
Sam asked: "Build EVERY SINGLE MISSING UI PIECE." 10 parallel agents shipped
10 new viewports over the existing Rust backend, then 11 audit agents
line-by-line reviewed each one, extracted pure-logic helpers, fixed ~30
bugs, and shipped 549 new unit tests. Everything wired through the layout
with single-key shortcuts and a live theme toggle.
**Eight new routes**
- `/reasoning` — Reasoning Theater: Cmd+K ask palette → animated 8-stage
deep_reference pipeline + FSRS-trust-scored evidence cards +
contradiction arcs rendered as live SVG between evidence nodes
- `/duplicates` — threshold-driven cluster detector with winner selection,
Merge/Review/Dismiss actions, debounced slider
- `/dreams` — Dream Cinema: trigger dream + scrubbable 5-stage replay
(Replay → Cross-reference → Strengthen → Prune → Transfer) + insight
cards with novelty glow
- `/schedule` — FSRS Review Calendar: 6×7 grid with urgency color
bands (overdue/today/week/future), retention sparkline, expand-day list
- `/importance` — 4-channel radar (Novelty/Arousal/Reward/Attention) with
composite score + top-important list
- `/activation` — live spreading-activation view: search → SVG concentric
rings with decay animation + live-mode event feed
- `/contradictions` — 2D cosmic constellation of conflicting memories,
arcs colored by severity, tooltips with previews
- `/patterns` — cross-project pattern transfer heatmap with category
filters, top-transferred sidebar
**Three layout additions**
- `AmbientAwarenessStrip` — slim top band with retention vitals, at-risk
count, active intentions, recent dream, activity sparkline, dreaming
indicator, Sanhedrin-watch flash. Pure `$derived` over existing stores.
- `ThemeToggle` — dark/light/auto cycle with matchMedia listener,
localStorage persistence, SSR-safe, reduced-motion-aware. Rendered in
sidebar footer next to the connection dot.
- `MemoryAuditTrail` — per-memory Sources panel integrated as a
Content/Audit tab into the existing /memories expansion.
**Pure-logic helper modules extracted (for testability + reuse)**
reasoning-helpers, duplicates-helpers, dream-helpers, schedule-helpers,
audit-trail-helpers, awareness-helpers, contradiction-helpers,
activation-helpers, patterns-helpers, importance-helpers.
**Bugs fixed during audit (not exhaustive)**
- Trust-color inconsistency between EvidenceCard and the page confidence
ring (0.75 boundary split emerald vs amber)
- `new Date('garbage').toLocaleDateString()` returned literal "Invalid Date"
in 3 components — all now return em-dash or raw string
- NaN propagation in `Math.max(0, Math.min(1, NaN))` across clamps
- Off-by-one PRNG in audit-trail seeded mock (seed === UINT32_MAX yielded
rand() === 1.0 → index out of bounds)
- Duplicates dismissals keyed by array index broke on re-fetch; now keyed
by sorted cluster member IDs with stale-dismissal pruning
- Empty-cluster crash in DuplicateCluster.pickWinner
- Undefined tags crash in DuplicateCluster.safeTags
- Debounce timer leak in threshold slider (missing onDestroy cleanup)
- Schedule day-vs-hour granularity mismatch between calendar cell and
sidebar list ("today" in one, "in 1d" in the other)
- Schedule 500-memory hard cap silently truncated; bumped to 2000 + banner
- Schedule DST boundary bug in daysBetween (wall-clock math vs
startOfDay-normalized)
- Dream stage clamp now handles NaN/Infinity/floats
- Dream double-click debounce via `if (dreaming) return`
- Theme setTheme runtime validation; initTheme idempotence (listener +
style-element dedup on repeat calls)
- ContradictionArcs node radius unclamped (trust < 0 or > 1 rendered
invalid sizes); tooltip position clamp (could push off-canvas)
- ContradictionArcs $state closure capture (width/height weren't reactive
in the derived layout block)
- Activation route was MISSING from the repo — audit agent created it
with identity-based event filtering and proper RAF cleanup
- Layout: ThemeToggle was imported but never rendered — now in sidebar
footer; sidebar overflow-y-auto added for the 16-entry nav
**Tests — 549 new, 910 total passing (0 failures)**
ReasoningChain 42 | EvidenceCard 50
DuplicateCluster 64 | DreamStageReplay 19
DreamInsightCard 43 | FSRSCalendar 32
MemoryAuditTrail 45 | AmbientAwareness 60
theme (store) 31 | ContradictionArcs 43
ActivationNetwork 54 | PatternTransfer 31
ImportanceRadar 35 | + existing 361 tests still green
**Gates passed**
- `npm run check`: 0 errors, 0 warnings across 623 files
- `npm test`: 910/910 passing, 22 test files
- `npm run build`: clean adapter-static output
**Layout wiring**
- Nav array expanded 8 → 16 entries (existing 8 + 8 new routes)
- Single-key shortcuts added: R/A/D/C/P/U/X/N (no conflicts with
existing G/M/T/F/E/I/S/,)
- Cmd+K palette search works across all 16
- Mobile nav = top 5 (Graph, Reasoning, Memories, Timeline, Feed)
- AmbientAwarenessStrip mounted as first child of <main>
- ThemeToggle rendered in sidebar footer (was imported-but-unmounted)
- Theme initTheme() + teardown wired into onMount cleanup chain
Net branch delta: 47 files changed, +13,756 insertions, -6 deletions
* chore(release): v2.0.8 "Pulse"
Bundled release: Reasoning Theater wired to the 8-stage deep_reference
cognitive pipeline, Pulse InsightToast, Memory Birth Ritual (v2.3
Terrarium), 7 new dashboard surfaces (/duplicates, /dreams, /schedule,
/importance, /activation, /contradictions, /patterns), 3D graph
brightness system with auto distance-compensation + user slider, and
contradiction-detection + primary-selection hardening in the
cross_reference tool. Intel Mac (x86_64-apple-darwin) also flows through
to the release matrix from PR #43.
Added:
- POST /api/deep_reference — HTTP surface for the 8-stage pipeline
- DeepReferenceCompleted WebSocket event (primary + supporting +
contradicting memory IDs for downstream graph animation)
- /reasoning route, full UI + Cmd+K Ask palette
- 7 new dashboard surfaces exposing the cognitive engine
- Graph brightness slider + localStorage persistence + distance-based
emissive compensation so nodes don't disappear into fog at zoom-out
Fixed:
- Contradiction-detection false positives: adjacent-domain memories no
longer flagged as conflicts (NEGATION_PAIRS wildcards removed,
shared-words floor 2 → 4, topic-sim floor 0.15 → 0.55, STAGE 5
overlap floor 0.15 → 0.4)
- Primary-memory selection: unified composite 0.5 × relevance + 0.2 ×
trust + 0.3 × term_presence with hard topic-term filter, closing the
class of bug where off-topic high-trust memories won queries about
specific subjects
- Graph default-load fallback from sort=recent to sort=connected when
the newest memory is isolated, both backend and client
Changed:
- Reasoning page information hierarchy: chain renders first as hero,
confidence meter + Primary Source citation footer below
- Cargo feature split: embeddings code-only + ort-download | ort-dynamic
backends; defaults preserve identical behavior for existing consumers
- CI release-build now gates PRs too so multi-platform regressions
surface pre-merge
2026-04-23 02:21:11 -05:00
|
|
|
|
mat.emissiveIntensity = breatheIntensity * brightness * distanceBoost;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Opacity also gets the distance boost (capped at 1.0) so the node
|
|
|
|
|
|
// body stays visible against the dark void at far zoom.
|
|
|
|
|
|
const baseOpacity = 0.3 + node.retention * 0.7;
|
|
|
|
|
|
mat.opacity = Math.min(1.0, baseOpacity * brightness * distanceBoost);
|
|
|
|
|
|
|
|
|
|
|
|
// Mirror the boost onto the glow sprite so the halo tracks the core.
|
|
|
|
|
|
const glow = this.glowMap.get(id);
|
|
|
|
|
|
if (glow) {
|
|
|
|
|
|
const glowMat = glow.material as THREE.SpriteMaterial;
|
|
|
|
|
|
const baseGlow = 0.3 + node.retention * 0.35;
|
|
|
|
|
|
glowMat.opacity = Math.min(0.95, baseGlow * brightness * distanceBoost);
|
2026-03-01 21:24:10 -06:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Distance-based label visibility
|
|
|
|
|
|
this.labelSprites.forEach((sprite, id) => {
|
2026-03-03 14:04:31 -06:00
|
|
|
|
if (animatingIds.has(id)) return;
|
2026-03-01 21:24:10 -06:00
|
|
|
|
const pos = this.positions.get(id);
|
|
|
|
|
|
if (!pos) return;
|
|
|
|
|
|
const dist = camera.position.distanceTo(pos);
|
|
|
|
|
|
const mat = sprite.material as THREE.SpriteMaterial;
|
|
|
|
|
|
const targetOpacity =
|
|
|
|
|
|
id === this.hoveredNode || id === this.selectedNode
|
|
|
|
|
|
? 1.0
|
|
|
|
|
|
: dist < 40
|
|
|
|
|
|
? 0.9
|
|
|
|
|
|
: dist < 80
|
|
|
|
|
|
? 0.9 * (1 - (dist - 40) / 40)
|
|
|
|
|
|
: 0;
|
|
|
|
|
|
mat.opacity += (targetOpacity - mat.opacity) * 0.1;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
getMeshes(): THREE.Mesh[] {
|
|
|
|
|
|
return Array.from(this.meshMap.values());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dispose() {
|
|
|
|
|
|
this.group.traverse((obj) => {
|
|
|
|
|
|
if (obj instanceof THREE.Mesh) {
|
|
|
|
|
|
obj.geometry?.dispose();
|
|
|
|
|
|
(obj.material as THREE.Material)?.dispose();
|
|
|
|
|
|
} else if (obj instanceof THREE.Sprite) {
|
|
|
|
|
|
(obj.material as THREE.SpriteMaterial)?.map?.dispose();
|
|
|
|
|
|
(obj.material as THREE.Material)?.dispose();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-03-03 14:04:31 -06:00
|
|
|
|
this.materializingNodes = [];
|
|
|
|
|
|
this.dissolvingNodes = [];
|
|
|
|
|
|
this.growingNodes = [];
|
2026-03-01 21:24:10 -06:00
|
|
|
|
}
|
|
|
|
|
|
}
|