feat(graph): dynamic force-directed Team-memory graph + learning-loop & activity rails

Rebuild the live "Team memory" view so the light/real-data version matches the
dark Bauhaus mock and the graph is genuinely DYNAMIC instead of dead static columns.

Frontend (frontend/src/components/graph/*, composed into GraphView.tsx):
- forceSim.ts: a tiny dependency-free force layout (charge repulsion, link springs,
  centroid recentering + gentle pull, 2-pass collision, bounds clamp, alpha anneal).
  No d3-force dependency added — keeps the shared pnpm-lock untouched so CI's
  frozen-lockfile install and the deploy path are unaffected.
- GraphCanvas.tsx: SVG render driven by the sim — draggable + pinnable nodes
  (double-click to release), curved edges that fan parallel pairs, weight-sized
  geometric node shapes, fade-in on new nodes/edges, animated learned_from dash,
  risk-path lighting with the rest dimmed, label collision-avoidance.
- MetricsRail / LearningLoop / ActivityStream / SelectedNodePanel / encoding.ts:
  the mock's rails + stream + detail panel, light shadcn (ToggleGroup, ScrollArea,
  Badge, Button) on theme tokens; only the SVG is bespoke.
- GraphView polls /api/pods/:id/graph every 5s and diffs (positions preserved across
  refreshes), with a best-effort ws /api/events nudge. A stale selection (node gone
  across a poll) is dropped so the canvas can't dim entirely.

Backend (additive — materializer de-noise untouched):
- live.ts: buildLoop() (observe→store→predict→outcome→adapt counts, deepest-recent
  stage active) and buildActivity() (time-sorted typed feed, same isFilePath /
  ENGINEER_NOISE / signature de-noise) emitted alongside nodes/edges/metrics.
- demo.ts: fallback loop + activity so the panels render on the demo path.
- shared/src/graph.ts: additive optional PodGraph.loop / .activity + LearningStage /
  ActivityEvent types.

Verified: pnpm lint + -r typecheck + -r build pass; Playwright on the dev build
confirmed force layout (distinct positions, ticks on load under StrictMode), drag,
risk-mode dimming (opacity 0.14), selection panel, legend, and no overlaps on both
the clean demo and the 31-node live hairball.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
sb-iam
2026-06-27 23:27:54 -07:00
parent 03a8af6da0
commit c97863e05e
13 changed files with 1362 additions and 253 deletions
+5
View File
@@ -8,3 +8,8 @@ export async function fetchPodGraph(podId: string): Promise<PodGraph> {
if (!res.ok) throw new Error(`graph request failed: ${res.status}`);
return res.json() as Promise<PodGraph>;
}
/** WebSocket URL for the live event bus — used to nudge the graph to refetch. */
export function backendEventsUrl(): string {
return `${BACKEND_URL.replace(/^http/, 'ws')}/api/events`;
}