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
+34
View File
@@ -48,6 +48,36 @@ export interface PodGraphMetric {
detail: string;
}
/** The five stages of PodMan's continual-learning loop, in order. */
export type LearningStageKey = 'observe' | 'store' | 'predict' | 'outcome' | 'adapt';
/** One stage of the learning-loop rail (observe→store→predict→outcome→adapt). */
export interface LearningStage {
key: LearningStageKey;
/** UPPERCASE display title, e.g. "OBSERVE". */
title: string;
/** Headline figure for the stage, e.g. "5/s" or "124". */
value: string;
/** One-line detail under the title. */
detail: string;
/** True for the single most-recently-active stage (pulses in the UI). */
active: boolean;
}
/** Kind of an activity-stream entry (drives the colored tag). */
export type ActivityKind = 'editing' | 'collision' | 'warns' | 'outcome' | 'learned_from';
/** One time-tagged entry in the activity stream. */
export interface ActivityEvent {
/** Stable id (source doc id + kind) so the UI can animate diffs. */
id: string;
/** ISO timestamp the event happened. */
at: string;
kind: ActivityKind;
/** Human-readable line, e.g. "Yahya opened auth.ts — unpushed changes". */
text: string;
}
/** A point-in-time render of a pod's team_model. */
export interface PodGraph {
podId: string;
@@ -56,6 +86,10 @@ export interface PodGraph {
nodes: PodGraphNode[];
edges: PodGraphEdge[];
metrics: PodGraphMetric[];
/** Continual-learning loop counts (observe→…→adapt). Additive/optional. */
loop?: LearningStage[];
/** Recent activity feed, most-recent first, capped ~8. Additive/optional. */
activity?: ActivityEvent[];
}
/** One node as a standalone document in the `graph_nodes` collection. */
+4
View File
@@ -18,6 +18,10 @@ export type {
PodGraphNodeKind,
PodGraphEdgeKind,
PodGraphNodeStatus,
LearningStage,
LearningStageKey,
ActivityEvent,
ActivityKind,
GraphNodeDoc,
GraphEdgeDoc,
} from './graph.js';