fix: comprehensive audit fixes for dashboard and backend

Backend:
- Emit WebSocket events from REST delete/promote/demote handlers
- Emit DreamStarted/ConsolidationStarted from MCP tool dispatch
- Add path validation in backup_to() for defense-in-depth

Dashboard:
- Fix ConnectionDiscovered field names (source_id/target_id)
- Fix $effect → onMount in settings (prevents infinite loop)
- Fix $derived → $derived.by in RetentionCurve
- Fix field name mismatches in settings (nodesProcessed, etc.)
- Fix nested <button> → <span role="button"> in memories
- Fix unhandled Promise rejection in stats consolidation
- Add missing EVENT_TYPE_COLORS entries
- Add Three.js resource disposal and event listener cleanup
- Eliminate duplicate root page, redirect to /graph
- Update nav links and keyboard shortcuts to /graph

All 734+ tests passing, 22MB binary, zero build warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sam Valladares 2026-02-22 15:50:47 -06:00
parent 22831af509
commit ec2af6e71b
220 changed files with 347 additions and 443 deletions

View file

@ -69,6 +69,19 @@
onDestroy(() => {
cancelAnimationFrame(animationId);
window.removeEventListener('resize', onResize);
container?.removeEventListener('pointermove', onPointerMove);
container?.removeEventListener('click', onClick);
// Dispose Three.js resources to prevent GPU memory leaks
scene?.traverse((obj: THREE.Object3D) => {
if (obj instanceof THREE.Mesh || obj instanceof THREE.InstancedMesh) {
obj.geometry?.dispose();
if (Array.isArray(obj.material)) {
obj.material.forEach((m: THREE.Material) => m.dispose());
} else if (obj.material) {
(obj.material as THREE.Material).dispose();
}
}
});
renderer?.dispose();
composer?.dispose();
});
@ -554,9 +567,9 @@
break;
}
case 'ConnectionDiscovered': {
const data = event.data as { source?: string; target?: string };
const srcPos = data.source ? nodePositions.get(data.source) : null;
const tgtPos = data.target ? nodePositions.get(data.target) : null;
const data = event.data as { source_id?: string; target_id?: string };
const srcPos = data.source_id ? nodePositions.get(data.source_id) : null;
const tgtPos = data.target_id ? nodePositions.get(data.target_id) : null;
if (srcPos && tgtPos) {
createConnectionFlash(srcPos, tgtPos, new THREE.Color(0xf59e0b));
}

View file

@ -16,7 +16,7 @@
}
// Generate SVG path for the decay curve
let curvePath = $derived(() => {
let curvePath = $derived.by(() => {
const points: string[] = [];
const maxDays = Math.max(stability * 3, 30);
const padding = 4;
@ -56,10 +56,10 @@
<line x1="4" y1="{4 + (height - 8) * 0.8}" x2="{width - 4}" y2="{4 + (height - 8) * 0.8}" stroke="#ef444430" stroke-width="0.5" stroke-dasharray="2,4" />
<!-- Decay curve -->
<path d={curvePath()} fill="none" stroke="#6366f1" stroke-width="2" stroke-linecap="round" />
<path d={curvePath} fill="none" stroke="#6366f1" stroke-width="2" stroke-linecap="round" />
<!-- Fill under curve -->
<path d="{curvePath()} L{width - 4},{height - 4} L4,{height - 4} Z" fill="url(#curveGrad)" opacity="0.15" />
<path d="{curvePath} L{width - 4},{height - 4} L4,{height - 4} Z" fill="url(#curveGrad)" opacity="0.15" />
<!-- Current retention dot -->
<circle cx="4" cy="{4 + (1 - retention) * (height - 8)}" r="3" fill={retColor(retention)} />

View file

@ -196,12 +196,17 @@ export const EVENT_TYPE_COLORS: Record<string, string> = {
MemoryCreated: '#10b981',
MemoryUpdated: '#3b82f6',
MemoryDeleted: '#ef4444',
MemoryPromoted: '#22c55e',
MemoryDemoted: '#f97316',
SearchPerformed: '#6366f1',
DreamStarted: '#8b5cf6',
DreamProgress: '#7c3aed',
DreamCompleted: '#a855f7',
ConsolidationStarted: '#f59e0b',
ConsolidationCompleted: '#f97316',
RetentionDecayed: '#ef4444',
ConnectionDiscovered: '#06b6d4',
ActivationSpread: '#14b8a6',
ImportanceScored: '#ec4899',
Heartbeat: '#6b7280',
};