From ad73659389f0593a764e22f4294c1dd2300b4399 Mon Sep 17 00:00:00 2001 From: sb-iam <59984144+sb-iam@users.noreply.github.com> Date: Sat, 27 Jun 2026 22:28:01 -0700 Subject: [PATCH] docs: deep redesign brief for the live Team-memory graph Self-contained handoff for a fresh session: rebuild the light/real-data graph to the dark Bauhaus mock's quality + structure (metrics rail, learning-loop rail, activity stream, selected-node panel) and make the graph dynamic (force-directed + animated), in light shadcn. Captures architecture, data, the dynamic-layout options, what to feed the new panels, files, gotchas, and acceptance criteria. Co-Authored-By: Claude Opus 4.8 --- docs/team-memory-redesign.md | 293 +++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 docs/team-memory-redesign.md diff --git a/docs/team-memory-redesign.md b/docs/team-memory-redesign.md new file mode 100644 index 0000000..842f92b --- /dev/null +++ b/docs/team-memory-redesign.md @@ -0,0 +1,293 @@ +# Team Memory Graph — Redesign Brief (fresh-session handoff) + +> **You are a fresh Claude Code session with no prior context. Read this whole file first.** +> Your job: rebuild the **live "Team memory" graph view** so the **light/real-data** version is as +> polished and functional as the original **dark Bauhaus mock**, and make the graph **dynamic** +> (force-directed + animated), not the current dead static-column layout. +> **Do not rewrite the backend materializer — it is good.** The problem is 100% the frontend rendering. + +--- + +## 0. Mission (one paragraph) + +PodMan's "Team memory" is a per-pod graph of who owns/edits which files, where work collides, and what +PodMan learned from accepted interventions — the continual-learning loop made legible in 10 seconds. +A **dark Bauhaus mock** of this view looks great (clean 3-panel layout, a learning-loop rail, an activity +stream, a readable graph). The **shipped light version on real data looks terrible** (a hairball of red +edges, overlapping labels, a static lifeless layout, and it's missing the learning-loop rail + activity +stream entirely). Make the light version match the mock's structure/polish/functionality, in the app's +**light shadcn theme**, and make the **graph dynamic** (organic force-directed layout, draggable, +animated transitions). Keep using **real data** from the existing materializer. + +--- + +## 1. The two reference points + +### A. The dark Bauhaus mock = what "good" looks like (target structure) + +A single dark card titled **"PODMAN — CONTINUAL-LEARNING OBSERVATORY"** with a `LIVE · POD demo-pod` +status. Layout: + +- **Left rail — WORKFLOW METRICS**: a vertical stack of bordered cards, each a big numeral + an + UPPERCASE tracked label + a one-line detail, with a colored left-accent bar: + `03 PODS WATCHED`, `05 ENGINEERS LIVE`, `02 COLLISIONS OPEN (▲ auth.ts critical)`, + `01 INTERVENTION SENT`, `86% ACCEPT RATE (▲ +14% this session)`, `124 MEMORY VECTORS`. +- **Center — the GRAPH**: sparse, geometric, readable. Node shapes encode kind + (engineer = filled square, file = outlined square, feature = circle, collision = triangle, + intervention = diamond). One **risk path is lit** (Karti+Yahya → auth.ts → collision → sync PR → + `learned_from`), everything else dimmed. Edges color-coded (`collides` red, `warns` amber/orange, + `learned_from` dashed violet, `owns` blue, `editing` paper, `touches` grey). +- **Right rail — LEARNING LOOP**: a vertical 5-step stepper with the active step highlighted/pulsing: + `01 OBSERVE (vision → 5 contexts/s)` → `02 STORE (124 vectors · Atlas)` → + `03 PREDICT (2 collisions flagged)` [active] → `04 OUTCOME (1 accepted · 0 dismissed)` → + `05 ADAPT (Karti→auth ownership +)`. Arrows between steps. +- **Bottom-left — ACTIVITY STREAM**: a time-stamped feed with colored kind-tags: + `15:48 EDITING Yahya opened auth.ts — unpushed changes detected`, + `15:48 COLLISION Critical overlap on auth.ts · Karti + Yahya`, + `15:49 WARNS PodMan spoke: "open a sync PR?" → card sent`, + `15:49 OUTCOME Sync PR accepted by the pod`, + `15:49 LEARNED_FROM Memory updated: Karti owns auth (confidence ↑)`. +- **Bottom-right — SELECTED NODE**: click a node → kind / name / relationships count / severity / a + one-line "why" (`Two engineers editing the same file before push — the signal git can't see.`). +- **Legend**: engineer / file / feature / collision / intervention · collides / warns / learned_from. + +It reads in 10 seconds because it is **sparse, color-coded, and tells the loop story** with the rails + +stream, not just a node blob. (The full mock HTML/CSS is reproduced in **Appendix A** — port its +structure to light shadcn.) + +### B. The shipped light version = what's wrong (the thing to fix) + +Same data, but: a **hairball** — every engineer fans red `collides` edges to ~6 collision triangles +(all labeled `sync PR`); **file labels overlap** in a dim middle column; the graph uses a **static +deterministic column layout** (`x` by kind, `y` evenly spread) so it looks dead/lifeless; and it is +**missing the LEARNING LOOP rail and the ACTIVITY STREAM** entirely — it's just a metrics rail + the bare +graph + a selected-node panel. (Two already-fixed-on-branch items: garbage collision labels +`infra/README.md### Running the git watcher` and full-path bleed — see PR #7 / commit `a21a289`, +`shortLabel` in `live.ts`. Build on top of that, don't redo it.) + +--- + +## 2. The gap to close (light vs mock) + +| Mock has | Light version | Action | +| ---------------------------------------- | ------------------------------- | ---------------------------------- | +| Workflow metrics rail | ✅ has it (3 metrics) | keep; restyle to match | +| **Learning loop rail (observe→…→adapt)** | ❌ missing | **build it** (needs live counts) | +| **Activity stream feed** | ❌ missing | **build it** (needs an event feed) | +| Selected-node panel | ✅ has it | keep | +| **Dynamic / animated graph** | ❌ static columns | **replace the layout** | +| Sparse, lit "risk path" | partial (Risk-path mode exists) | improve emphasis + spacing | +| Legend | ✅ | keep | + +--- + +## 3. Current architecture (build on this — do NOT rewrite the materializer) + +**Backend (good, keep):** + +- `backend/src/graph/live.ts` — `materializePodGraph(podId)`: builds the graph from the real Mongo + collections (`pods`, `engineer_states`, `observations`, `collisions`, `interventions`, `outcomes`). + It already de-noises hard: collapses collisions by `memorySignature`, caps to 8, collapses + interventions to one per collision, filters junk files (`isFilePath`), prunes test-artifact engineers + (`ENGINEER_NOISE`), caps files to 9, short labels (`shortLabel`). Output is ~20 clean nodes for + `demo-pod`. **This is solid — extend it, don't replace it.** +- `backend/src/graph/store.ts` — `loadPodGraph(podId)`: live materializer → seeded `team_model.graph` + → demo fallback (`createDemoPodGraph`). Plus `reachFrom` (`$graphLookup`). +- Route: `GET /api/pods/:id/graph` returns `PodGraph` (also `/graph/reach/:node`). +- `backend/src/memory/db.ts` — `collections()`, `getGitStates(podId)`, `getDb()`. +- WS bus: `backend/src/server.ts` hosts `ws /api/events` (the agent + `/api/outcome` broadcast here). + +**Frontend (this is where the work is):** + +- `frontend/src/components/GraphView.tsx` — **the thing you redesign** (~90% of the work). Currently: + fetches `/api/pods/:id/graph`, renders a bespoke SVG with the static column layout, Risk/Learning/Whole + toggles, a metrics rail, a selected-node panel, a legend. Composed from shadcn primitives (`Button`, + `Badge`) + Tailwind utilities. Theme-aware via shadcn tokens. +- `frontend/src/lib/graph.ts` — `fetchPodGraph(podId)`. +- Opened from each `PodCard`'s `⋯` menu → "Team memory" (`onOpenGraph(pod.id)` in + `frontend/src/App.tsx`). It is a conditional render (no route). + +**Data contract** (`shared/src/graph.ts`): + +```ts +PodGraph = { podId, generatedAt, nodes: PodGraphNode[], edges: PodGraphEdge[], metrics: PodGraphMetric[] } +PodGraphNode = { id, kind, label, summary, weight 0..1, status: 'stable'|'active'|'risk'|'learned', x, y } +// kind: 'engineer'|'feature'|'file'|'collision'|'intervention' +PodGraphEdge = { id, source, target, kind, label, strength 0..1 } +// kind: 'owns'|'editing'|'touches'|'collides'|'warns'|'learned_from' +PodGraphMetric = { label, value, detail } +``` + +**Theme / components (HARD RULE):** the app is **light shadcn**, built from the **ruixen registry** — +add primitives with `npx shadcn@latest add "https://ruixen.com/r/[component]"` and compose from +`@/components/ui/*` (`Button`, `Badge`, `Card`, `Tabs`, `ToggleGroup`, etc.) using the design tokens +(`var(--card)` / `--foreground` / `--muted-foreground` / `--border`, `--chart-1..5`). Only the SVG/canvas +graph is bespoke. Match `frontend/src/App.tsx`'s `StatPill`/`BriefLine` utility patterns. + +--- + +## 4. Target design (build this) + +A light shadcn page with the **mock's structure**: + +``` +┌───────────────────────────────────────────────────────────────────────┐ +│ Header: "Team memory · What PodMan learned · " [← Pods] │ +├───────────────────────────────────────────────────────────────────────┤ +│ Toggles: Risk path | Learning edges | Whole graph (keep) │ +├──────────────┬──────────────────────────────────┬─────────────────────┤ +│ WORKFLOW │ │ LEARNING LOOP │ +│ METRICS │ DYNAMIC GRAPH CANVAS │ observe→store→ │ +│ (cards) │ (force-directed + animated) │ predict→outcome→ │ +│ │ │ adapt (active pulses)│ +├──────────────┴──────────────────────────────────┴─────────────────────┤ +│ ACTIVITY STREAM (time-tagged feed) │ SELECTED NODE (detail) │ +└───────────────────────────────────────────────────────────────────────┘ +``` + +- **Light shadcn** throughout (theme-aware; follows dark mode if the app ever toggles). Keep the + geometric **node-shape + color encoding** (it's the legible part) but on light surfaces with the + app's hues (engineer blue `#2563eb`, file slate outline `#475569`, feature amber `#d97706`, + collision red `#dc2626`, intervention violet `#7c3aed`; edges: collides red, warns amber, + learned_from dashed violet, owns blue, editing slate, touches faint slate). +- **Default to "Risk path"**: light the collision→intervention→`learned_from` chain; dim the rest. + +--- + +## 5. Make the graph DYNAMIC (the headline new requirement) + +The static column layout (`live.ts` `layout()` sets `x`/`y` by kind) looks dead. Replace the frontend +rendering with a **dynamic** graph. Pick one (recommended order): + +1. **`d3-force` force-directed (recommended).** Add `d3-force` (small). Run a force simulation on the + `PodGraph` nodes/edges: link force (by `edge.strength`), charge/repulsion, center, collision radius + (by `node.weight`). Render nodes/edges as SVG, update positions per tick. Make nodes **draggable** + (pin on drag). Animate new nodes/edges fading in on data refresh, and the `learned_from` dashed + stroke animating. Ignore the server's `x`/`y` (or use them as initial positions). Keep node shapes. +2. `react-force-graph` / `force-graph` (canvas) — heavier, faster for big graphs; overkill at ~20 nodes + but fine. +3. A custom animated **layered** layout (engineers → files → collisions → interventions columns, but + with curved edges, eased position transitions on refresh, and gentle idle motion). Lighter-weight + than d3-force; still feels alive if you animate transitions. + +**Realtime/dynamic data:** poll `GET /api/pods/:id/graph` every ~5s and **animate the diff** between +snapshots (don't hard-replace). Optionally subscribe to `ws /api/events` for instant nudges. New +collisions/interventions should visibly animate in; the `learned_from` edge + gold node should pop on a +new accepted outcome. + +**De-hairball:** even force-directed, ~6 collisions × 3 engineers = many `collides` edges. Mitigate: +bundle/curve edges, lower non-risk edge opacity, default to Risk-path emphasis, size nodes by `weight`, +and keep label collision-avoidance (offset labels, hide on overlap, show on hover/select). + +--- + +## 6. Data for the new panels (extend the materializer or add endpoints) + +The mock's **Learning Loop** and **Activity Stream** need data the current `PodGraph` doesn't carry. Two +options: (a) extend `materializePodGraph` to also return `loop` + `activity`, or (b) add small endpoints. +Recommended: extend the return type (additive to `shared/src/graph.ts`). + +- **Learning loop counts** (`observe→store→predict→outcome→adapt`): + - observe = recent `observations` count (e.g. last 60s) / rate + - store = `memory_vectors` or `collisions.embedding` count (Voyage vectors) + - predict = open `collisions` (distinct signatures) count + - outcome = `outcomes` accepted vs dismissed counts + - adapt = `team_model.ownership` entries / learned owners count + - mark the "active" stage = the most recent activity. +- **Activity stream**: merge + time-sort recent events from `collisions.detectedAt`, + `interventions.createdAt`, `outcomes.recordedAt`, `engineer_states.gitUpdatedAt` → a typed feed + `{ at, kind: 'editing'|'collision'|'warns'|'outcome'|'learned_from', text }`. Cap to ~8 most recent. + +(`backend/src/memory/db.ts` `collections()` gives you `observations/collisions/interventions/outcomes`; +`getGitStates` gives engineer_states; `team_model` is `db.collection('team_model')`.) + +--- + +## 7. Files to touch + +- **`frontend/src/components/GraphView.tsx`** — the redesign (force-directed graph + 3-panel layout + + learning-loop rail + activity stream). May split into `GraphCanvas.tsx`, `LearningLoop.tsx`, + `ActivityStream.tsx`, `MetricsRail.tsx`. +- **`frontend/src/lib/graph.ts`** — add fetches for loop/activity if you add endpoints. +- **`backend/src/graph/live.ts`** (extend, don't rewrite) — emit `loop` + `activity` in the result; + keep all the de-noise. +- **`shared/src/graph.ts`** — add `loop`/`activity` types to `PodGraph` (additive). +- **deps** — `d3-force` (+ `@types/d3-force`) via pnpm in `frontend`. +- Possibly add a ruixen primitive (e.g. `timeline`, `stepper`) via the shadcn CLI if one fits. + +--- + +## 8. Constraints & gotchas (READ — these will bite you) + +- **The materializer is good — do not rewrite it.** It already de-noises (caps, collapse-by-signature, + engineer/file filters, short labels). The bad UI is the **frontend layout/render**, not the data. +- **PWA service worker caches aggressively** — after any deploy, hard-refresh (Cmd-Shift-R) or test in a + private window, or you'll think nothing changed. +- **Deploy = merge to `main`** (DO `deploy_on_push: true`). `main` is shared by ~4 engineers and moves + fast. Work on a branch, open a PR, merge. Don't push to `main` directly. +- **`learned_from` "money" edge won't render on `demo-pod`** right now — its one accepted outcome is + orphaned (points at a collision deleted by test churn). It needs **one intact accept flow** (real + collision → intervention → someone clicks Accept) to draw. To demo, seed a clean chain or clear test + docs (writes to shared Atlas — confirm scope first). +- **Atlas creds rotate frequently** — `podman/.env`'s `MONGODB_URI` may be stale; the **deployed env** + has the working one. If local Mongo auth fails, that's why. +- **Build verification**: some sandboxes can't run `pnpm`/`vite`/shadcn deps (`lucide-react`, + `@radix-ui`). Verify the frontend with `pnpm build` in a real env or CI before merging. Backend + typecheck excludes uninstalled `ws`/`sharp`/`@livekit/rtc-node` noise. +- **Compose from ruixen/shadcn primitives** (`npx shadcn add ruixen.com/r/[component]`, + `@/components/ui/*`); only the SVG/canvas graph is bespoke. Match `StatPill`/`BriefLine` in `App.tsx`. +- **Light theme + tokens** — never hardcode dark colors for chrome; use `var(--card)/--foreground/…`. + Keep fixed semantic hues only for the node/edge kind encoding. + +--- + +## 9. Acceptance criteria + +- Light Team-memory page matches the mock's structure: **metrics rail + dynamic graph + learning-loop + rail + activity stream + selected-node panel + legend**. +- **Graph is dynamic**: force-directed (or animated layered), **draggable**, **animates** new + nodes/edges and refresh transitions; **no overlapping labels, no hairball**. +- Reads in 10s; the **risk/money path is obvious** by default. +- **Light shadcn** theme, theme-aware; composed from ruixen primitives. +- Uses **real data** from `materializePodGraph`; graceful demo fallback when empty. +- `pnpm build` + typechecks pass; deploys; verified after a hard-refresh. + +--- + +## 10. Suggested first moves for the new session + +1. Read this file + `docs/graph.md` + `docs/live-ui-spec.md` (R1/R2 sections) + `CLAUDE.md`. +2. `git fetch`; branch off `main` (or `feat/live-graph-glue`, which has the latest graph work). +3. Hit the live data once: `curl https://165-22-129-249.sslip.io/api/pods/demo-pod/graph` — that's the + real `PodGraph` you'll render. +4. Build a `d3-force` `GraphCanvas` first (replace the static layout), get it draggable + animated. +5. Add `LearningLoop` + `ActivityStream` (extend the materializer to feed them). +6. Polish to the mock; `pnpm build`; PR → main → redeploy → hard-refresh. + +--- + +## Appendix A — the dark mock (reference structure to port to light) + +The mock is a single dark card. Structure + the exact content to reproduce (in light shadcn): + +- Header: brand glyph (blue square + amber circle + red triangle + outlined square) + `PODMAN / +CONTINUAL-LEARNING OBSERVATORY` + `● LIVE · POD demo-pod`. +- Grid `180px 1fr 196px`: **metrics rail** | **graph** | **learning-loop rail**. +- Metrics cards: big `Archivo`-weight numeral, uppercase tracked label, muted detail, colored + left-accent (blue/red/yellow/violet/green). +- Graph: SVG, geometric node shapes by kind, color-coded edges, one lit risk path, dim others; click a + node → highlight its incident edges + neighbors, fill the selected-node panel. +- Learning-loop rail: 5 bordered steps with number + UPPERCASE title + muted sub; the active step has a + pulsing left bar; `↓` arrows between. +- Legend row (node kinds + edge kinds). +- Activity stream (time + colored tag + text) and selected-node panel below. + +Palette used (port to shadcn tokens for chrome; keep these as node/edge hues): +`bg #0c0c0e`, `panel #141417`, `line #2a2a31`, `paper/text #ECE7DA`, `muted #8d897e`, +`blue #3B5BFF`, `red #E2403A`, `amber #F6C445`, `violet #8b6cff`, `green #46c07a`. +For light: chrome → `var(--card)/--foreground/--border/--muted-foreground`; node/edge hues → +blue `#2563eb`, slate `#475569`, amber `#d97706`, red `#dc2626`, violet `#7c3aed` (light-readable). + +> The dark mock was a `show_widget` demo (not a saved file). If you want the literal HTML/CSS, ask the +> user to paste it, or reconstruct from this appendix — the **structure + content above is the spec**. +> Goal: same structure, same legibility, **light theme + dynamic graph**.