@memnest/ui-core 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/store.ts","../src/geometry.ts","../src/cluster.ts","../src/hit.ts","../src/encoding.ts","../src/draw.ts","../src/controllers/graph.ts","../src/controllers/lineage.ts","../src/controllers/detail.ts","../src/controllers/trace.ts","../src/controllers/timeline.ts","../src/controllers/finder.ts","../src/workspace.ts"],"sourcesContent":["/** What every controller exposes. Framework wrappers subscribe and read; they never mutate. */\nexport interface Observable<S> {\n getState(): S;\n /** Returns the unsubscribe function. */\n subscribe(listener: () => void): () => void;\n}\n\nexport interface Store<S> extends Observable<S> {\n /** Replaces the state with a new object, so snapshot identity changes exactly when state does. */\n set(patch: Partial<S> | ((state: S) => Partial<S>)): void;\n}\n\nexport function createStore<S extends object>(initial: S): Store<S> {\n let state = initial;\n const listeners = new Set<() => void>();\n return {\n getState: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n set(patch) {\n const next = typeof patch === 'function' ? patch(state) : patch;\n state = { ...state, ...next };\n for (const listener of [...listeners]) listener();\n },\n };\n}\n\n/**\n * Drops results of superseded async work: `const token = seq.next()` before awaiting,\n * `if (!seq.isCurrent(token)) return` after.\n */\nexport function createSequencer() {\n let current = 0;\n return {\n next: () => ++current,\n isCurrent: (token: number) => token === current,\n /** Invalidates everything in flight (dispose). */\n cancel: () => {\n current++;\n },\n };\n}\n\nexport const errorText = (error: unknown): string => (error instanceof Error ? error.message : String(error));\n","export interface Point {\n x: number;\n y: number;\n}\n\nexport interface Size {\n width: number;\n height: number;\n}\n\n/** screen = world × k + (x, y) */\nexport interface Viewport {\n x: number;\n y: number;\n k: number;\n}\n\nexport interface Bounds {\n minX: number;\n minY: number;\n maxX: number;\n maxY: number;\n}\n\nexport const IDENTITY_VIEWPORT: Viewport = { x: 0, y: 0, k: 1 };\nexport const ZOOM_LIMITS = { min: 0.02, max: 8 } as const;\n\nexport function boundsOf(circles: Iterable<Point & { r?: number }>): Bounds | null {\n let bounds: Bounds | null = null;\n for (const { x, y, r = 0 } of circles) {\n if (!bounds) bounds = { minX: x - r, minY: y - r, maxX: x + r, maxY: y + r };\n else {\n bounds.minX = Math.min(bounds.minX, x - r);\n bounds.minY = Math.min(bounds.minY, y - r);\n bounds.maxX = Math.max(bounds.maxX, x + r);\n bounds.maxY = Math.max(bounds.maxY, y + r);\n }\n }\n return bounds;\n}\n\n/** The viewport that shows `bounds` centred in `size`, never zooming in past `maxK`. */\nexport function fitViewport(bounds: Bounds | null, size: Size, padding = 60, maxK = 2.5): Viewport {\n if (!bounds || size.width <= 0 || size.height <= 0) return { x: size.width / 2, y: size.height / 2, k: 1 };\n const width = Math.max(bounds.maxX - bounds.minX, 1);\n const height = Math.max(bounds.maxY - bounds.minY, 1);\n const k = clamp(\n Math.min((size.width - padding * 2) / width, (size.height - padding * 2) / height),\n ZOOM_LIMITS.min,\n maxK,\n );\n const cx = (bounds.minX + bounds.maxX) / 2;\n const cy = (bounds.minY + bounds.maxY) / 2;\n return { x: size.width / 2 - cx * k, y: size.height / 2 - cy * k, k };\n}\n\n/** Zooms by `factor` keeping the world point under `screen` fixed. */\nexport function zoomAt(viewport: Viewport, screen: Point, factor: number): Viewport {\n const k = clamp(viewport.k * factor, ZOOM_LIMITS.min, ZOOM_LIMITS.max);\n const world = toWorld(viewport, screen);\n return { x: screen.x - world.x * k, y: screen.y - world.y * k, k };\n}\n\nexport const panBy = (viewport: Viewport, dx: number, dy: number): Viewport => ({ ...viewport, x: viewport.x + dx, y: viewport.y + dy });\n\nexport const toWorld = (viewport: Viewport, screen: Point): Point => ({ x: (screen.x - viewport.x) / viewport.k, y: (screen.y - viewport.y) / viewport.k });\n\nexport const toScreen = (viewport: Viewport, world: Point): Point => ({ x: world.x * viewport.k + viewport.x, y: world.y * viewport.k + viewport.y });\n\nexport const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n","import { MEMORY_KINDS, queryTerms, type GraphEdge, type GraphNode, type MemoryKind } from '@memnest/core';\n\nexport interface Cluster {\n /** `cluster:<key>`; the catch-all is `cluster:*`. */\n id: string;\n /** The shared term, or '' for the catch-all. Searching for it expands the cluster. */\n key: string;\n label: string;\n count: number;\n kinds: Record<MemoryKind, number>;\n /** Members that are superseded or forgotten. */\n inactive: number;\n memberIds: string[];\n}\n\nexport interface ClusterEdge {\n from: string;\n to: string;\n /** Memory edges between the two clusters. */\n weight: number;\n}\n\nexport interface ClusterOptions {\n /** Including the catch-all. Default 48. */\n maxClusters?: number;\n /** Terms never used as keys, e.g. the words of the current search. */\n exclude?: readonly string[];\n}\n\nexport interface Clustering {\n clusters: Cluster[];\n edges: ClusterEdge[];\n clusterOf: ReadonlyMap<string, string>;\n}\n\nconst CATCH_ALL = 'cluster:*';\nconst usable = (term: string) => term.length >= 3 && !/^\\d+$/.test(term);\n\n/**\n * Groups memories by topic so a large container reads as a few dozen labelled groups instead of a\n * hairball. Each memory joins the group of its most widespread term (ignoring terms shared by more\n * than 60% of memories, which say nothing). The largest groups are kept; the rest share a catch-all.\n * Deterministic, and linear in the total number of terms.\n */\nexport function clusterNodes(nodes: readonly GraphNode[], edges: readonly GraphEdge[], options: ClusterOptions = {}): Clustering {\n const maxClusters = Math.max(2, options.maxClusters ?? 48);\n const exclude = new Set(options.exclude?.flatMap((t) => queryTerms(t)) ?? []);\n\n const termsOf = nodes.map((node) => queryTerms(node.content).filter((t) => usable(t) && !exclude.has(t)));\n const df = new Map<string, number>();\n for (const terms of termsOf) for (const t of terms) df.set(t, (df.get(t) ?? 0) + 1);\n const ceiling = Math.max(2, Math.floor(nodes.length * 0.6));\n\n const keyOf = termsOf.map((terms) => {\n let best: string | undefined;\n for (const t of terms) {\n const count = df.get(t)!;\n if (count < 2 || count > ceiling) continue;\n const bestCount = best === undefined ? -1 : df.get(best)!;\n if (count > bestCount || (count === bestCount && t < best!)) best = t;\n }\n return best ?? '';\n });\n\n const sizes = new Map<string, number>();\n for (const key of keyOf) if (key) sizes.set(key, (sizes.get(key) ?? 0) + 1);\n const kept = new Set(\n [...sizes.entries()]\n .sort(([a, x], [b, y]) => y - x || (a < b ? -1 : 1))\n .slice(0, maxClusters - 1)\n .map(([key]) => key),\n );\n\n const byId = new Map<string, Cluster>();\n const clusterOf = new Map<string, string>();\n nodes.forEach((node, i) => {\n const key = kept.has(keyOf[i]!) ? keyOf[i]! : '';\n const id = key ? `cluster:${key}` : CATCH_ALL;\n let cluster = byId.get(id);\n if (!cluster) {\n cluster = {\n id,\n key,\n label: key || 'other',\n count: 0,\n kinds: Object.fromEntries(MEMORY_KINDS.map((k) => [k, 0])) as Record<MemoryKind, number>,\n inactive: 0,\n memberIds: [],\n };\n byId.set(id, cluster);\n }\n cluster.count++;\n cluster.kinds[node.kind]++;\n if (!node.isLatest || node.forgotten) cluster.inactive++;\n cluster.memberIds.push(node.id);\n clusterOf.set(node.id, id);\n });\n\n const weights = new Map<string, number>();\n for (const edge of edges) {\n const from = clusterOf.get(edge.from);\n const to = clusterOf.get(edge.to);\n if (!from || !to || from === to) continue;\n const pair = from < to ? `${from}\u0000${to}` : `${to}\u0000${from}`;\n weights.set(pair, (weights.get(pair) ?? 0) + 1);\n }\n\n return {\n clusters: [...byId.values()].sort((a, z) => z.count - a.count || (a.key < z.key ? -1 : 1)),\n edges: [...weights.entries()].map(([pair, weight]) => {\n const [from, to] = pair.split('\u0000') as [string, string];\n return { from, to, weight };\n }),\n clusterOf,\n };\n}\n","import { quadtree } from 'd3-quadtree';\nimport type { Point } from './geometry';\n\nexport interface HitCircle {\n id: string;\n x: number;\n y: number;\n r: number;\n}\n\nexport interface HitIndex {\n /** The circle under a world point, allowing `slop` extra world units for small targets. */\n pick(world: Point, slop?: number): HitCircle | null;\n readonly size: number;\n}\n\n/** A quadtree over circle centres: picking stays logarithmic at 10,000 nodes. */\nexport function createHitIndex(circles: readonly HitCircle[]): HitIndex {\n const tree = quadtree<HitCircle>(\n [...circles],\n (c) => c.x,\n (c) => c.y,\n );\n const maxR = circles.reduce((max, c) => Math.max(max, c.r), 0);\n return {\n size: circles.length,\n pick(world, slop = 0) {\n // Nearest centre within reach of the largest circle, then an exact containment check.\n const candidate = tree.find(world.x, world.y, maxR + slop);\n if (candidate && Math.hypot(candidate.x - world.x, candidate.y - world.y) <= candidate.r + slop) return candidate;\n // A large circle can contain the point while a small neighbour's centre is nearer.\n let best: HitCircle | null = null;\n let bestDistance = Infinity;\n tree.visit((node, x0, y0, x1, y1) => {\n if (!node.length) {\n for (let leaf: typeof node | undefined = node; leaf; leaf = (leaf as { next?: typeof node }).next) {\n const c = (leaf as { data: HitCircle }).data;\n const d = Math.hypot(c.x - world.x, c.y - world.y);\n if (d <= c.r + slop && d < bestDistance) {\n best = c;\n bestDistance = d;\n }\n }\n }\n const reach = maxR + slop;\n return x0 > world.x + reach || x1 < world.x - reach || y0 > world.y + reach || y1 < world.y - reach;\n });\n return best;\n },\n };\n}\n","import type { GraphNode } from '@memnest/core';\n\n/** Size by reinforcementCount: a fact stated five times is visibly bigger than one stated once. */\nexport const nodeRadius = (node: Pick<GraphNode, 'reinforcementCount'>): number =>\n 5 + Math.min(11, 2.5 * Math.sqrt(Math.max(0, node.reinforcementCount - 1)));\n\nexport const clusterRadius = (count: number): number => 14 + Math.min(70, 3.2 * Math.sqrt(count));\n\nexport const documentRadius = 7;\n\n/** Shortens text for labels at a word boundary. */\nexport function shorten(text: string, max = 48): string {\n if (text.length <= max) return text;\n const cut = text.slice(0, max - 1);\n const space = cut.lastIndexOf(' ');\n return `${space > max * 0.6 ? cut.slice(0, space) : cut}…`;\n}\n","import type { MemoryKind } from '@memnest/core';\nimport type { GraphState } from './controllers/graph';\nimport { clusterRadius, nodeRadius, shorten } from './encoding';\nimport type { Size, Viewport } from './geometry';\n\nexport interface CanvasGradientLike {\n addColorStop(offset: number, color: string): void;\n}\n\n/** The subset of CanvasRenderingContext2D the renderer uses. No DOM types. */\nexport interface Canvas2DLike {\n save(): void;\n restore(): void;\n setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void;\n fillRect(x: number, y: number, w: number, h: number): void;\n beginPath(): void;\n moveTo(x: number, y: number): void;\n lineTo(x: number, y: number): void;\n quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void;\n arc(x: number, y: number, r: number, start: number, end: number): void;\n fill(): void;\n stroke(): void;\n fillText(text: string, x: number, y: number): void;\n strokeText(text: string, x: number, y: number): void;\n setLineDash(segments: number[]): void;\n createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): CanvasGradientLike;\n fillStyle: unknown;\n strokeStyle: unknown;\n lineWidth: number;\n globalAlpha: number;\n globalCompositeOperation: string;\n font: string;\n textAlign: string;\n textBaseline: string;\n lineJoin: string;\n lineCap: string;\n}\n\n/** Colours are hex (#rgb or #rrggbb) wherever the renderer derives glows from them. */\nexport interface GraphTheme {\n /**\n * How light meets the ground. On a dark ground neurons and signals add light, so they glow; on a light\n * ground adding light would wash out to white, so they lay soft colour down instead.\n */\n ground: 'dark' | 'light';\n /** A flat ground, dark or light to match `ground`. */\n background: string;\n /** The bright core of a signal travelling along a connection. */\n spark: string;\n kinds: Record<MemoryKind, string>;\n /** Clusters are neutral: their colour would otherwise suggest a kind they do not have. */\n cluster: string;\n clusterStroke: string;\n edges: { updates: string; extends: string; aggregate: string };\n label: string;\n labelHalo: string;\n selection: string;\n lineage: string;\n /** Opacity of superseded memories. */\n supersededAlpha: number;\n /** Opacity of forgotten memories (drawn hollow). */\n forgottenAlpha: number;\n font: string;\n}\n\nexport interface SceneCircle {\n id: string;\n x: number;\n y: number;\n r: number;\n fill: string | null;\n stroke: string | null;\n strokeWidth: number;\n alpha: number;\n /** How brightly the neuron shines, 0–1. */\n glow: number;\n selected: boolean;\n /** Stable per circle in [0, 1): varies each pulse so no two breathe in step. */\n seed: number;\n label: string | null;\n /** Higher labels win when space is short. */\n priority: number;\n}\n\nexport interface SceneLine {\n x1: number;\n y1: number;\n x2: number;\n y2: number;\n /** The circle the line ends at, which flashes when a signal arrives. */\n to: string | null;\n color: string;\n width: number;\n alpha: number;\n dash: number[] | null;\n /** Draw an arrowhead at (x2, y2), outside a circle of this radius. */\n arrow: number | null;\n /** Stable per line in [0, 1): sets its curve and when it fires. */\n seed: number;\n}\n\nexport interface Scene {\n circles: SceneCircle[];\n lines: SceneLine[];\n}\n\n/** A stable pseudo-random number in [0, 1) for a string. */\nfunction seedOf(text: string): number {\n let hash = 2166136261;\n for (let i = 0; i < text.length; i++) hash = Math.imul(hash ^ text.charCodeAt(i), 16777619);\n // Finalise, so ids that differ by one character land far apart.\n hash = Math.imul(hash ^ (hash >>> 16), 0x85ebca6b);\n hash = Math.imul(hash ^ (hash >>> 13), 0xc2b2ae35);\n return ((hash ^ (hash >>> 16)) >>> 0) / 4294967296;\n}\n\n/** Turns graph state into drawable primitives in world coordinates. Pure. */\nexport function buildGraphScene(state: GraphState, theme: GraphTheme): Scene {\n const { positions, selectedId, lineage } = state;\n const circles: SceneCircle[] = [];\n const lines: SceneLine[] = [];\n\n if (state.mode === 'clusters') {\n for (const edge of state.clusterEdges) {\n const a = positions.get(edge.from);\n const b = positions.get(edge.to);\n if (!a || !b) continue;\n lines.push({\n x1: a.x,\n y1: a.y,\n x2: b.x,\n y2: b.y,\n to: edge.to,\n color: theme.edges.aggregate,\n width: Math.min(6, 1 + Math.log2(edge.weight)),\n alpha: 0.5,\n dash: null,\n arrow: null,\n seed: seedOf(`${edge.from} ${edge.to}`),\n });\n }\n for (const cluster of state.clusters) {\n const p = positions.get(cluster.id);\n if (!p) continue;\n circles.push({\n id: cluster.id,\n x: p.x,\n y: p.y,\n r: clusterRadius(cluster.count),\n fill: theme.cluster,\n stroke: theme.clusterStroke,\n strokeWidth: 1,\n // Translucent, so the glow behind shows through.\n alpha: 0.62,\n glow: 0.45 + Math.min(0.4, Math.log10(cluster.count) / 8),\n selected: false,\n seed: seedOf(cluster.id),\n label: `${cluster.label} · ${cluster.count.toLocaleString('en')}`,\n priority: cluster.count,\n });\n }\n return { circles, lines };\n }\n\n const radius = new Map(state.nodes.map((n) => [n.id, nodeRadius(n)]));\n const lineageEdges = new Set(lineage?.edges.map((e) => `${e.from} ${e.to}`) ?? []);\n for (const edge of state.edges) {\n const a = positions.get(edge.from);\n const b = positions.get(edge.to);\n if (!a || !b) continue;\n const inLineage = lineageEdges.has(`${edge.from} ${edge.to}`);\n lines.push({\n x1: a.x,\n y1: a.y,\n x2: b.x,\n y2: b.y,\n to: edge.to,\n color: inLineage ? theme.lineage : edge.relation === 'updates' ? theme.edges.updates : theme.edges.extends,\n width: inLineage ? 2.5 : 1.25,\n alpha: lineage && !inLineage ? 0.25 : 0.9,\n // updates: solid with an arrow to the replaced fact; extends: dotted, no arrow.\n dash: edge.relation === 'extends' ? [0.5, 4] : null,\n arrow: edge.relation === 'updates' ? radius.get(edge.to) ?? 6 : null,\n seed: seedOf(`${edge.from} ${edge.to}`),\n });\n }\n for (const node of state.nodes) {\n const p = positions.get(node.id);\n if (!p) continue;\n const selected = node.id === selectedId;\n const inLineage = lineage?.memoryIds.has(node.id) ?? false;\n const dimmed = Boolean(lineage) && !inLineage && !selected;\n const base = node.forgotten ? theme.forgottenAlpha : node.isLatest ? 1 : theme.supersededAlpha;\n // Reinforced memories shine brighter; superseded ones smoulder; forgotten ones have gone dark.\n const glow = node.forgotten ? 0 : node.isLatest ? 0.55 + Math.min(0.45, 0.12 * (node.reinforcementCount - 1)) : 0.22;\n circles.push({\n id: node.id,\n x: p.x,\n y: p.y,\n r: radius.get(node.id)!,\n fill: node.forgotten ? null : theme.kinds[node.kind],\n stroke: selected ? theme.selection : inLineage ? theme.lineage : node.forgotten ? theme.kinds[node.kind] : null,\n strokeWidth: selected ? 3 : inLineage || node.forgotten ? 2 : 0,\n alpha: dimmed ? base * 0.35 : base,\n glow: dimmed ? glow * 0.3 : selected ? 1 : glow,\n selected,\n seed: seedOf(node.id),\n label: shorten(node.content, 42),\n priority: (selected ? 1e9 : 0) + (inLineage ? 1e6 : 0) + node.reinforcementCount * 10 + (node.isLatest ? 5 : 0),\n });\n }\n return { circles, lines };\n}\n\nexport interface DrawOptions {\n pixelRatio?: number;\n /** Maximum labels per frame. Default 160. */\n maxLabels?: number;\n /**\n * Milliseconds on any steady clock. When given, neurons breathe and signals travel along\n * connections, lighting up the memory they reach. Omit for a still frame (reduced motion, snapshots).\n */\n time?: number;\n}\n\nconst TAU = Math.PI * 2;\n/** Only topics are this large: a memory's radius tops out at 16. */\nconst TOPIC_RADIUS = 17;\n\nfunction parseHex(color: string): [number, number, number] | null {\n let hex = color.trim();\n if (!hex.startsWith('#')) return null;\n hex = hex.slice(1);\n if (hex.length === 3) hex = hex.replace(/./g, (c) => c + c);\n if (!/^[0-9a-f]{6}$/i.test(hex)) return null;\n const n = Number.parseInt(hex, 16);\n return [n >> 16, (n >> 8) & 255, n & 255];\n}\n\n/** `color` at `alpha`, for hex colours; anything else passes through (or clears, at alpha 0). */\nfunction withAlpha(color: string, alpha: number): string {\n const rgb = parseHex(color);\n if (!rgb) return alpha <= 0 ? 'rgba(0,0,0,0)' : color;\n return `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${alpha})`;\n}\n\n/** `color` moved towards `toward` by `t`, for hex colours. */\nfunction mix(color: string, toward: string, t: number): string {\n const a = parseHex(color);\n const b = parseHex(toward);\n if (!a || !b) return color;\n const channel = (i: number) => Math.round(a[i]! + (b[i]! - a[i]!) * t);\n return `rgb(${channel(0)},${channel(1)},${channel(2)})`;\n}\n\n/**\n * Unit radial gradients (radius 1 at the origin), made once per context and colour. Drawn through a\n * transform that places and scales them, so thousands of glowing neurons cost no gradient allocations.\n */\nconst spriteCache = new WeakMap<object, Map<string, CanvasGradientLike>>();\n\nfunction unitGradient(ctx: Canvas2DLike, key: string, stops: () => Array<[number, string]>): CanvasGradientLike {\n let cache = spriteCache.get(ctx);\n if (!cache) spriteCache.set(ctx, (cache = new Map()));\n let gradient = cache.get(key);\n if (!gradient) {\n gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, 1);\n for (const [offset, color] of stops()) gradient.addColorStop(offset, color);\n cache.set(key, gradient);\n }\n return gradient;\n}\n\nconst haloGradient = (ctx: Canvas2DLike, color: string, ground: GraphTheme['ground']) =>\n unitGradient(ctx, `halo ${ground} ${color}`, () =>\n ground === 'dark'\n ? [\n [0, withAlpha(color, 0.6)],\n [0.22, withAlpha(color, 0.26)],\n [0.55, withAlpha(color, 0.07)],\n [1, withAlpha(color, 0)],\n ]\n : // A soft tint: on a light ground a strong halo reads as a stain, not a glow.\n [\n [0, withAlpha(color, 0.34)],\n [0.25, withAlpha(color, 0.15)],\n [0.6, withAlpha(color, 0.04)],\n [1, withAlpha(color, 0)],\n ],\n );\n\nconst coreGradient = (ctx: Canvas2DLike, color: string, ground: GraphTheme['ground']) =>\n unitGradient(ctx, `core ${ground} ${color}`, () =>\n ground === 'dark'\n ? [\n [0, mix(color, '#ffffff', 0.9)],\n [0.3, mix(color, '#ffffff', 0.45)],\n [0.75, color],\n [1, mix(color, '#000000', 0.3)],\n ]\n : // A softer highlight and a firmer edge, so the neuron keeps its shape against white.\n [\n [0, mix(color, '#ffffff', 0.6)],\n [0.45, mix(color, '#ffffff', 0.12)],\n [0.85, color],\n [1, mix(color, '#000000', 0.2)],\n ],\n );\n\nconst sparkGradient = (ctx: Canvas2DLike, color: string, core: string) =>\n unitGradient(ctx, `spark ${color} ${core}`, () => [\n [0, withAlpha(core, 1)],\n [0.15, withAlpha(mix(color, core, 0.5), 0.9)],\n [0.4, withAlpha(color, 0.3)],\n [1, withAlpha(color, 0)],\n ]);\n\n/**\n * Draws a scene to a 2D canvas as a glowing neural network. Labels appear once a circle is large enough\n * on screen to own one, highest priority first, capped per frame, so 2,000 nodes stay legible and fast.\n */\nexport function drawScene(ctx: Canvas2DLike, scene: Scene, viewport: Viewport, size: Size, theme: GraphTheme, options: DrawOptions = {}): void {\n const ratio = options.pixelRatio ?? 1;\n const live = options.time !== undefined;\n const time = options.time ?? 0;\n const { k } = viewport;\n // Light adds up on a dark ground; on a light one, colour is laid down.\n const glow = theme.ground === 'dark' ? 'lighter' : 'source-over';\n ctx.save();\n // A plain ground: only the neurons and their connections carry colour.\n ctx.setTransform(ratio, 0, 0, ratio, 0, 0);\n ctx.globalCompositeOperation = 'source-over';\n ctx.globalAlpha = 1;\n ctx.fillStyle = theme.background;\n ctx.fillRect(0, 0, size.width, size.height);\n\n const world = () => ctx.setTransform(ratio * k, 0, 0, ratio * k, ratio * viewport.x, ratio * viewport.y);\n /** Maps the unit square to a square of half-width `r` centred on a world point. */\n const place = (x: number, y: number, r: number) => {\n const scale = ratio * k * r;\n ctx.setTransform(scale, 0, 0, scale, ratio * (viewport.x + x * k), ratio * (viewport.y + y * k));\n };\n\n // Cull to the visible world rectangle.\n const margin = 80 / k;\n const minX = -viewport.x / k - margin;\n const minY = -viewport.y / k - margin;\n const maxX = (size.width - viewport.x) / k + margin;\n const maxY = (size.height - viewport.y) / k + margin;\n const visible = (x: number, y: number) => x >= minX && x <= maxX && y >= minY && y <= maxY;\n\n // Connections: curved axons with a soft glow, each firing a signal now and then.\n world();\n ctx.globalCompositeOperation = glow;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n const flashes = new Map<string, number>();\n const sparks: Array<{ x: number; y: number; color: string; strength: number }> = [];\n const lines = scene.lines.filter((line) => visible(line.x1, line.y1) || visible(line.x2, line.y2));\n // Light adds up: the more connections on screen, the dimmer each, so dense regions glow instead of burning white.\n const density = Math.min(1, 12 / Math.sqrt(lines.length || 1));\n for (const line of lines) {\n const dx = line.x2 - line.x1;\n const dy = line.y2 - line.y1;\n const length = Math.hypot(dx, dy) || 1;\n const bend = (line.seed - 0.5) * 0.4 * length;\n const cx = (line.x1 + line.x2) / 2 - (dy / length) * bend;\n const cy = (line.y1 + line.y2) / 2 + (dx / length) * bend;\n\n ctx.strokeStyle = line.color;\n ctx.beginPath();\n ctx.moveTo(line.x1, line.y1);\n ctx.quadraticCurveTo(cx, cy, line.x2, line.y2);\n ctx.setLineDash([]);\n ctx.globalAlpha = line.alpha * 0.1 * density;\n ctx.lineWidth = (line.width * 4) / k;\n ctx.stroke();\n ctx.setLineDash(line.dash ? line.dash.map((d) => d / k) : []);\n ctx.globalAlpha = line.alpha * (0.25 + 0.35 * density);\n ctx.lineWidth = line.width / k;\n ctx.stroke();\n\n if (line.arrow !== null) {\n const angle = Math.atan2(line.y2 - cy, line.x2 - cx);\n const tipX = line.x2 - Math.cos(angle) * (line.arrow + 1.5 / k);\n const tipY = line.y2 - Math.sin(angle) * (line.arrow + 1.5 / k);\n // Arrowheads scale with the node on screen, so a zoomed-out graph is not all arrows.\n const head = Math.min(6, Math.max(2, line.arrow * k)) / k;\n ctx.setLineDash([]);\n ctx.fillStyle = line.color;\n ctx.beginPath();\n ctx.moveTo(tipX, tipY);\n ctx.lineTo(tipX - Math.cos(angle - 0.45) * head, tipY - Math.sin(angle - 0.45) * head);\n ctx.lineTo(tipX - Math.cos(angle + 0.45) * head, tipY - Math.sin(angle + 0.45) * head);\n ctx.fill();\n }\n\n if (live) {\n const period = 2800 + line.seed * 6000;\n const travel = Math.min(2400, Math.max(700, length * 9));\n const u = ((time + line.seed * 9973) % period) / travel;\n if (u <= 1) {\n const e = u < 0.5 ? 2 * u * u : 1 - (2 - 2 * u) ** 2 / 2;\n const inv = 1 - e;\n sparks.push({\n x: inv * inv * line.x1 + 2 * inv * e * cx + e * e * line.x2,\n y: inv * inv * line.y1 + 2 * inv * e * cy + e * e * line.y2,\n color: line.color,\n strength: Math.min(1, line.alpha * 1.1) * Math.sin(Math.PI * Math.min(1, u * 1.15 + 0.08)),\n });\n } else if (line.to && u < 1.5) {\n flashes.set(line.to, Math.max(flashes.get(line.to) ?? 0, 1 - (u - 1) / 0.5));\n }\n }\n }\n ctx.setLineDash([]);\n\n for (const spark of sparks) {\n place(spark.x, spark.y, Math.min(9, Math.max(4, 3 * k)) / k);\n ctx.globalAlpha = Math.max(0, spark.strength);\n ctx.fillStyle = sparkGradient(ctx, spark.color, theme.spark);\n ctx.fillRect(-1, -1, 2, 2);\n }\n\n // Halos.\n const shown = scene.circles.filter((c) => visible(c.x, c.y));\n const crowd = Math.min(1, 22 / Math.sqrt(shown.length || 1));\n for (const circle of shown) {\n const color = circle.fill ?? circle.stroke;\n const flash = flashes.get(circle.id) ?? 0;\n const pulse = live ? 0.75 + 0.25 * Math.sin(time / (1100 + circle.seed * 1400) + circle.seed * TAU) : 1;\n const strength = Math.min(1, circle.glow * (pulse * crowd + flash * 0.55));\n if (!color || strength <= 0.01) continue;\n // At a distance, each memory still reads as a point of light rather than a pinprick.\n place(circle.x, circle.y, Math.max(circle.r * (3 + flash * 0.8), 7 / k));\n ctx.globalAlpha = strength;\n ctx.fillStyle = haloGradient(ctx, color, theme.ground);\n ctx.fillRect(-1, -1, 2, 2);\n }\n\n // Cell bodies: a bright nucleus fading to the kind's colour; one arc each for fill and ring.\n ctx.globalCompositeOperation = 'source-over';\n const labelled: SceneCircle[] = [];\n for (const circle of shown) {\n place(circle.x, circle.y, circle.r);\n // A faded neuron is a ghost: a faint glassy body and a thin rim in its colour. Dimming the colour itself\n // would turn amber into brown on a dark ground.\n const ghost = circle.fill !== null && circle.alpha < 1 && circle.r < TOPIC_RADIUS;\n ctx.globalCompositeOperation = ghost ? glow : 'source-over';\n ctx.globalAlpha = ghost ? circle.alpha * 0.45 : circle.alpha;\n ctx.beginPath();\n ctx.arc(0, 0, 1, 0, TAU);\n if (circle.fill) {\n ctx.fillStyle = coreGradient(ctx, circle.fill, theme.ground);\n ctx.fill();\n }\n if (circle.stroke && circle.strokeWidth > 0) {\n ctx.globalAlpha = circle.alpha;\n ctx.strokeStyle = circle.stroke;\n ctx.lineWidth = circle.strokeWidth / (k * circle.r);\n ctx.stroke();\n } else if (ghost) {\n ctx.globalAlpha = Math.min(1, circle.alpha * 2);\n ctx.strokeStyle = circle.fill!;\n ctx.lineWidth = 1 / (k * circle.r);\n ctx.stroke();\n }\n if (circle.label && (circle.r * k >= 9 || circle.priority >= 1e6)) labelled.push(circle);\n }\n\n // The selected memory sends out a slow ripple.\n ctx.globalCompositeOperation = glow;\n world();\n if (live) {\n for (const circle of shown) {\n if (!circle.selected) continue;\n const phase = (time % 2400) / 2400;\n ctx.globalAlpha = 0.6 * (1 - phase);\n ctx.strokeStyle = circle.stroke ?? theme.selection;\n ctx.lineWidth = 1.5 / k;\n ctx.beginPath();\n ctx.arc(circle.x, circle.y, circle.r * (1.3 + phase * 2.2), 0, TAU);\n ctx.stroke();\n }\n }\n ctx.globalCompositeOperation = 'source-over';\n\n labelled.sort((a, z) => z.priority - a.priority);\n ctx.setTransform(ratio, 0, 0, ratio, 0, 0);\n ctx.font = theme.font;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n ctx.lineJoin = 'round';\n const taken: Array<[number, number, number, number]> = [];\n for (const circle of labelled.slice(0, options.maxLabels ?? 160)) {\n const x = circle.x * k + viewport.x;\n const y = (circle.y + circle.r) * k + viewport.y + 6;\n const width = circle.label!.length * 6.2;\n const box: [number, number, number, number] = [x - width / 2, y, x + width / 2, y + 14];\n if (taken.some(([x0, y0, x1, y1]) => box[0] < x1 && box[2] > x0 && box[1] < y1 && box[3] > y0)) continue;\n taken.push(box);\n ctx.globalAlpha = Math.max(circle.alpha, 0.7);\n ctx.strokeStyle = theme.labelHalo;\n ctx.lineWidth = 4;\n ctx.strokeText(circle.label!, x, y);\n ctx.fillStyle = theme.label;\n ctx.fillText(circle.label!, x, y);\n }\n ctx.restore();\n}\n","import { scopeOf, type GraphEdge, type GraphNode, type GraphSnapshot, type LineageEdge, type MemnestApi, type MemoryKind } from '@memnest/core';\nimport { clusterNodes, type Cluster, type ClusterEdge } from '../cluster';\nimport { clusterRadius, nodeRadius } from '../encoding';\nimport { IDENTITY_VIEWPORT, boundsOf, fitViewport, panBy, toWorld, zoomAt, type Point, type Size, type Viewport } from '../geometry';\nimport { createHitIndex, type HitIndex } from '../hit';\nimport { inlineLayoutRunner, type LayoutRunner } from '../layout/runner';\nimport { createSequencer, createStore, errorText, type Observable } from '../store';\n\nexport const CLUSTER_THRESHOLD = 2000;\nexport const GRAPH_LOAD_LIMIT = 10_000;\n\nexport interface GraphFilter {\n /** Empty: every kind. */\n kinds: MemoryKind[];\n includeSuperseded: boolean;\n includeForgotten: boolean;\n /** Words that must all appear in the memory (case-insensitive), or an exact memory id. */\n search: string;\n}\n\nexport interface GraphLineageOverlay {\n rootId: string;\n memoryIds: ReadonlySet<string>;\n edges: LineageEdge[];\n}\n\nexport interface GraphState {\n containerTag: string;\n status: 'idle' | 'loading' | 'layout' | 'ready' | 'error';\n error: string | null;\n filter: GraphFilter;\n /** Above the cluster threshold the graph shows topic clusters; a filter or search expands them. */\n mode: 'nodes' | 'clusters';\n totalMemories: number;\n /** Nodes fetched (at most the load limit). */\n loaded: number;\n /** True when the container holds more memories than were fetched. */\n truncated: boolean;\n /** Nodes matching the filter, before clustering. */\n matching: number;\n nodes: GraphNode[];\n edges: GraphEdge[];\n clusters: Cluster[];\n clusterEdges: ClusterEdge[];\n positions: ReadonlyMap<string, Point>;\n selectedId: string | null;\n lineage: GraphLineageOverlay | null;\n viewport: Viewport;\n size: Size;\n layoutMs: number | null;\n /** What the pointer is over, and where (screen coordinates), for tooltips. */\n hover: { pick: NonNullable<GraphPick>; x: number; y: number } | null;\n}\n\nexport type GraphPick = { type: 'node'; id: string } | { type: 'cluster'; id: string; key: string } | null;\n\nexport interface GraphController extends Observable<GraphState> {\n load(): Promise<void>;\n setFilter(filter: Partial<GraphFilter>): void;\n select(memoryId: string | null): void;\n expandLineage(memoryId: string): Promise<void>;\n collapseLineage(): void;\n /** Searches for the cluster's term, which shows its members (or finer clusters). */\n expandCluster(clusterId: string): void;\n setSize(width: number, height: number): void;\n panBy(dx: number, dy: number): void;\n zoomAt(screen: Point, factor: number): void;\n fit(): void;\n /** What is under a screen point. */\n pick(screen: Point): GraphPick;\n /** Tracks the pointer for tooltips; null when it leaves. Updates state only when the target changes. */\n hover(screen: Point | null): void;\n dispose(): void;\n}\n\nexport interface GraphControllerOptions {\n client: MemnestApi;\n containerTag: string;\n layout?: LayoutRunner;\n clusterThreshold?: number;\n limit?: number;\n filter?: Partial<GraphFilter>;\n /** Called when a node is picked or selected, so other views can follow. */\n onSelect?: (memoryId: string | null) => void;\n /** Default true. */\n autoload?: boolean;\n}\n\nexport const DEFAULT_GRAPH_FILTER: GraphFilter = { kinds: [], includeSuperseded: true, includeForgotten: false, search: '' };\n\nexport function matchesFilter(node: GraphNode, filter: GraphFilter): boolean {\n if (filter.kinds.length > 0 && !filter.kinds.includes(node.kind)) return false;\n if (!node.isLatest && !filter.includeSuperseded) return false;\n if (node.forgotten && !filter.includeForgotten) return false;\n const search = filter.search.trim().toLowerCase();\n if (!search) return true;\n if (node.id === filter.search.trim()) return true;\n const content = node.content.toLowerCase();\n return search.split(/\\s+/).every((word) => content.includes(word));\n}\n\nexport function createGraphController(options: GraphControllerOptions): GraphController {\n const { client } = options;\n const scope = scopeOf(options.containerTag);\n const runner = options.layout ?? inlineLayoutRunner;\n const threshold = options.clusterThreshold ?? CLUSTER_THRESHOLD;\n const limit = options.limit ?? GRAPH_LOAD_LIMIT;\n const store = createStore<GraphState>({\n containerTag: scope.containerTag,\n status: 'idle',\n error: null,\n filter: { ...DEFAULT_GRAPH_FILTER, ...options.filter },\n mode: 'nodes',\n totalMemories: 0,\n loaded: 0,\n truncated: false,\n matching: 0,\n nodes: [],\n edges: [],\n clusters: [],\n clusterEdges: [],\n positions: new Map(),\n selectedId: null,\n lineage: null,\n viewport: IDENTITY_VIEWPORT,\n size: { width: 0, height: 0 },\n layoutMs: null,\n hover: null,\n });\n const loads = createSequencer();\n const layouts = createSequencer();\n let snapshot: GraphSnapshot | null = null;\n let hits: HitIndex = createHitIndex([]);\n let userMoved = false;\n let disposed = false;\n\n /** Filters, clusters if needed, and lays out. Keeps previous positions as the starting point. */\n async function recompute(): Promise<void> {\n if (!snapshot) return;\n const token = layouts.next();\n const { filter, lineage, positions: previous, mode: previousMode } = store.getState();\n let nodes = snapshot.nodes.filter((n) => matchesFilter(n, filter));\n const matching = nodes.length;\n if (lineage) {\n const present = new Set(nodes.map((n) => n.id));\n nodes = nodes.concat(snapshot.nodes.filter((n) => lineage.memoryIds.has(n.id) && !present.has(n.id)));\n }\n const ids = new Set(nodes.map((n) => n.id));\n const edges = snapshot.edges.filter((e) => ids.has(e.from) && ids.has(e.to));\n const mode = nodes.length > threshold ? 'clusters' : 'nodes';\n\n let clusters: Cluster[] = [];\n let clusterEdges: ClusterEdge[] = [];\n let layoutNodes: Array<{ id: string; r: number }>;\n let layoutEdges: Array<{ from: string; to: string }>;\n if (mode === 'clusters') {\n ({ clusters, edges: clusterEdges } = clusterNodes(nodes, edges, { exclude: filter.search ? [filter.search] : [] }));\n layoutNodes = clusters.map((c) => ({ id: c.id, r: clusterRadius(c.count) }));\n layoutEdges = clusterEdges;\n } else {\n layoutNodes = nodes.map((n) => ({ id: n.id, r: nodeRadius(n) }));\n layoutEdges = edges;\n }\n\n store.set({ status: 'layout', hover: null, matching, mode, nodes: mode === 'nodes' ? nodes : [], edges: mode === 'nodes' ? edges : [], clusters, clusterEdges });\n const started = performance.now();\n let positions: Map<string, { x: number; y: number }>;\n const laidOut = new Set(layoutNodes.map((n) => n.id));\n try {\n positions = await runner.run({\n algorithm: 'force',\n nodes: layoutNodes,\n edges: layoutEdges,\n options: {\n initial: [...previous].filter(([id]) => laidOut.has(id)),\n // Clusters are big circles that must not overlap; a handful of nodes needs room for labels.\n ...(mode === 'clusters'\n ? { linkDistance: 180, charge: -900, collidePadding: 28, collideIterations: 4 }\n : layoutNodes.length <= 40\n ? { linkDistance: 90, charge: -320, collidePadding: 24 }\n : {}),\n },\n });\n } catch (error) {\n if (layouts.isCurrent(token) && !disposed) store.set({ status: 'error', error: errorText(error) });\n return;\n }\n if (!layouts.isCurrent(token) || disposed) return;\n\n const radius = new Map(layoutNodes.map((n) => [n.id, n.r]));\n const circles = [...positions].map(([id, p]) => ({ id, x: p.x, y: p.y, r: radius.get(id) ?? 6 }));\n hits = createHitIndex(circles);\n const state = store.getState();\n const refit = !userMoved || previousMode !== mode;\n store.set({\n status: 'ready',\n error: null,\n positions,\n layoutMs: Math.round(performance.now() - started),\n ...(refit ? { viewport: fitViewport(boundsOf(circles), state.size) } : {}),\n });\n if (refit) userMoved = false;\n }\n\n const controller: GraphController = {\n getState: store.getState,\n subscribe: store.subscribe,\n\n async load() {\n const token = loads.next();\n store.set({ status: 'loading', error: null });\n try {\n const loaded = await client.graph(scope, { limit, includeSuperseded: true, includeForgotten: true });\n if (!loads.isCurrent(token) || disposed) return;\n snapshot = loaded;\n const { selectedId, lineage } = store.getState();\n const present = new Set(loaded.nodes.map((n) => n.id));\n store.set({\n totalMemories: loaded.totalMemories,\n loaded: loaded.nodes.length,\n truncated: loaded.truncated,\n selectedId: selectedId && present.has(selectedId) ? selectedId : null,\n lineage: lineage && present.has(lineage.rootId) ? lineage : null,\n });\n await recompute();\n } catch (error) {\n if (loads.isCurrent(token) && !disposed) store.set({ status: 'error', error: errorText(error) });\n }\n },\n\n setFilter(patch) {\n store.set((s) => ({ filter: { ...s.filter, ...patch } }));\n void recompute();\n },\n\n select(memoryId) {\n if (store.getState().selectedId === memoryId) return;\n store.set({ selectedId: memoryId });\n options.onSelect?.(memoryId);\n },\n\n async expandLineage(memoryId) {\n const token = loads.next();\n try {\n const graph = await client.getLineage(scope, memoryId);\n if (!loads.isCurrent(token) || disposed) return;\n if (!graph) {\n store.set({ lineage: null });\n return;\n }\n store.set({\n lineage: {\n rootId: memoryId,\n memoryIds: new Set(graph.memories.map((m) => m.id)),\n edges: graph.edges.filter((e) => e.relation !== 'source'),\n },\n });\n // Lineage members hidden by the filter or clustering are shown alongside the matches.\n const { mode } = store.getState();\n if (mode === 'clusters') store.set((s) => ({ filter: { ...s.filter, search: memoryId } }));\n await recompute();\n } catch (error) {\n if (loads.isCurrent(token) && !disposed) store.set({ status: 'error', error: errorText(error) });\n }\n },\n\n collapseLineage() {\n if (!store.getState().lineage) return;\n store.set({ lineage: null });\n void recompute();\n },\n\n expandCluster(clusterId) {\n const cluster = store.getState().clusters.find((c) => c.id === clusterId);\n if (!cluster || !cluster.key) return;\n const current = store.getState().filter.search.trim();\n controller.setFilter({ search: current ? `${current} ${cluster.key}` : cluster.key });\n },\n\n setSize(width, height) {\n const { size, viewport } = store.getState();\n if (size.width === width && size.height === height) return;\n const first = size.width === 0 || size.height === 0;\n // Keep the centre where it was when the canvas resizes.\n const next = first ? viewport : panBy(viewport, (width - size.width) / 2, (height - size.height) / 2);\n store.set({ size: { width, height }, viewport: next });\n if (first && !userMoved) controller.fit();\n },\n\n panBy(dx, dy) {\n userMoved = true;\n store.set((s) => ({ viewport: panBy(s.viewport, dx, dy), hover: null }));\n },\n\n zoomAt(screen, factor) {\n userMoved = true;\n store.set((s) => ({ viewport: zoomAt(s.viewport, screen, factor), hover: null }));\n },\n\n fit() {\n const { positions, size, mode, clusters, nodes } = store.getState();\n const radius = new Map<string, number>(\n mode === 'clusters' ? clusters.map((c) => [c.id, clusterRadius(c.count)]) : nodes.map((n) => [n.id, nodeRadius(n)]),\n );\n userMoved = false;\n store.set({ viewport: fitViewport(boundsOf([...positions].map(([id, p]) => ({ ...p, r: radius.get(id) ?? 6 }))), size) });\n },\n\n pick(screen) {\n const { viewport, mode, clusters } = store.getState();\n // Four screen pixels of slop, whatever the zoom.\n const hit = hits.pick(toWorld(viewport, screen), 4 / viewport.k);\n if (!hit) return null;\n if (mode === 'clusters') {\n const cluster = clusters.find((c) => c.id === hit.id);\n return cluster ? { type: 'cluster', id: cluster.id, key: cluster.key } : null;\n }\n return { type: 'node', id: hit.id };\n },\n\n hover(screen) {\n const previous = store.getState().hover;\n const pick = screen ? controller.pick(screen) : null;\n if (!pick) {\n if (previous) store.set({ hover: null });\n return;\n }\n // Anchor the tooltip to the target, not the pointer, so it holds still while the pointer moves over it.\n if (previous && previous.pick.id === pick.id) return;\n const { viewport, positions } = store.getState();\n const p = positions.get(pick.id)!;\n store.set({ hover: { pick, x: p.x * viewport.k + viewport.x, y: p.y * viewport.k + viewport.y } });\n },\n\n dispose() {\n disposed = true;\n loads.cancel();\n layouts.cancel();\n },\n };\n\n if (options.autoload !== false) void controller.load();\n return controller;\n}\n","import { scopeOf, type LineageGraph, type MemnestApi } from '@memnest/core';\nimport { documentRadius, nodeRadius } from '../encoding';\nimport { boundsOf, type Bounds, type Point } from '../geometry';\nimport { layeredLayout } from '../layout/layered';\nimport { createSequencer, createStore, errorText, type Observable } from '../store';\n\nexport interface LineageState {\n status: 'idle' | 'loading' | 'ready' | 'not-found' | 'error';\n error: string | null;\n rootId: string | null;\n graph: LineageGraph | null;\n /** Newer memories on the left, older ones and source documents to the right. */\n positions: ReadonlyMap<string, Point>;\n bounds: Bounds | null;\n}\n\nexport interface LineageController extends Observable<LineageState> {\n load(memoryId: string): Promise<void>;\n reload(): Promise<void>;\n clear(): void;\n dispose(): void;\n}\n\n/** Lays out a lineage graph: memories and their source documents as a layered DAG. */\nexport function layoutLineage(graph: LineageGraph): { positions: Map<string, Point>; bounds: Bounds | null } {\n const nodes = [\n ...graph.memories.map((m) => ({ id: m.id, r: nodeRadius(m) })),\n ...graph.documents.map((d) => ({ id: d.id, r: documentRadius })),\n ];\n const positions = layeredLayout(nodes, graph.edges, { layerGap: 190, nodeGap: 96 });\n const radius = new Map(nodes.map((n) => [n.id, n.r]));\n return { positions, bounds: boundsOf([...positions].map(([id, p]) => ({ ...p, r: radius.get(id)! + 90 }))) };\n}\n\nexport function createLineageController(options: { client: MemnestApi; containerTag: string }): LineageController {\n const scope = scopeOf(options.containerTag);\n const store = createStore<LineageState>({ status: 'idle', error: null, rootId: null, graph: null, positions: new Map(), bounds: null });\n const seq = createSequencer();\n\n const controller: LineageController = {\n getState: store.getState,\n subscribe: store.subscribe,\n async load(memoryId) {\n const token = seq.next();\n store.set({ status: 'loading', error: null, rootId: memoryId });\n try {\n const graph = await options.client.getLineage(scope, memoryId);\n if (!seq.isCurrent(token)) return;\n if (!graph) {\n store.set({ status: 'not-found', graph: null, positions: new Map(), bounds: null });\n return;\n }\n store.set({ status: 'ready', graph, ...layoutLineage(graph) });\n } catch (error) {\n if (seq.isCurrent(token)) store.set({ status: 'error', error: errorText(error) });\n }\n },\n async reload() {\n const { rootId } = store.getState();\n if (rootId) await controller.load(rootId);\n },\n clear() {\n seq.cancel();\n store.set({ status: 'idle', error: null, rootId: null, graph: null, positions: new Map(), bounds: null });\n },\n dispose: () => seq.cancel(),\n };\n return controller;\n}\n","import { scopeOf, type Chunk, type DocumentRef, type LineageGraph, type MemnestApi, type Memory } from '@memnest/core';\nimport { createSequencer, createStore, errorText, type Observable } from '../store';\n\nexport interface SourceDocument {\n document: DocumentRef;\n /** Loaded on request: the document text (redacted; empty once deleted). */\n content: string | null;\n /** Loaded on request: the raw chunks the memory was extracted from. Direct writes have none. */\n chunks: Chunk[] | null;\n loading: boolean;\n error: string | null;\n}\n\nexport interface DetailState {\n status: 'idle' | 'loading' | 'ready' | 'not-found' | 'error';\n error: string | null;\n memoryId: string | null;\n memory: Memory | null;\n /** The full version history this memory belongs to (UPDATES chain), oldest first. */\n versions: Memory[];\n /** Memories this one enriches, and memories that enrich it. */\n extends: Memory[];\n extendedBy: Memory[];\n sources: SourceDocument[];\n /** Forget is two steps: request, then confirm (D5). */\n forget: 'idle' | 'confirming' | 'forgetting';\n forgetError: string | null;\n}\n\nexport interface DetailController extends Observable<DetailState> {\n load(memoryId: string): Promise<void>;\n reload(): Promise<void>;\n clear(): void;\n loadSource(documentId: string): Promise<void>;\n requestForget(): void;\n cancelForget(): void;\n confirmForget(): Promise<Memory | null>;\n dispose(): void;\n}\n\n/** The UPDATES chain through `rootId`, oldest first. */\nexport function versionChain(graph: LineageGraph, rootId: string): Memory[] {\n const byId = new Map(graph.memories.map((m) => [m.id, m]));\n const newerOf = new Map<string, Memory>();\n for (const m of graph.memories) if (m.supersedes) newerOf.set(m.supersedes, m);\n const root = byId.get(rootId);\n if (!root) return [];\n const chain = [root];\n const seen = new Set([root.id]);\n for (let older = root.supersedes ? byId.get(root.supersedes) : undefined; older && !seen.has(older.id); older = older.supersedes ? byId.get(older.supersedes) : undefined) {\n chain.unshift(older);\n seen.add(older.id);\n }\n for (let newer = newerOf.get(root.id); newer && !seen.has(newer.id); newer = newerOf.get(newer.id)) {\n chain.push(newer);\n seen.add(newer.id);\n }\n return chain;\n}\n\nconst EMPTY: DetailState = {\n status: 'idle',\n error: null,\n memoryId: null,\n memory: null,\n versions: [],\n extends: [],\n extendedBy: [],\n sources: [],\n forget: 'idle',\n forgetError: null,\n};\n\nexport function createDetailController(options: {\n client: MemnestApi;\n containerTag: string;\n /** Called after a memory is forgotten, so other views can refresh. */\n onForgotten?: (memory: Memory) => void;\n}): DetailController {\n const { client } = options;\n const scope = scopeOf(options.containerTag);\n const store = createStore<DetailState>(EMPTY);\n const seq = createSequencer();\n\n const updateSource = (documentId: string, patch: Partial<SourceDocument>) =>\n store.set((s) => ({ sources: s.sources.map((source) => (source.document.id === documentId ? { ...source, ...patch } : source)) }));\n\n const controller: DetailController = {\n getState: store.getState,\n subscribe: store.subscribe,\n\n async load(memoryId) {\n const token = seq.next();\n store.set({ ...EMPTY, status: 'loading', memoryId });\n try {\n const [memory, graph] = await Promise.all([client.getMemory(scope, memoryId), client.getLineage(scope, memoryId)]);\n if (!seq.isCurrent(token)) return;\n if (!memory || !graph) {\n store.set({ status: 'not-found' });\n return;\n }\n const byId = new Map(graph.memories.map((m) => [m.id, m]));\n const sourceIds = new Set(graph.edges.filter((e) => e.relation === 'source' && e.from === memoryId).map((e) => e.to));\n store.set({\n status: 'ready',\n memory,\n versions: versionChain(graph, memoryId),\n extends: memory.extendsIds.flatMap((id) => (byId.has(id) ? [byId.get(id)!] : [])),\n extendedBy: graph.memories.filter((m) => m.extendsIds.includes(memoryId)),\n sources: graph.documents\n .filter((d) => sourceIds.has(d.id))\n .map((document) => ({ document, content: null, chunks: null, loading: false, error: null })),\n });\n } catch (error) {\n if (seq.isCurrent(token)) store.set({ status: 'error', error: errorText(error) });\n }\n },\n\n async reload() {\n const { memoryId } = store.getState();\n if (memoryId) await controller.load(memoryId);\n },\n\n clear() {\n seq.cancel();\n store.set(EMPTY);\n },\n\n async loadSource(documentId) {\n // Guarded by the memory id, not the sequencer: loading a source must not cancel the memory load.\n const memoryId = store.getState().memoryId;\n updateSource(documentId, { loading: true, error: null });\n try {\n const found = await client.getDocument(scope, documentId);\n if (store.getState().memoryId !== memoryId) return;\n updateSource(documentId, {\n loading: false,\n content: found?.document.content ?? null,\n chunks: found?.chunks ?? [],\n ...(found ? {} : { error: 'document not found' }),\n });\n } catch (error) {\n if (store.getState().memoryId === memoryId) updateSource(documentId, { loading: false, error: errorText(error) });\n }\n },\n\n requestForget() {\n const { memory, forget } = store.getState();\n if (!memory || memory.forgottenAt || forget !== 'idle') return;\n store.set({ forget: 'confirming', forgetError: null });\n },\n\n cancelForget() {\n if (store.getState().forget === 'confirming') store.set({ forget: 'idle' });\n },\n\n async confirmForget() {\n const { memory, forget } = store.getState();\n if (!memory || forget !== 'confirming') return null;\n store.set({ forget: 'forgetting', forgetError: null });\n try {\n const forgotten = await client.forget(scope, memory.id);\n if (store.getState().memoryId !== memory.id) return forgotten;\n store.set((s) => ({\n forget: 'idle',\n memory: forgotten,\n versions: s.versions.map((v) => (v.id === forgotten.id ? forgotten : v)),\n }));\n options.onForgotten?.(forgotten);\n return forgotten;\n } catch (error) {\n store.set({ forget: 'confirming', forgetError: errorText(error) });\n return null;\n }\n },\n\n dispose: () => seq.cancel(),\n };\n return controller;\n}\n","import { scopeOf, type ExcludedReason, type MemnestApi, type Memory, type MemoryKind, type SearchResponse } from '@memnest/core';\nimport { createSequencer, createStore, errorText, type Observable } from '../store';\n\nexport interface TraceRow {\n memoryId: string;\n /** Null when the memory could not be loaded (e.g. deleted since). */\n content: string | null;\n kind: MemoryKind | null;\n lexicalRank?: number;\n lexicalScore?: number;\n vectorRank?: number;\n vectorScore?: number;\n rrfScore: number;\n rerankScore?: number;\n included: boolean;\n excludedReason?: ExcludedReason;\n tokens: number;\n /** Tokens used by included memories up to and including this row. */\n cumulativeTokens: number;\n}\n\nexport interface TraceState {\n query: string;\n tokenBudget: number;\n status: 'idle' | 'loading' | 'ready' | 'error';\n error: string | null;\n response: SearchResponse | null;\n /** Every candidate from either retriever, in packing order. */\n rows: TraceRow[];\n /** Index of the first row the budget excluded: the budget line is drawn above it. Null when nothing was cut. */\n budgetLine: number | null;\n /** Budget spent on chunks, which share what memories leave. */\n chunkTokens: number;\n}\n\nexport interface TraceController extends Observable<TraceState> {\n setQuery(query: string): void;\n setTokenBudget(tokens: number): void;\n run(): Promise<void>;\n /** Runs the last query again, e.g. after a forget. No-op before the first run. */\n rerun(): Promise<void>;\n dispose(): void;\n}\n\nexport const DEFAULT_TRACE_BUDGET = 2000;\n\n/** Joins the trace with memory content and computes the budget line. Pure. */\nexport function buildTraceRows(response: SearchResponse, memories: ReadonlyMap<string, Memory>): { rows: TraceRow[]; budgetLine: number | null } {\n let used = 0;\n let budgetLine: number | null = null;\n const rows = response.trace.candidates.map((candidate, index) => {\n if (candidate.included) used += candidate.tokens;\n if (budgetLine === null && candidate.excludedReason === 'budget') budgetLine = index;\n const memory = memories.get(candidate.memoryId);\n return { ...candidate, content: memory?.content ?? null, kind: memory?.kind ?? null, cumulativeTokens: used };\n });\n return { rows, budgetLine };\n}\n\nexport function createTraceController(options: { client: MemnestApi; containerTag: string; tokenBudget?: number }): TraceController {\n const { client } = options;\n const scope = scopeOf(options.containerTag);\n const store = createStore<TraceState>({\n query: '',\n tokenBudget: options.tokenBudget ?? DEFAULT_TRACE_BUDGET,\n status: 'idle',\n error: null,\n response: null,\n rows: [],\n budgetLine: null,\n chunkTokens: 0,\n });\n const seq = createSequencer();\n let lastRun: { query: string; tokenBudget: number } | null = null;\n\n async function execute(query: string, tokenBudget: number): Promise<void> {\n const token = seq.next();\n lastRun = { query, tokenBudget };\n store.set({ status: 'loading', error: null });\n try {\n const response = await client.search(query, scope, { tokenBudget });\n // Excluded candidates are not in the results; fetch them so every row has its text.\n const memories = new Map(response.memories.map((r) => [r.memory.id, r.memory]));\n const missing = response.trace.candidates.map((c) => c.memoryId).filter((id) => !memories.has(id));\n const fetched = await Promise.all(missing.map((id) => client.getMemory(scope, id)));\n if (!seq.isCurrent(token)) return;\n for (const memory of fetched) if (memory) memories.set(memory.id, memory);\n store.set({\n status: 'ready',\n response,\n ...buildTraceRows(response, memories),\n chunkTokens: response.chunks.reduce((sum, c) => sum + c.tokens, 0),\n });\n } catch (error) {\n if (seq.isCurrent(token)) store.set({ status: 'error', error: errorText(error) });\n }\n }\n\n return {\n getState: store.getState,\n subscribe: store.subscribe,\n setQuery: (query) => store.set({ query }),\n setTokenBudget: (tokenBudget) => store.set({ tokenBudget }),\n async run() {\n const { query, tokenBudget } = store.getState();\n if (!query.trim()) return;\n await execute(query, tokenBudget);\n },\n async rerun() {\n if (lastRun) await execute(lastRun.query, lastRun.tokenBudget);\n },\n dispose: () => seq.cancel(),\n };\n}\n","import { scopeOf, type MemnestApi, type Memory } from '@memnest/core';\nimport { shorten } from '../encoding';\nimport { createSequencer, createStore, errorText, type Observable } from '../store';\n\nexport type TimelineStatus = 'current' | 'superseded' | 'expired' | 'forgotten';\n\nexport interface TimelineItem {\n memory: Memory;\n laneId: string;\n start: string;\n /** When the fact stopped (or will stop) being true. Null while it is true with no expiry. */\n end: string | null;\n status: TimelineStatus;\n /** The memory that replaced this one, when superseded. */\n supersededBy: string | null;\n}\n\nexport interface TimelineLane {\n id: string;\n /** The newest fact in the lane. */\n label: string;\n items: TimelineItem[];\n}\n\nexport interface TimelineTick {\n at: string;\n label: string;\n}\n\nexport interface Timeline {\n lanes: TimelineLane[];\n range: { start: string; end: string } | null;\n ticks: TimelineTick[];\n}\n\nexport interface TimelineState extends Timeline {\n topic: string;\n status: 'idle' | 'loading' | 'ready' | 'error';\n error: string | null;\n}\n\nexport interface TimelineController extends Observable<TimelineState> {\n setTopic(topic: string): void;\n run(): Promise<void>;\n rerun(): Promise<void>;\n dispose(): void;\n}\n\nconst DAY = 86_400_000;\nconst iso = (ms: number) => new Date(ms).toISOString();\nconst earliest = (...values: Array<string | undefined | null>) =>\n values.filter((v): v is string => typeof v === 'string').sort()[0] ?? null;\n\n/**\n * Facts over time. Memories linked by UPDATES share a lane; each fact runs from `validFrom` until\n * it was superseded, expired or forgotten, whichever came first. Pure.\n */\nexport function buildTimeline(memories: readonly Memory[], now: string): Timeline {\n const byId = new Map(memories.map((m) => [m.id, m]));\n const newer = new Map<string, Memory>();\n for (const m of memories) if (m.supersedes && byId.has(m.supersedes)) newer.set(m.supersedes, m);\n\n // Lanes: follow each chain to its oldest member.\n const laneOf = new Map<string, string>();\n const rootOf = (m: Memory): string => {\n const seen = new Set<string>();\n let current = m;\n while (current.supersedes && byId.has(current.supersedes) && !seen.has(current.id)) {\n seen.add(current.id);\n current = byId.get(current.supersedes)!;\n }\n return current.id;\n };\n for (const m of memories) laneOf.set(m.id, rootOf(m));\n\n const items: TimelineItem[] = memories.map((memory) => {\n const replacement = newer.get(memory.id);\n const supersededAt = replacement ? earliest(replacement.validFrom, replacement.createdAt) : null;\n const expiredAt = memory.validUntil && memory.validUntil <= now ? memory.validUntil : null;\n const endedAt = earliest(supersededAt, expiredAt, memory.forgottenAt);\n const status: TimelineStatus =\n endedAt === null\n ? memory.isLatest\n ? 'current'\n : 'superseded'\n : endedAt === memory.forgottenAt\n ? 'forgotten'\n : endedAt === supersededAt\n ? 'superseded'\n : 'expired';\n return {\n memory,\n laneId: laneOf.get(memory.id)!,\n start: memory.validFrom,\n // A current fact with a future expiry shows where it will end.\n end: endedAt ?? memory.validUntil ?? null,\n status,\n supersededBy: replacement?.id ?? null,\n };\n });\n\n const lanes = new Map<string, TimelineItem[]>();\n for (const item of items) (lanes.get(item.laneId) ?? lanes.set(item.laneId, []).get(item.laneId)!).push(item);\n const laneList = [...lanes.entries()]\n .map(([id, laneItems]) => {\n laneItems.sort((a, z) => (a.start < z.start ? -1 : a.start > z.start ? 1 : a.memory.version - z.memory.version));\n return { id, label: shorten(laneItems.at(-1)!.memory.content, 60), items: laneItems };\n })\n .sort((a, z) => (a.items[0]!.start < z.items[0]!.start ? -1 : 1));\n\n if (items.length === 0) return { lanes: [], range: null, ticks: [] };\n const start = Date.parse(items.map((i) => i.start).sort()[0]!);\n const lastEnd = Math.max(Date.parse(now), ...items.map((i) => Date.parse(i.end ?? now)));\n // A little room either side, and at least a day, so a single point in time still reads as a line.\n const span = Math.max(lastEnd - start, DAY);\n const range = { start: iso(start - span * 0.03), end: iso(start + span * 1.03) };\n return { lanes: laneList, range, ticks: timeTicks(range.start, range.end) };\n}\n\n/** About `count` evenly spaced, human-readable ticks (UTC). */\nexport function timeTicks(start: string, end: string, count = 6): TimelineTick[] {\n const from = Date.parse(start);\n const to = Date.parse(end);\n const span = Math.max(to - from, 1);\n const withTime = span < 3 * DAY;\n const crossesYear = new Date(from).getUTCFullYear() !== new Date(to).getUTCFullYear();\n const format = new Intl.DateTimeFormat('en', {\n timeZone: 'UTC',\n month: 'short',\n day: 'numeric',\n ...(withTime ? { hour: '2-digit', minute: '2-digit', hourCycle: 'h23' as const } : crossesYear ? { year: 'numeric' } : {}),\n });\n return Array.from({ length: count }, (_, i) => {\n const at = from + (span * i) / (count - 1);\n return { at: iso(at), label: format.format(at) };\n });\n}\n\nexport function createTimelineController(options: { client: MemnestApi; containerTag: string; now?: () => string }): TimelineController {\n const { client } = options;\n const scope = scopeOf(options.containerTag);\n const now = options.now ?? (() => new Date().toISOString());\n const store = createStore<TimelineState>({ topic: '', status: 'idle', error: null, lanes: [], range: null, ticks: [] });\n const seq = createSequencer();\n let lastTopic: string | null = null;\n\n async function execute(topic: string): Promise<void> {\n const token = seq.next();\n lastTopic = topic;\n store.set({ status: 'loading', error: null });\n try {\n // The trace names every matching memory, superseded and forgotten ones included.\n const response = await client.search(topic, scope, { tokenBudget: 1, candidates: 30 });\n const found = new Map(response.memories.map((r) => [r.memory.id, r.memory]));\n const ids = response.trace.candidates.map((c) => c.memoryId);\n for (const memory of await Promise.all(ids.filter((id) => !found.has(id)).map((id) => client.getMemory(scope, id)))) {\n if (memory) found.set(memory.id, memory);\n }\n // Complete each version chain, so a switch shows both sides even if only one matched the words.\n const chainRoots = [...found.values()].filter((m) => m.supersedes || !m.isLatest).slice(0, 20);\n for (const graph of await Promise.all(chainRoots.map((m) => client.getLineage(scope, m.id)))) {\n for (const m of graph?.memories ?? []) {\n if (graph!.edges.some((e) => e.relation === 'updates' && (e.from === m.id || e.to === m.id))) found.set(m.id, m);\n }\n }\n if (!seq.isCurrent(token)) return;\n store.set({ status: 'ready', ...buildTimeline([...found.values()], now()) });\n } catch (error) {\n if (seq.isCurrent(token)) store.set({ status: 'error', error: errorText(error) });\n }\n }\n\n return {\n getState: store.getState,\n subscribe: store.subscribe,\n setTopic: (topic) => store.set({ topic }),\n async run() {\n const { topic } = store.getState();\n if (topic.trim()) await execute(topic);\n },\n async rerun() {\n if (lastTopic !== null) await execute(lastTopic);\n },\n dispose: () => seq.cancel(),\n };\n}\n","import { scopeOf, type MemnestApi, type Memory } from '@memnest/core';\nimport { createSequencer, createStore, errorText, type Observable } from '../store';\n\nexport interface FinderState {\n query: string;\n /** Include superseded and forgotten memories. */\n includeHistory: boolean;\n status: 'idle' | 'loading' | 'ready' | 'error';\n error: string | null;\n items: Memory[];\n /** More pages exist (browsing without a query). */\n hasMore: boolean;\n}\n\nexport interface FinderController extends Observable<FinderState> {\n setQuery(query: string): void;\n setIncludeHistory(include: boolean): void;\n /** Searches when there is a query; otherwise lists the newest memories. */\n run(): Promise<void>;\n loadMore(): Promise<void>;\n dispose(): void;\n}\n\nconst PAGE = 50;\n\n/** Finds memories to inspect: by search (with every candidate the trace names), or by browsing newest first. */\nexport function createFinderController(options: { client: MemnestApi; containerTag: string }): FinderController {\n const { client } = options;\n const scope = scopeOf(options.containerTag);\n const store = createStore<FinderState>({ query: '', includeHistory: false, status: 'idle', error: null, items: [], hasMore: false });\n const seq = createSequencer();\n const visible = (m: Memory, includeHistory: boolean) => includeHistory || (m.isLatest && !m.forgottenAt);\n\n async function search(query: string, includeHistory: boolean, token: number) {\n const response = await client.search(query, scope, { tokenBudget: 1_000_000, candidates: PAGE });\n const found = new Map(response.memories.map((r) => [r.memory.id, r.memory]));\n const ids = response.trace.candidates.map((c) => c.memoryId);\n const missing = ids.filter((id) => !found.has(id));\n for (const memory of await Promise.all(missing.map((id) => client.getMemory(scope, id)))) if (memory) found.set(memory.id, memory);\n if (!seq.isCurrent(token)) return;\n store.set({ status: 'ready', hasMore: false, items: ids.flatMap((id) => (found.has(id) && visible(found.get(id)!, includeHistory) ? [found.get(id)!] : [])) });\n }\n\n /** Pages in creation order, oldest first (listMemories' keyset order). */\n async function browse(includeHistory: boolean, token: number, after?: string) {\n const page = await client.listMemories(scope, { limit: PAGE, ...(after ? { after } : {}) }, { latestOnly: !includeHistory, includeForgotten: includeHistory });\n if (!seq.isCurrent(token)) return;\n store.set((s) => ({ status: 'ready', items: after ? [...s.items, ...page] : page, hasMore: page.length === PAGE }));\n }\n\n return {\n getState: store.getState,\n subscribe: store.subscribe,\n setQuery: (query) => store.set({ query }),\n setIncludeHistory: (includeHistory) => store.set({ includeHistory }),\n async run() {\n const token = seq.next();\n const { query, includeHistory } = store.getState();\n store.set({ status: 'loading', error: null });\n try {\n if (query.trim()) await search(query, includeHistory, token);\n else await browse(includeHistory, token);\n } catch (error) {\n if (seq.isCurrent(token)) store.set({ status: 'error', error: errorText(error) });\n }\n },\n async loadMore() {\n const { items, hasMore, includeHistory, query, status } = store.getState();\n if (!hasMore || query.trim() || status === 'loading' || items.length === 0) return;\n const token = seq.next();\n store.set({ status: 'loading' });\n try {\n await browse(includeHistory, token, items.at(-1)!.id);\n } catch (error) {\n if (seq.isCurrent(token)) store.set({ status: 'error', error: errorText(error) });\n }\n },\n dispose: () => seq.cancel(),\n };\n}\n","import type { MemnestApi, Memory } from '@memnest/core';\nimport { createDetailController, type DetailController } from './controllers/detail';\nimport { createFinderController, type FinderController } from './controllers/finder';\nimport { createGraphController, type GraphController, type GraphControllerOptions } from './controllers/graph';\nimport { createLineageController, type LineageController } from './controllers/lineage';\nimport { createTimelineController, type TimelineController } from './controllers/timeline';\nimport { createTraceController, type TraceController } from './controllers/trace';\nimport type { LayoutRunner } from './layout/runner';\nimport { createStore, type Observable } from './store';\n\nexport interface SelectionState {\n memoryId: string | null;\n}\n\nexport interface Workspace {\n containerTag: string;\n selection: Observable<SelectionState>;\n /** Selecting a memory anywhere opens its detail and lineage, and highlights it in the graph. */\n select(memoryId: string | null): void;\n finder: FinderController;\n detail: DetailController;\n lineage: LineageController;\n trace: TraceController;\n timeline: TimelineController;\n graph: GraphController;\n /** Reloads every view that has data, e.g. after new memories arrive. */\n refresh(): Promise<void>;\n dispose(): void;\n}\n\nexport interface WorkspaceOptions {\n client: MemnestApi;\n containerTag: string;\n layout?: LayoutRunner;\n graph?: Pick<GraphControllerOptions, 'clusterThreshold' | 'limit' | 'filter' | 'autoload'>;\n now?: () => string;\n}\n\n/** Every controller for one container, wired so selection and forgetting stay consistent across views. */\nexport function createWorkspace(options: WorkspaceOptions): Workspace {\n const { client, containerTag } = options;\n const selection = createStore<SelectionState>({ memoryId: null });\n let workspace: Workspace;\n\n const onForgotten = (_memory: Memory) => {\n // A forgotten memory changes what search returns, what the graph shows and what the timeline says.\n void Promise.all([\n workspace.finder.run(),\n workspace.trace.rerun(),\n workspace.timeline.rerun(),\n workspace.graph.load(),\n workspace.lineage.reload(),\n ]);\n };\n\n const select = (memoryId: string | null) => {\n if (selection.getState().memoryId === memoryId) return;\n selection.set({ memoryId });\n workspace.graph.select(memoryId);\n if (memoryId) {\n void workspace.detail.load(memoryId);\n void workspace.lineage.load(memoryId);\n } else {\n workspace.detail.clear();\n workspace.lineage.clear();\n }\n };\n\n workspace = {\n containerTag,\n selection,\n select,\n finder: createFinderController({ client, containerTag }),\n detail: createDetailController({ client, containerTag, onForgotten }),\n lineage: createLineageController({ client, containerTag }),\n trace: createTraceController({ client, containerTag }),\n timeline: createTimelineController({ client, containerTag, ...(options.now ? { now: options.now } : {}) }),\n graph: createGraphController({\n client,\n containerTag,\n ...options.graph,\n ...(options.layout ? { layout: options.layout } : {}),\n onSelect: (memoryId) => select(memoryId),\n }),\n async refresh() {\n await Promise.all([\n workspace.finder.run(),\n workspace.trace.rerun(),\n workspace.timeline.rerun(),\n workspace.graph.load(),\n workspace.detail.reload(),\n workspace.lineage.reload(),\n ]);\n },\n dispose() {\n for (const controller of [workspace.finder, workspace.detail, workspace.lineage, workspace.trace, workspace.timeline, workspace.graph]) {\n controller.dispose();\n }\n },\n };\n return workspace;\n}\n"],"mappings":"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","names":["scopeOf","scopeOf","scopeOf","scopeOf","scopeOf","scopeOf","scopeOf","scopeOf","scopeOf","scopeOf"]}
@@ -0,0 +1,142 @@
1
+ interface Point {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ interface Size {
6
+ width: number;
7
+ height: number;
8
+ }
9
+ /** screen = world × k + (x, y) */
10
+ interface Viewport {
11
+ x: number;
12
+ y: number;
13
+ k: number;
14
+ }
15
+ interface Bounds {
16
+ minX: number;
17
+ minY: number;
18
+ maxX: number;
19
+ maxY: number;
20
+ }
21
+ declare const IDENTITY_VIEWPORT: Viewport;
22
+ declare const ZOOM_LIMITS: {
23
+ readonly min: 0.02;
24
+ readonly max: 8;
25
+ };
26
+ declare function boundsOf(circles: Iterable<Point & {
27
+ r?: number;
28
+ }>): Bounds | null;
29
+ /** The viewport that shows `bounds` centred in `size`, never zooming in past `maxK`. */
30
+ declare function fitViewport(bounds: Bounds | null, size: Size, padding?: number, maxK?: number): Viewport;
31
+ /** Zooms by `factor` keeping the world point under `screen` fixed. */
32
+ declare function zoomAt(viewport: Viewport, screen: Point, factor: number): Viewport;
33
+ declare const panBy: (viewport: Viewport, dx: number, dy: number) => Viewport;
34
+ declare const toWorld: (viewport: Viewport, screen: Point) => Point;
35
+ declare const toScreen: (viewport: Viewport, world: Point) => Point;
36
+ declare const clamp: (value: number, min: number, max: number) => number;
37
+
38
+ interface LayoutNode {
39
+ id: string;
40
+ /** Radius, for collision. Default 6. */
41
+ r?: number;
42
+ }
43
+ interface LayoutEdge {
44
+ from: string;
45
+ to: string;
46
+ }
47
+
48
+ interface ForceOptions {
49
+ /** Simulation ticks. Default 300, fewer for large graphs. */
50
+ iterations?: number;
51
+ /** Seed for the simulation's randomness. Same seed, same input, same output. Default 1. */
52
+ seed?: number;
53
+ /** Starting positions, e.g. the previous layout, so nodes stay where the user saw them. */
54
+ initial?: ReadonlyMap<string, Point> | ReadonlyArray<[string, Point]>;
55
+ /** Default 36. */
56
+ linkDistance?: number;
57
+ /** Many-body strength per node. Default -40. */
58
+ charge?: number;
59
+ /** Extra space kept around each node. Default 2. */
60
+ collidePadding?: number;
61
+ /** Collision passes per tick; more keeps large circles apart. Default 1. */
62
+ collideIterations?: number;
63
+ }
64
+ /**
65
+ * A seeded generator in [0, 1) (mulberry32). Not a plain LCG: consecutive LCG outputs are correlated,
66
+ * so (angle, radius) pairs fall on lattice lines and a scatter comes out as visible spiral arms.
67
+ */
68
+ declare function seededRandom(seed: number): () => number;
69
+ /** d3-force layout run to completion off-screen: link, many-body, collision and weak centring. */
70
+ declare function forceLayout(nodes: readonly LayoutNode[], edges: readonly LayoutEdge[], options?: ForceOptions): Map<string, Point>;
71
+
72
+ interface LayeredOptions {
73
+ /** Distance between layers (x). Default 220. */
74
+ layerGap?: number;
75
+ /** Distance between nodes in a layer (y). Default 84. */
76
+ nodeGap?: number;
77
+ /** Barycentre ordering passes. Default 6. */
78
+ sweeps?: number;
79
+ }
80
+ /**
81
+ * A Sugiyama-style layered layout for small DAGs (lineage): longest-path layering, so every edge
82
+ * points to a later layer; barycentre sweeps to reduce crossings; even spacing within a layer.
83
+ * Deterministic: the same input always yields the same positions. Edges run left → right,
84
+ * `from` in an earlier layer than `to`. Edges that would close a cycle are ignored.
85
+ */
86
+ declare function layeredLayout(nodes: readonly LayoutNode[], edges: readonly LayoutEdge[], options?: LayeredOptions): Map<string, Point>;
87
+
88
+ type LayoutRequest = {
89
+ algorithm: 'force';
90
+ nodes: LayoutNode[];
91
+ edges: LayoutEdge[];
92
+ options?: Omit<ForceOptions, 'initial'> & {
93
+ initial?: Array<[string, Point]>;
94
+ };
95
+ } | {
96
+ algorithm: 'layered';
97
+ nodes: LayoutNode[];
98
+ edges: LayoutEdge[];
99
+ options?: LayeredOptions;
100
+ };
101
+ /** d3-force for the global graph, a layered (Sugiyama-style) layout for lineage DAGs. */
102
+ declare function computeLayout(nodes: readonly LayoutNode[], edges: readonly LayoutEdge[], opts: {
103
+ algorithm: 'force';
104
+ } & ForceOptions): Map<string, Point>;
105
+ declare function computeLayout(nodes: readonly LayoutNode[], edges: readonly LayoutEdge[], opts: {
106
+ algorithm: 'layered';
107
+ } & LayeredOptions): Map<string, Point>;
108
+ declare function runLayoutRequest(request: LayoutRequest): Map<string, Point>;
109
+
110
+ /** Above this many nodes, layout runs off the main thread when a worker is available. */
111
+ declare const WORKER_LAYOUT_THRESHOLD = 500;
112
+ interface LayoutRunner {
113
+ run(request: LayoutRequest): Promise<Map<string, Point>>;
114
+ dispose?(): void;
115
+ }
116
+ /**
117
+ * Runs layout on the calling thread, after yielding once so the caller's "laying out" state can
118
+ * render first. The default, and what tests use; browsers should use a worker for large graphs.
119
+ */
120
+ declare const inlineLayoutRunner: LayoutRunner;
121
+ /** The subset of Worker (main side) or the worker global (worker side) the protocol needs. */
122
+ interface MessagePortLike {
123
+ postMessage(message: unknown): void;
124
+ addEventListener(type: 'message', listener: (event: {
125
+ data: unknown;
126
+ }) => void): void;
127
+ removeEventListener(type: 'message', listener: (event: {
128
+ data: unknown;
129
+ }) => void): void;
130
+ terminate?(): void;
131
+ }
132
+ /** Worker side: answers layout requests. `@memnest/ui-core/layout-worker` calls this on the worker global. */
133
+ declare function serveLayoutRequests(port: MessagePortLike): () => void;
134
+ /**
135
+ * Main side: small layouts run inline (posting would cost more than it saves); large ones are
136
+ * sent to the worker, so a 2,000-node force layout never blocks input or painting.
137
+ */
138
+ declare function createWorkerLayoutRunner(port: MessagePortLike, options?: {
139
+ threshold?: number;
140
+ }): LayoutRunner;
141
+
142
+ export { type Bounds as B, type ForceOptions as F, IDENTITY_VIEWPORT as I, type LayoutRunner as L, type MessagePortLike as M, type Point as P, type Size as S, type Viewport as V, WORKER_LAYOUT_THRESHOLD as W, ZOOM_LIMITS as Z, type LayeredOptions as a, type LayoutEdge as b, type LayoutNode as c, type LayoutRequest as d, boundsOf as e, clamp as f, computeLayout as g, createWorkerLayoutRunner as h, fitViewport as i, forceLayout as j, inlineLayoutRunner as k, layeredLayout as l, serveLayoutRequests as m, toWorld as n, panBy as p, runLayoutRequest as r, seededRandom as s, toScreen as t, zoomAt as z };
@@ -0,0 +1,142 @@
1
+ interface Point {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ interface Size {
6
+ width: number;
7
+ height: number;
8
+ }
9
+ /** screen = world × k + (x, y) */
10
+ interface Viewport {
11
+ x: number;
12
+ y: number;
13
+ k: number;
14
+ }
15
+ interface Bounds {
16
+ minX: number;
17
+ minY: number;
18
+ maxX: number;
19
+ maxY: number;
20
+ }
21
+ declare const IDENTITY_VIEWPORT: Viewport;
22
+ declare const ZOOM_LIMITS: {
23
+ readonly min: 0.02;
24
+ readonly max: 8;
25
+ };
26
+ declare function boundsOf(circles: Iterable<Point & {
27
+ r?: number;
28
+ }>): Bounds | null;
29
+ /** The viewport that shows `bounds` centred in `size`, never zooming in past `maxK`. */
30
+ declare function fitViewport(bounds: Bounds | null, size: Size, padding?: number, maxK?: number): Viewport;
31
+ /** Zooms by `factor` keeping the world point under `screen` fixed. */
32
+ declare function zoomAt(viewport: Viewport, screen: Point, factor: number): Viewport;
33
+ declare const panBy: (viewport: Viewport, dx: number, dy: number) => Viewport;
34
+ declare const toWorld: (viewport: Viewport, screen: Point) => Point;
35
+ declare const toScreen: (viewport: Viewport, world: Point) => Point;
36
+ declare const clamp: (value: number, min: number, max: number) => number;
37
+
38
+ interface LayoutNode {
39
+ id: string;
40
+ /** Radius, for collision. Default 6. */
41
+ r?: number;
42
+ }
43
+ interface LayoutEdge {
44
+ from: string;
45
+ to: string;
46
+ }
47
+
48
+ interface ForceOptions {
49
+ /** Simulation ticks. Default 300, fewer for large graphs. */
50
+ iterations?: number;
51
+ /** Seed for the simulation's randomness. Same seed, same input, same output. Default 1. */
52
+ seed?: number;
53
+ /** Starting positions, e.g. the previous layout, so nodes stay where the user saw them. */
54
+ initial?: ReadonlyMap<string, Point> | ReadonlyArray<[string, Point]>;
55
+ /** Default 36. */
56
+ linkDistance?: number;
57
+ /** Many-body strength per node. Default -40. */
58
+ charge?: number;
59
+ /** Extra space kept around each node. Default 2. */
60
+ collidePadding?: number;
61
+ /** Collision passes per tick; more keeps large circles apart. Default 1. */
62
+ collideIterations?: number;
63
+ }
64
+ /**
65
+ * A seeded generator in [0, 1) (mulberry32). Not a plain LCG: consecutive LCG outputs are correlated,
66
+ * so (angle, radius) pairs fall on lattice lines and a scatter comes out as visible spiral arms.
67
+ */
68
+ declare function seededRandom(seed: number): () => number;
69
+ /** d3-force layout run to completion off-screen: link, many-body, collision and weak centring. */
70
+ declare function forceLayout(nodes: readonly LayoutNode[], edges: readonly LayoutEdge[], options?: ForceOptions): Map<string, Point>;
71
+
72
+ interface LayeredOptions {
73
+ /** Distance between layers (x). Default 220. */
74
+ layerGap?: number;
75
+ /** Distance between nodes in a layer (y). Default 84. */
76
+ nodeGap?: number;
77
+ /** Barycentre ordering passes. Default 6. */
78
+ sweeps?: number;
79
+ }
80
+ /**
81
+ * A Sugiyama-style layered layout for small DAGs (lineage): longest-path layering, so every edge
82
+ * points to a later layer; barycentre sweeps to reduce crossings; even spacing within a layer.
83
+ * Deterministic: the same input always yields the same positions. Edges run left → right,
84
+ * `from` in an earlier layer than `to`. Edges that would close a cycle are ignored.
85
+ */
86
+ declare function layeredLayout(nodes: readonly LayoutNode[], edges: readonly LayoutEdge[], options?: LayeredOptions): Map<string, Point>;
87
+
88
+ type LayoutRequest = {
89
+ algorithm: 'force';
90
+ nodes: LayoutNode[];
91
+ edges: LayoutEdge[];
92
+ options?: Omit<ForceOptions, 'initial'> & {
93
+ initial?: Array<[string, Point]>;
94
+ };
95
+ } | {
96
+ algorithm: 'layered';
97
+ nodes: LayoutNode[];
98
+ edges: LayoutEdge[];
99
+ options?: LayeredOptions;
100
+ };
101
+ /** d3-force for the global graph, a layered (Sugiyama-style) layout for lineage DAGs. */
102
+ declare function computeLayout(nodes: readonly LayoutNode[], edges: readonly LayoutEdge[], opts: {
103
+ algorithm: 'force';
104
+ } & ForceOptions): Map<string, Point>;
105
+ declare function computeLayout(nodes: readonly LayoutNode[], edges: readonly LayoutEdge[], opts: {
106
+ algorithm: 'layered';
107
+ } & LayeredOptions): Map<string, Point>;
108
+ declare function runLayoutRequest(request: LayoutRequest): Map<string, Point>;
109
+
110
+ /** Above this many nodes, layout runs off the main thread when a worker is available. */
111
+ declare const WORKER_LAYOUT_THRESHOLD = 500;
112
+ interface LayoutRunner {
113
+ run(request: LayoutRequest): Promise<Map<string, Point>>;
114
+ dispose?(): void;
115
+ }
116
+ /**
117
+ * Runs layout on the calling thread, after yielding once so the caller's "laying out" state can
118
+ * render first. The default, and what tests use; browsers should use a worker for large graphs.
119
+ */
120
+ declare const inlineLayoutRunner: LayoutRunner;
121
+ /** The subset of Worker (main side) or the worker global (worker side) the protocol needs. */
122
+ interface MessagePortLike {
123
+ postMessage(message: unknown): void;
124
+ addEventListener(type: 'message', listener: (event: {
125
+ data: unknown;
126
+ }) => void): void;
127
+ removeEventListener(type: 'message', listener: (event: {
128
+ data: unknown;
129
+ }) => void): void;
130
+ terminate?(): void;
131
+ }
132
+ /** Worker side: answers layout requests. `@memnest/ui-core/layout-worker` calls this on the worker global. */
133
+ declare function serveLayoutRequests(port: MessagePortLike): () => void;
134
+ /**
135
+ * Main side: small layouts run inline (posting would cost more than it saves); large ones are
136
+ * sent to the worker, so a 2,000-node force layout never blocks input or painting.
137
+ */
138
+ declare function createWorkerLayoutRunner(port: MessagePortLike, options?: {
139
+ threshold?: number;
140
+ }): LayoutRunner;
141
+
142
+ export { type Bounds as B, type ForceOptions as F, IDENTITY_VIEWPORT as I, type LayoutRunner as L, type MessagePortLike as M, type Point as P, type Size as S, type Viewport as V, WORKER_LAYOUT_THRESHOLD as W, ZOOM_LIMITS as Z, type LayeredOptions as a, type LayoutEdge as b, type LayoutNode as c, type LayoutRequest as d, boundsOf as e, clamp as f, computeLayout as g, createWorkerLayoutRunner as h, fitViewport as i, forceLayout as j, inlineLayoutRunner as k, layeredLayout as l, serveLayoutRequests as m, toWorld as n, panBy as p, runLayoutRequest as r, seededRandom as s, toScreen as t, zoomAt as z };