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 <noreply@anthropic.com>
This commit is contained in:
sb-iam
2026-06-27 22:28:01 -07:00
parent a21a2895c4
commit ad73659389
+293
View File
@@ -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 · <pod>" [← 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**.