From a21a2895c430cee70ad35e3665b6491a1e545b40 Mon Sep 17 00:00:00 2001 From: sb-iam <59984144+sb-iam@users.noreply.github.com> Date: Sat, 27 Jun 2026 22:14:42 -0700 Subject: [PATCH] fix(graph): readable labels + file cap (de-hairball the viz) The deployed graph was an unreadable mess: full file paths centered on nodes bled across columns, and collision labels showed garbage vision symbols ("infra/README.md### Running the git watcher"). - node labels are now the last two path segments (full path in summary) - collision labels are just the file (drop the junk #symbol) - cap file nodes to 9 (collision files prioritized) demo-pod: 22 -> 20 clean nodes; no label bleed. Co-Authored-By: Claude Opus 4.8 --- backend/src/graph/live.ts | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/backend/src/graph/live.ts b/backend/src/graph/live.ts index 0eadcd3..6cbb74d 100644 --- a/backend/src/graph/live.ts +++ b/backend/src/graph/live.ts @@ -53,6 +53,14 @@ export function isFilePath(f: string): boolean { /** Engineer names that are test/verification artifacts, not real teammates. */ const ENGINEER_NOISE = /(^verify\b|^.$|testrepo|-?check\b|\d{4,})/i; +const MAX_FILES = 9; + +/** Short, readable node label — last two path segments (full path goes in summary). */ +function shortLabel(file: string): string { + const parts = file.split('/').filter(Boolean); + return parts.slice(-2).join('/') || file; +} + const STATUS_RANK: Record = { stable: 0, active: 1, @@ -183,7 +191,7 @@ export async function materializePodGraph(podId: string): Promise e.kind === 'collides' && e.target === id)) dropNode(id); } + // Cap file nodes to the most-connected (collision files first). + const fileNodes = [...b.nodes.values()].filter((n) => n.kind === 'file'); + if (fileNodes.length > MAX_FILES) { + const inCollision = (id: string) => + [...b.edges.values()].some((e) => e.kind === 'touches' && e.source === id); + const degree = (id: string) => + [...b.edges.values()].filter((e) => e.source === id || e.target === id).length; + fileNodes.sort( + (a, z) => + Number(inCollision(z.id)) - Number(inCollision(a.id)) || degree(z.id) - degree(a.id), + ); + for (const n of fileNodes.slice(MAX_FILES)) dropNode(n.id); + } + // Files / interventions left with no edges -> drop. for (const [id, n] of [...b.nodes]) { if (n.kind === 'file' || n.kind === 'intervention') {