diff --git a/apps/mcp/src/ui/mcp-app.ts b/apps/mcp/src/ui/mcp-app.ts index d91532b5..bde44e15 100644 --- a/apps/mcp/src/ui/mcp-app.ts +++ b/apps/mcp/src/ui/mcp-app.ts @@ -84,7 +84,7 @@ type GraphNode = MemoryNode | DocumentNode interface GraphLink extends LinkObject { source: string | GraphNode target: string | GraphNode - edgeType: "doc-memory" | "version" | "same-space" + edgeType: "derives" | "updates" | "extends" } // ============================================================================= @@ -99,14 +99,14 @@ const MEMORY_BORDER = { const EDGE_COLORS = { dark: { - "doc-memory": "#4A5568", - version: "#8B5CF6", - "same-space": "#00D4B8", + derives: "#3B82F6", + updates: "#8B5CF6", + extends: "#14B8A6", }, light: { - "doc-memory": "#A0AEC0", - version: "#8B5CF6", - "same-space": "#0D9488", + derives: "#60A5FA", + updates: "#8B5CF6", + extends: "#2DD4BF", }, } @@ -174,7 +174,7 @@ function transformData(data: ToolResultData): { const nodeIds = new Set() const SPREAD = 50 - // Group documents by spaceId for same-space edges + // Group documents by spaceId for extends edges const spaceGroups = new Map() for (const doc of data.documents) { @@ -214,19 +214,19 @@ function transformData(data: ToolResultData): { } as MemoryNode) nodeIds.add(mem.id) - // Doc-memory link - links.push({ source: doc.id, target: mem.id, edgeType: "doc-memory" }) + // Derives link + links.push({ source: doc.id, target: mem.id, edgeType: "derives" }) - // Version chain link + // Updates link if (mem.parentMemoryId && nodeIds.has(mem.parentMemoryId)) { links.push({ source: mem.parentMemoryId, target: mem.id, - edgeType: "version", + edgeType: "updates", }) } - // Track space groups for same-space edges + // Track space groups for extends edges if (mem.spaceId) { const group = spaceGroups.get(mem.spaceId) if (group) group.push(doc.id) @@ -235,7 +235,7 @@ function transformData(data: ToolResultData): { } } - // Same-space edges between documents sharing a space + // Extends edges between documents sharing a space const addedEdges = new Set() for (const docIds of spaceGroups.values()) { const unique = [...new Set(docIds)] @@ -247,7 +247,7 @@ function transformData(data: ToolResultData): { links.push({ source: unique[i]!, target: unique[j]!, - edgeType: "same-space", + edgeType: "extends", }) } } @@ -313,7 +313,7 @@ function drawDocumentNode( // ============================================================================= function getLinkColor(link: GraphLink): string { const palette = isDark ? EDGE_COLORS.dark : EDGE_COLORS.light - return palette[link.edgeType] || palette["doc-memory"] + return palette[link.edgeType] || palette["derives"] } const graph = new ForceGraph(container) @@ -366,17 +366,17 @@ const graph = new ForceGraph(container) }, ) .linkWidth((link: GraphLink) => { - if (link.edgeType === "version") return 2 - if (link.edgeType === "same-space") return 0.5 + if (link.edgeType === "updates") return 2 + if (link.edgeType === "extends") return 0.5 return 1 }) .linkColor(getLinkColor) .linkLineDash((link: GraphLink) => { - if (link.edgeType === "same-space") return [4, 2] + if (link.edgeType === "extends") return [4, 2] return null as unknown as number[] }) .linkDirectionalArrowLength((link: GraphLink) => - link.edgeType === "version" ? 4 : 0, + link.edgeType === "updates" ? 4 : 0, ) .linkDirectionalArrowRelPos(1) .onNodeClick(handleNodeClick) @@ -390,11 +390,11 @@ const graph = new ForceGraph(container) .d3Force( "link", forceLink() - .distance((l: GraphLink) => (l.edgeType === "doc-memory" ? 40 : 80)) + .distance((l: GraphLink) => (l.edgeType === "derives" ? 40 : 80)) .strength((l: GraphLink) => { - if (l.edgeType === "doc-memory") return 0.8 - if (l.edgeType === "version") return 1.0 - return 0.15 // same-space + if (l.edgeType === "derives") return 0.8 + if (l.edgeType === "updates") return 1.0 + return 0.15 // extends }), ) .d3Force("collide", forceCollide(18)) diff --git a/packages/memory-graph/src/__tests__/graph-data-utils.test.ts b/packages/memory-graph/src/__tests__/graph-data-utils.test.ts index 68a9ae42..4f074a18 100644 --- a/packages/memory-graph/src/__tests__/graph-data-utils.test.ts +++ b/packages/memory-graph/src/__tests__/graph-data-utils.test.ts @@ -59,20 +59,20 @@ describe("getMemoryBorderColor", () => { }) describe("getEdgeVisualProps", () => { - it("returns correct props for doc-memory edges", () => { - const props = getEdgeVisualProps("doc-memory") + it("returns correct props for derives edges", () => { + const props = getEdgeVisualProps("derives") expect(props.opacity).toBeCloseTo(0.3) expect(props.thickness).toBeCloseTo(1.5) }) - it("returns correct props for version edges", () => { - const props = getEdgeVisualProps("version") + it("returns correct props for updates edges", () => { + const props = getEdgeVisualProps("updates") expect(props.opacity).toBeCloseTo(0.6) expect(props.thickness).toBeCloseTo(2) }) - it("returns correct props for same-space edges", () => { - const props = getEdgeVisualProps("same-space") + it("returns correct props for extends edges", () => { + const props = getEdgeVisualProps("extends") expect(props.opacity).toBeCloseTo(0.15) expect(props.thickness).toBeCloseTo(1) }) diff --git a/packages/memory-graph/src/__tests__/simulation.test.ts b/packages/memory-graph/src/__tests__/simulation.test.ts index e47a92ff..61d46752 100644 --- a/packages/memory-graph/src/__tests__/simulation.test.ts +++ b/packages/memory-graph/src/__tests__/simulation.test.ts @@ -30,7 +30,7 @@ function makeEdge(source: string, target: string): GraphEdge { source, target, visualProps: { opacity: 0.5, thickness: 1.5 }, - edgeType: "doc-memory", + edgeType: "derives", } } diff --git a/packages/memory-graph/src/canvas/renderer.ts b/packages/memory-graph/src/canvas/renderer.ts index fd7f8b5d..07575288 100644 --- a/packages/memory-graph/src/canvas/renderer.ts +++ b/packages/memory-graph/src/canvas/renderer.ts @@ -34,111 +34,21 @@ export function renderFrame( colors: GraphThemeColors, ): void { ctx.clearRect(0, 0, width, height) - drawDocDocLines(ctx, nodes, viewport, width, height, colors) drawEdges(ctx, edges, viewport, width, height, state, nodeMap, colors) drawNodes(ctx, nodes, viewport, width, height, state, colors) } -function drawDocDocLines( - ctx: CanvasRenderingContext2D, - nodes: GraphNode[], - viewport: ViewportState, - width: number, - height: number, - colors: GraphThemeColors, -): void { - // Collect visible docs with screen positions - const docs: { x: number; y: number }[] = [] - for (const n of nodes) { - if (n.type !== "document") continue - const s = viewport.worldToScreen(n.x, n.y) - if (s.x > -100 && s.x < width + 100 && s.y > -100 && s.y < height + 100) { - docs.push(s) - } - } - if (docs.length < 2) return - - // Build spatial grid for O(n*k) nearest-neighbor instead of O(n^2) - const CELL = 200 - const grid = new Map() - for (let i = 0; i < docs.length; i++) { - const d = docs[i]! - const key = `${Math.floor(d.x / CELL)},${Math.floor(d.y / CELL)}` - let cell = grid.get(key) - if (!cell) { - cell = [] - grid.set(key, cell) - } - cell.push(i) - } - - // For each doc, find 2 nearest from neighboring cells only - ctx.strokeStyle = colors.edgeSameSpace - ctx.lineWidth = 1 - ctx.globalAlpha = 0.3 - ctx.setLineDash([4, 6]) - ctx.beginPath() - - for (let i = 0; i < docs.length; i++) { - const d = docs[i]! - const cx = Math.floor(d.x / CELL) - const cy = Math.floor(d.y / CELL) - let best1 = -1 - let best2 = -1 - let dist1 = Number.POSITIVE_INFINITY - let dist2 = Number.POSITIVE_INFINITY - - for (let dx = -1; dx <= 1; dx++) { - for (let dy = -1; dy <= 1; dy++) { - const cell = grid.get(`${cx + dx},${cy + dy}`) - if (!cell) continue - for (const j of cell) { - if (j === i) continue - const other = docs[j]! - const ox = other.x - d.x - const oy = other.y - d.y - const dist = ox * ox + oy * oy - if (dist < dist1) { - best2 = best1 - dist2 = dist1 - best1 = j - dist1 = dist - } else if (dist < dist2) { - best2 = j - dist2 = dist - } - } - } - } - - if (best1 >= 0 && i < best1) { - const b1 = docs[best1]! - ctx.moveTo(d.x, d.y) - ctx.lineTo(b1.x, b1.y) - } - if (best2 >= 0 && i < best2) { - const b2 = docs[best2]! - ctx.moveTo(d.x, d.y) - ctx.lineTo(b2.x, b2.y) - } - } - - ctx.stroke() - ctx.setLineDash([]) - ctx.globalAlpha = 1 -} - function edgeStyle( edge: GraphEdge, colors: GraphThemeColors, ): { color: string; width: number } { - if (edge.edgeType === "doc-memory") - return { color: colors.edgeDocMemory, width: 1.5 } - if (edge.edgeType === "version") - return { color: colors.edgeVersion, width: 2 } - if (edge.edgeType === "same-space") - return { color: colors.edgeSameSpace, width: 1 } - return { color: colors.edgeSameSpace, width: 1 } + if (edge.edgeType === "derives") + return { color: colors.edgeDerives, width: 1.5 } + if (edge.edgeType === "updates") + return { color: colors.edgeUpdates, width: 2 } + if (edge.edgeType === "extends") + return { color: colors.edgeExtends, width: 1 } + return { color: colors.edgeExtends, width: 1 } } function batchKey(style: { color: string; width: number }): string { @@ -152,7 +62,7 @@ interface PreparedEdge { endY: number connected: boolean style: { color: string; width: number } - isVersion: boolean + isUpdates: boolean arrowSize: number } @@ -172,8 +82,8 @@ function drawEdges( const prepared: PreparedEdge[] = [] for (const edge of edges) { - // Zoom-based edge culling for same-space edges (low-priority visual) - if (edge.edgeType === "same-space") { + // Zoom-based edge culling for extends edges (low-priority visual) + if (edge.edgeType === "extends") { if (viewport.zoom < 0.3) continue } @@ -183,7 +93,7 @@ function drawEdges( typeof edge.target === "string" ? nodeMap.get(edge.target) : edge.target if (!src || !tgt) continue - if (edge.edgeType === "doc-memory") { + if (edge.edgeType === "derives") { const mem = src.type === "memory" ? src : tgt if (mem.size * viewport.zoom < 3) continue } @@ -226,9 +136,9 @@ function drawEdges( endY: t.y - uy * tr, connected, style: edgeStyle(edge, colors), - isVersion: edge.edgeType === "version", + isUpdates: edge.edgeType === "updates", arrowSize: - edge.edgeType === "version" ? Math.max(6, 8 * viewport.zoom) : 0, + edge.edgeType === "updates" ? Math.max(6, 8 * viewport.zoom) : 0, }) } @@ -251,10 +161,10 @@ function drawEdges( if (!first) continue const isDimmed = key.endsWith("|d") - // Draw glow pass behind version edges for visual emphasis - // Version edges share a distinct style key, so a batch is all-version or none - const isVersionBatch = first.isVersion - if (isVersionBatch && !isDimmed) { + // Draw glow pass behind updates edges for visual emphasis + // Updates edges share a distinct style key, so a batch is all-updates or none + const isUpdatesBatch = first.isUpdates + if (isUpdatesBatch && !isDimmed) { ctx.save() ctx.globalAlpha = 0.3 ctx.strokeStyle = first.style.color @@ -279,7 +189,7 @@ function drawEdges( } ctx.stroke() - if (isVersionBatch) { + if (isUpdatesBatch) { ctx.fillStyle = first.style.color for (const e of batch) { drawArrowHead(ctx, e.startX, e.startY, e.endX, e.endY, e.arrowSize) diff --git a/packages/memory-graph/src/canvas/simulation.ts b/packages/memory-graph/src/canvas/simulation.ts index 082c5eb0..f7d0db4d 100644 --- a/packages/memory-graph/src/canvas/simulation.ts +++ b/packages/memory-graph/src/canvas/simulation.ts @@ -19,11 +19,11 @@ export class ForceSimulation { d3 .forceLink(edges) .id((d) => d.id) - .distance((link) => (link.edgeType === "doc-memory" ? 150 : 300)) + .distance((link) => (link.edgeType === "derives" ? 150 : 300)) .strength((link) => { - if (link.edgeType === "doc-memory") return 0.8 - if (link.edgeType === "version") return 1.0 - if (link.edgeType === "same-space") return 0.1 + if (link.edgeType === "derives") return 0.8 + if (link.edgeType === "updates") return 1.0 + if (link.edgeType === "extends") return 0.1 return 0.3 }), ) diff --git a/packages/memory-graph/src/components/legend.tsx b/packages/memory-graph/src/components/legend.tsx index dc76a5d7..a3068fde 100644 --- a/packages/memory-graph/src/components/legend.tsx +++ b/packages/memory-graph/src/components/legend.tsx @@ -383,20 +383,20 @@ export const Legend = memo(function Legend({ >
- - Doc > Memory + + Derives
- - Version chain + + Updates
- - Same space + + Extends
diff --git a/packages/memory-graph/src/constants.ts b/packages/memory-graph/src/constants.ts index 91bdb5b7..0d45ef3b 100644 --- a/packages/memory-graph/src/constants.ts +++ b/packages/memory-graph/src/constants.ts @@ -45,9 +45,9 @@ export const DEFAULT_COLORS: GraphThemeColors = { textPrimary: "#ffffff", textSecondary: "#e2e8f0", textMuted: "#94a3b8", - edgeDocMemory: "#4A5568", - edgeVersion: "#8B5CF6", - edgeSameSpace: "#4A6A8A", + edgeDerives: "#3B82F6", + edgeUpdates: "#8B5CF6", + edgeExtends: "#14B8A6", memBorderForgotten: "#EF4444", memBorderExpiring: "#F59E0B", memBorderRecent: "#10B981", diff --git a/packages/memory-graph/src/hooks/use-graph-data.ts b/packages/memory-graph/src/hooks/use-graph-data.ts index 93a39b74..be71a732 100644 --- a/packages/memory-graph/src/hooks/use-graph-data.ts +++ b/packages/memory-graph/src/hooks/use-graph-data.ts @@ -29,11 +29,11 @@ export function getMemoryBorderColor( export function getEdgeVisualProps(edgeType: string) { switch (edgeType) { - case "doc-memory": + case "derives": return { opacity: 0.3, thickness: 1.5 } - case "version": + case "updates": return { opacity: 0.6, thickness: 2 } - case "same-space": + case "extends": return { opacity: 0.15, thickness: 1 } default: return { opacity: 0.3, thickness: 1 } @@ -168,20 +168,20 @@ export function useGraphData( for (const mem of doc.memories) allNodeIds.add(mem.id) } - // Doc-memory edges + // Derives edges (doc -> memory) for (const doc of documents) { for (const mem of doc.memories) { result.push({ id: `dm-${doc.id}-${mem.id}`, source: doc.id, target: mem.id, - visualProps: getEdgeVisualProps("doc-memory"), - edgeType: "doc-memory", + visualProps: getEdgeVisualProps("derives"), + edgeType: "derives", }) } } - // Version chain edges + // Updates edges (version chain) for (const doc of documents) { for (const mem of doc.memories) { if (mem.parentMemoryId && allNodeIds.has(mem.parentMemoryId)) { @@ -189,14 +189,14 @@ export function useGraphData( id: `ver-${mem.parentMemoryId}-${mem.id}`, source: mem.parentMemoryId, target: mem.id, - visualProps: getEdgeVisualProps("version"), - edgeType: "version", + visualProps: getEdgeVisualProps("updates"), + edgeType: "updates", }) } } } - // Same-space edges: connect documents that share a spaceId + // Extends edges: connect documents that share a spaceId const spaceGroups = new Map() for (const doc of documents) { for (const mem of doc.memories) { @@ -221,8 +221,8 @@ export function useGraphData( id: `ss-${key}`, source: a, target: b, - visualProps: getEdgeVisualProps("same-space"), - edgeType: "same-space", + visualProps: getEdgeVisualProps("extends"), + edgeType: "extends", }) } } diff --git a/packages/memory-graph/src/hooks/use-graph-theme.ts b/packages/memory-graph/src/hooks/use-graph-theme.ts index 288dc0a9..b495189d 100644 --- a/packages/memory-graph/src/hooks/use-graph-theme.ts +++ b/packages/memory-graph/src/hooks/use-graph-theme.ts @@ -32,15 +32,9 @@ function resolveColors(): GraphThemeColors { DEFAULT_COLORS.textSecondary, ), textMuted: readCssVar("--graph-text-muted", DEFAULT_COLORS.textMuted), - edgeDocMemory: readCssVar( - "--graph-edge-doc-mem", - DEFAULT_COLORS.edgeDocMemory, - ), - edgeVersion: readCssVar("--graph-edge-version", DEFAULT_COLORS.edgeVersion), - edgeSameSpace: readCssVar( - "--graph-edge-same-space", - DEFAULT_COLORS.edgeSameSpace, - ), + edgeDerives: readCssVar("--graph-edge-derives", DEFAULT_COLORS.edgeDerives), + edgeUpdates: readCssVar("--graph-edge-updates", DEFAULT_COLORS.edgeUpdates), + edgeExtends: readCssVar("--graph-edge-extends", DEFAULT_COLORS.edgeExtends), memBorderForgotten: readCssVar( "--graph-mem-border-forgotten", DEFAULT_COLORS.memBorderForgotten, diff --git a/packages/memory-graph/src/types.ts b/packages/memory-graph/src/types.ts index 6d76760a..b8b99b7d 100644 --- a/packages/memory-graph/src/types.ts +++ b/packages/memory-graph/src/types.ts @@ -29,7 +29,7 @@ export interface GraphApiDocument { export interface GraphApiEdge { source: string target: string - edgeType: "doc-memory" | "version" | "same-space" + edgeType: "derives" | "updates" | "extends" } // Typed node data @@ -86,7 +86,7 @@ export interface GraphEdge { opacity: number thickness: number } - edgeType: "doc-memory" | "version" | "same-space" + edgeType: "derives" | "updates" | "extends" } export interface GraphThemeColors { @@ -101,9 +101,9 @@ export interface GraphThemeColors { textPrimary: string textSecondary: string textMuted: string - edgeDocMemory: string - edgeVersion: string - edgeSameSpace: string + edgeDerives: string + edgeUpdates: string + edgeExtends: string memBorderForgotten: string memBorderExpiring: string memBorderRecent: string