hippo-memory 1.19.0 → 1.21.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,170 @@
1
+ import { loadEntitiesByMemoryId, loadEntitiesByIds, loadNeighborRelations, } from './graph.js';
2
+ import { MAX_HOPS, DEFAULT_MAX_NEIGHBORS } from './graph-recall.js';
3
+ /** Default hops expanded from each seed (MVP; hard cap MAX_HOPS=3 reused from graph-recall). */
4
+ export const DEFAULT_GRAPH_HOPS = 2;
5
+ /** Default per-hop multiplicative decay applied to the seed strength. */
6
+ export const DEFAULT_GRAPH_DECAY = 0.5;
7
+ /** Default number of top lexical seeds expanded from. */
8
+ export const DEFAULT_GRAPH_SEED_COUNT = 10;
9
+ /** Recommended RRF weight for the graph stream — a CLI-only convenience default. The
10
+ * library `graphStream.weight` option stays REQUIRED (opt-in is explicit). */
11
+ export const DEFAULT_GRAPH_STREAM_WEIGHT = 0.5;
12
+ /**
13
+ * Pick the top `seedCount` candidates by best lexical rank (lowest position across the
14
+ * BM25 and dense ranked lists), with strength = 1/(bestRank + 1). Pure; exported for
15
+ * direct unit testing. A candidate present in either ranked list is eligible.
16
+ */
17
+ export function selectGraphSeeds(bm25Ranked, cosineRanked, seedCount) {
18
+ if (seedCount <= 0)
19
+ return [];
20
+ const bestPos = new Map();
21
+ const consider = (list) => {
22
+ for (let p = 0; p < list.length; p++) {
23
+ const idx = list[p];
24
+ const prev = bestPos.get(idx);
25
+ if (prev === undefined || p < prev)
26
+ bestPos.set(idx, p);
27
+ }
28
+ };
29
+ consider(bm25Ranked);
30
+ consider(cosineRanked);
31
+ return [...bestPos.entries()]
32
+ .sort((a, b) => a[1] - b[1] || a[0] - b[0]) // best rank asc, then index asc (deterministic)
33
+ .slice(0, seedCount)
34
+ .map(([index, best]) => ({ index, strength: 1 / (best + 1) }));
35
+ }
36
+ /**
37
+ * Accumulate per-entryIndex graph-proximity scores from ONE store's graph into
38
+ * `graphScore`. Pure reads. `seeds` are the lexical seeds (index + strength); only the
39
+ * seeds whose entities live in THIS store are expanded. The origin seed strength is
40
+ * carried UNCHANGED along each BFS path; the per-hop decay is applied as decay^depth so
41
+ * a neighbour's score = originSeedStrength x decay^(graph distance).
42
+ */
43
+ function accumulateForRoot(root, seeds, entries, memIdToIndex, graphScore, hops, decay, maxNeighbors, tenantId) {
44
+ if (seeds.length === 0)
45
+ return;
46
+ // The strongest seed strength per source memory id (a memId could appear once, but
47
+ // guard against dup indices mapping to the same memId).
48
+ const strengthByMemId = new Map();
49
+ for (const s of seeds) {
50
+ const memId = entries[s.index].id;
51
+ strengthByMemId.set(memId, Math.max(strengthByMemId.get(memId) ?? 0, s.strength));
52
+ }
53
+ const seedEntities = loadEntitiesByMemoryId(root, tenantId, [...strengthByMemId.keys()]);
54
+ if (seedEntities.length === 0)
55
+ return;
56
+ // entityId -> origin seed strength (carried unchanged along the path).
57
+ const originStrength = new Map();
58
+ for (const e of seedEntities) {
59
+ const st = strengthByMemId.get(e.memoryId) ?? 0;
60
+ originStrength.set(e.id, Math.max(originStrength.get(e.id) ?? 0, st));
61
+ }
62
+ const visited = new Set(seedEntities.map((e) => e.id)); // seeds never re-reached
63
+ const reachedScore = new Map(); // entityId -> best score
64
+ let frontier = seedEntities.map((e) => e.id);
65
+ let frontierStrength = new Map(originStrength); // entityId -> seed strength
66
+ for (let depth = 1; depth <= hops && frontier.length > 0; depth++) {
67
+ const frontierSet = new Set(frontier);
68
+ const rels = loadNeighborRelations(root, tenantId, frontier, {
69
+ limit: Math.max(maxNeighbors, maxNeighbors * frontier.length),
70
+ });
71
+ const hopFactor = Math.pow(decay, depth);
72
+ // Pass 1: accumulate the STRONGEST reaching-seed strength per new neighbour across ALL
73
+ // relations at this depth BEFORE committing any to `visited` (codex P2). Marking a node
74
+ // visited mid-loop would lock it to whichever relation SQLite returned first, so a later
75
+ // edge from a STRONGER lexical seed would be dropped and the neighbour mis-scored. A node
76
+ // already in `visited` was committed at an earlier (shorter) depth and keeps that score.
77
+ const bestStrengthThisDepth = new Map();
78
+ for (const rel of rels) {
79
+ const fromIn = frontierSet.has(rel.fromEntityId);
80
+ const toIn = frontierSet.has(rel.toEntityId);
81
+ let neighborId;
82
+ let reacherId;
83
+ if (fromIn && !toIn) {
84
+ neighborId = rel.toEntityId;
85
+ reacherId = rel.fromEntityId;
86
+ }
87
+ else if (toIn && !fromIn) {
88
+ neighborId = rel.fromEntityId;
89
+ reacherId = rel.toEntityId;
90
+ }
91
+ else
92
+ continue;
93
+ if (visited.has(neighborId))
94
+ continue;
95
+ const seedStrength = frontierStrength.get(reacherId) ?? originStrength.get(reacherId) ?? 0;
96
+ bestStrengthThisDepth.set(neighborId, Math.max(bestStrengthThisDepth.get(neighborId) ?? 0, seedStrength));
97
+ }
98
+ // Pass 2: commit strongest-first (then id asc — deterministic), so the per-hop fanout cap
99
+ // keeps the highest-scoring neighbours rather than whichever SQLite happened to return.
100
+ const nextFrontier = [];
101
+ const nextStrength = new Map();
102
+ const ordered = [...bestStrengthThisDepth.keys()].sort((a, b) => {
103
+ const d = bestStrengthThisDepth.get(b) - bestStrengthThisDepth.get(a);
104
+ return d !== 0 ? d : a - b;
105
+ });
106
+ for (const neighborId of ordered) {
107
+ if (nextFrontier.length >= maxNeighbors)
108
+ break; // per-hop fanout cap (strongest kept)
109
+ const seedStrength = bestStrengthThisDepth.get(neighborId);
110
+ visited.add(neighborId);
111
+ reachedScore.set(neighborId, Math.max(reachedScore.get(neighborId) ?? 0, seedStrength * hopFactor));
112
+ nextStrength.set(neighborId, seedStrength);
113
+ nextFrontier.push(neighborId);
114
+ }
115
+ frontier = nextFrontier;
116
+ frontierStrength = nextStrength;
117
+ }
118
+ if (reachedScore.size === 0)
119
+ return;
120
+ // Reached entity ids -> source memory ids -> in-pool entry indices.
121
+ const reachedEntities = loadEntitiesByIds(root, tenantId, [...reachedScore.keys()]);
122
+ for (const ent of reachedEntities) {
123
+ const idx = memIdToIndex.get(ent.memoryId);
124
+ if (idx === undefined)
125
+ continue; // reached memory is not in the candidate pool
126
+ const score = reachedScore.get(ent.id) ?? 0;
127
+ if (score <= 0)
128
+ continue;
129
+ graphScore.set(idx, Math.max(graphScore.get(idx) ?? 0, score));
130
+ }
131
+ }
132
+ /**
133
+ * Produce the graph-retrieval ranked list: `entries[]` indices ordered by graph
134
+ * proximity (desc) to the lexical `seeds`. Only graph-reached, in-pool, non-seed
135
+ * indices appear; the rest are absent (-> rrfFuse absentRank). Pure reads.
136
+ *
137
+ * Returns `[]` when there are no seeds/entries, the graph is empty, no seed maps to an
138
+ * entity, or nothing reached is in-pool — the caller then skips the 3rd fusion list.
139
+ */
140
+ export function graphRankStream(entries, seeds, opts) {
141
+ if (seeds.length === 0 || entries.length === 0)
142
+ return [];
143
+ const hops = Math.min(Math.max(opts.hops ?? DEFAULT_GRAPH_HOPS, 1), MAX_HOPS);
144
+ const decay = opts.decay ?? DEFAULT_GRAPH_DECAY;
145
+ const maxNeighbors = opts.maxNeighbors ?? DEFAULT_MAX_NEIGHBORS;
146
+ const memIdToIndex = new Map();
147
+ for (let i = 0; i < entries.length; i++)
148
+ memIdToIndex.set(entries[i].id, i);
149
+ const graphScore = new Map();
150
+ const roots = opts.globalRoot && opts.globalRoot !== opts.hippoRoot
151
+ ? [opts.hippoRoot, opts.globalRoot]
152
+ : [opts.hippoRoot];
153
+ for (const root of roots) {
154
+ accumulateForRoot(root, seeds, entries, memIdToIndex, graphScore, hops, decay, maxNeighbors, opts.tenantId);
155
+ }
156
+ // Seed-exclusion guard (plan-eng-critic MED): graphScore is keyed by entryIndex
157
+ // GLOBALLY across roots, but each root's BFS visited-set is per-root, so a memory that
158
+ // is a seed in one store could be reached as a neighbour in the other store and pick up
159
+ // a score via max(). Drop every seed index so the "seeds are never scored by the graph
160
+ // stream" invariant holds across roots, not just within a single store's traversal.
161
+ for (const s of seeds)
162
+ graphScore.delete(s.index);
163
+ if (graphScore.size === 0)
164
+ return [];
165
+ return [...graphScore.keys()].sort((a, b) => {
166
+ const d = (graphScore.get(b) ?? 0) - (graphScore.get(a) ?? 0);
167
+ return d !== 0 ? d : a - b; // score desc, then index asc (deterministic)
168
+ });
169
+ }
170
+ //# sourceMappingURL=graph-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-stream.js","sourceRoot":"","sources":["../../src/graph-stream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,310 @@
1
+ /**
2
+ * E3 graph observability + visualization — READ-ONLY over the entity/relation
3
+ * graph (docs/plans/2026-06-02-graph-observability.md).
4
+ *
5
+ * This module only READS the graph (`loadEntities` / `loadRelations`) and renders
6
+ * view-models; it issues no INSERT/UPDATE/DELETE, so `scripts/check-graph-writes.mjs`
7
+ * stays green. Used by the CLI (`hippo graph show` / `hippo graph view`) and the
8
+ * HTTP `GET /v1/graph` route, which all build the same `GraphModel`.
9
+ */
10
+ import { loadEntities, loadEntitiesByName, loadEntitiesByIds, loadRelations, loadNeighborRelations, loadRelationsAmong, withGraphReadSnapshot, } from './graph.js';
11
+ /** Default bound for the CLI viewer / show so an unbounded set is never laid out. */
12
+ export const DEFAULT_VIEW_LIMIT = 500;
13
+ /**
14
+ * Build the view-model from the graph (reads only). With `opts.entity`, returns a
15
+ * focus subgraph: every entity whose name === entity, plus their 1-hop neighbours,
16
+ * plus the edges among that union. Dangling edges (an endpoint outside the node
17
+ * set) are always dropped.
18
+ */
19
+ export function buildGraphModel(hippoRoot, tenantId, opts = {}) {
20
+ const limit = opts.limit ?? DEFAULT_VIEW_LIMIT;
21
+ // All reads run inside ONE read snapshot so a concurrent `graph extract` /
22
+ // sleep-drain rebuild can't make the model mix old entity ids with new relation
23
+ // ids (codex P2). Every load* call below is passed the snapshot connection `db`.
24
+ return withGraphReadSnapshot(hippoRoot, (db) => {
25
+ let nodeEntities;
26
+ let relations;
27
+ let truncated;
28
+ if (opts.entity !== undefined) {
29
+ // Focus subgraph. (1) Query the named entity DIRECTLY (by name, not from a
30
+ // globally-capped list) so it is found even on a graph larger than `limit`.
31
+ // (2) A name can map to MANY entities (e.g. many notes for one customer), so
32
+ // cap the focus matches. (3) Discover 1-hop neighbours and cap the UNION to
33
+ // `limit` nodes. (4) Load ALL edges AMONG the union so neighbour-to-neighbour
34
+ // edges that don't touch the focus are included too. (codex P2s.)
35
+ const focus = loadEntitiesByName(hippoRoot, tenantId, opts.entity, { limit }, db);
36
+ if (focus.length === 0)
37
+ return { nodes: [], edges: [], truncated: false };
38
+ const focusIds = focus.map((e) => e.id);
39
+ const hop = loadNeighborRelations(hippoRoot, tenantId, focusIds, { limit }, db);
40
+ const union = new Set(focusIds);
41
+ let neighboursCapped = false;
42
+ for (const r of hop) {
43
+ if (union.size >= limit) {
44
+ neighboursCapped = true; // node cap filled before all neighbours were added
45
+ break;
46
+ }
47
+ union.add(r.fromEntityId);
48
+ union.add(r.toEntityId);
49
+ }
50
+ const unionIds = [...union].slice(0, limit);
51
+ nodeEntities = loadEntitiesByIds(hippoRoot, tenantId, unionIds, db);
52
+ // Edges AMONG the union (BOTH endpoints in the set): includes
53
+ // neighbour-to-neighbour edges, and the LIMIT can never drop a valid in-union
54
+ // edge in favour of out-of-union rows (codex P2).
55
+ relations = loadRelationsAmong(hippoRoot, tenantId, unionIds, { limit }, db);
56
+ truncated =
57
+ focus.length >= limit ||
58
+ hop.length >= limit || // neighbour scan capped -> a 1-hop neighbour may be omitted (codex P2)
59
+ neighboursCapped || // node cap filled before all neighbours were consumed (codex P2)
60
+ union.size > unionIds.length ||
61
+ relations.length >= limit;
62
+ }
63
+ else {
64
+ nodeEntities = loadEntities(hippoRoot, tenantId, { limit }, db);
65
+ relations = loadRelations(hippoRoot, tenantId, { limit }, db);
66
+ truncated = nodeEntities.length >= limit || relations.length >= limit;
67
+ }
68
+ const nodeIds = new Set(nodeEntities.map((e) => e.id));
69
+ const nodes = nodeEntities.map((e) => ({
70
+ id: e.id,
71
+ type: e.entityType,
72
+ name: e.name,
73
+ }));
74
+ const edges = relations
75
+ .filter((r) => nodeIds.has(r.fromEntityId) && nodeIds.has(r.toEntityId))
76
+ .map((r) => ({ from: r.fromEntityId, to: r.toEntityId, relType: r.relType }));
77
+ return { nodes, edges, truncated };
78
+ });
79
+ }
80
+ const LAYOUT_W = 1000;
81
+ const LAYOUT_H = 700;
82
+ /**
83
+ * Deterministic Fruchterman-Reingold-style force layout. Seeded circular init +
84
+ * fixed iterations, NO `Math.random`, so the same model always yields the same
85
+ * positions (testable, stable output). Non-finite coordinates from a degenerate
86
+ * step are clamped to the viewport centre; all positions are clamped in-bounds.
87
+ */
88
+ export function layoutGraph(model, opts = {}) {
89
+ const width = opts.width ?? LAYOUT_W;
90
+ const height = opts.height ?? LAYOUT_H;
91
+ const iterations = opts.iterations ?? 300;
92
+ const cx = width / 2;
93
+ const cy = height / 2;
94
+ const nodes = model.nodes;
95
+ const n = nodes.length;
96
+ const pos = new Map();
97
+ if (n === 0)
98
+ return pos;
99
+ if (n === 1) {
100
+ pos.set(nodes[0].id, { x: cx, y: cy });
101
+ return pos;
102
+ }
103
+ const radius = Math.min(width, height) * 0.4;
104
+ nodes.forEach((node, i) => {
105
+ const angle = (2 * Math.PI * i) / n;
106
+ pos.set(node.id, { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) });
107
+ });
108
+ const idIndex = new Map(nodes.map((node, i) => [node.id, i]));
109
+ const k = Math.sqrt((width * height) / n); // ideal edge length
110
+ for (let iter = 0; iter < iterations; iter++) {
111
+ const disp = nodes.map(() => ({ x: 0, y: 0 }));
112
+ // Repulsion between all pairs.
113
+ for (let i = 0; i < n; i++) {
114
+ const pi = pos.get(nodes[i].id);
115
+ for (let j = i + 1; j < n; j++) {
116
+ const pj = pos.get(nodes[j].id);
117
+ const dx = pi.x - pj.x;
118
+ const dy = pi.y - pj.y;
119
+ const dist = Math.hypot(dx, dy) || 0.01;
120
+ const rep = (k * k) / dist;
121
+ const ux = dx / dist;
122
+ const uy = dy / dist;
123
+ disp[i].x += ux * rep;
124
+ disp[i].y += uy * rep;
125
+ disp[j].x -= ux * rep;
126
+ disp[j].y -= uy * rep;
127
+ }
128
+ }
129
+ // Attraction along edges.
130
+ for (const e of model.edges) {
131
+ const i = idIndex.get(e.from);
132
+ const j = idIndex.get(e.to);
133
+ if (i === undefined || j === undefined)
134
+ continue;
135
+ const pi = pos.get(e.from);
136
+ const pj = pos.get(e.to);
137
+ const dx = pi.x - pj.x;
138
+ const dy = pi.y - pj.y;
139
+ const dist = Math.hypot(dx, dy) || 0.01;
140
+ const att = (dist * dist) / k;
141
+ const ux = dx / dist;
142
+ const uy = dy / dist;
143
+ disp[i].x -= ux * att;
144
+ disp[i].y -= uy * att;
145
+ disp[j].x += ux * att;
146
+ disp[j].y += uy * att;
147
+ }
148
+ // Apply with cooling; clamp.
149
+ const temp = Math.max(1, (width / 10) * (1 - iter / iterations));
150
+ nodes.forEach((node, i) => {
151
+ const p = pos.get(node.id);
152
+ const dl = Math.hypot(disp[i].x, disp[i].y) || 0.01;
153
+ let nx = p.x + (disp[i].x / dl) * Math.min(dl, temp);
154
+ let ny = p.y + (disp[i].y / dl) * Math.min(dl, temp);
155
+ if (!Number.isFinite(nx))
156
+ nx = cx;
157
+ if (!Number.isFinite(ny))
158
+ ny = cy;
159
+ p.x = Math.max(20, Math.min(width - 20, nx));
160
+ p.y = Math.max(20, Math.min(height - 20, ny));
161
+ });
162
+ }
163
+ return pos;
164
+ }
165
+ /** HTML-escape for SVG text / title / markup content (XSS guard). */
166
+ export function escapeHtml(s) {
167
+ return s
168
+ .replace(/&/g, '&amp;')
169
+ .replace(/</g, '&lt;')
170
+ .replace(/>/g, '&gt;')
171
+ .replace(/"/g, '&quot;')
172
+ .replace(/'/g, '&#39;');
173
+ }
174
+ const NODE_COLORS = {
175
+ decision: '#6366f1',
176
+ policy: '#10b981',
177
+ customer: '#f59e0b',
178
+ project: '#ec4899',
179
+ person: '#3b82f6',
180
+ system: '#64748b',
181
+ };
182
+ // Client script: pan (drag bg), zoom (wheel), click-to-highlight a node's edges +
183
+ // neighbours. Reads the inlined model via JSON.parse (never innerHTML), so user
184
+ // strings never reach an HTML sink here. No template literals / `${}` so it nests
185
+ // safely inside this module's own template strings.
186
+ const CLIENT_JS = [
187
+ "(function(){",
188
+ " var svg=document.getElementById('g');",
189
+ " var vb=svg.getAttribute('viewBox').split(' ').map(Number);",
190
+ " var view={x:vb[0],y:vb[1],w:vb[2],h:vb[3]};",
191
+ " function apply(){svg.setAttribute('viewBox',view.x+' '+view.y+' '+view.w+' '+view.h);}",
192
+ " var panning=false,sx=0,sy=0;",
193
+ " svg.addEventListener('mousedown',function(e){if(e.target.closest('.node'))return;panning=true;sx=e.clientX;sy=e.clientY;});",
194
+ " window.addEventListener('mouseup',function(){panning=false;});",
195
+ " window.addEventListener('mousemove',function(e){if(!panning)return;var r=svg.getBoundingClientRect();var k=view.w/r.width;view.x-=(e.clientX-sx)*k;view.y-=(e.clientY-sy)*k;sx=e.clientX;sy=e.clientY;apply();});",
196
+ " svg.addEventListener('wheel',function(e){e.preventDefault();var r=svg.getBoundingClientRect();var mx=view.x+(e.clientX-r.left)/r.width*view.w;var my=view.y+(e.clientY-r.top)/r.height*view.h;var f=e.deltaY<0?0.9:1.1;view.x=mx-(mx-view.x)*f;view.y=my-(my-view.y)*f;view.w*=f;view.h*=f;apply();},{passive:false});",
197
+ " var data={};try{data=JSON.parse(document.getElementById('graph-data').textContent);}catch(_){}",
198
+ " var adj={};(data.edges||[]).forEach(function(e){(adj[e.from]=adj[e.from]||[]).push(e.to);(adj[e.to]=adj[e.to]||[]).push(e.from);});",
199
+ " var active=null;",
200
+ " document.querySelectorAll('.node').forEach(function(g){g.addEventListener('click',function(ev){ev.stopPropagation();var id=g.getAttribute('data-id');if(active===id){clearHi();active=null;return;}active=id;highlight(id);});});",
201
+ " svg.addEventListener('click',function(){clearHi();active=null;});",
202
+ " function clearHi(){svg.classList.remove('focused');document.querySelectorAll('.hi').forEach(function(el){el.classList.remove('hi');});}",
203
+ " function highlight(id){clearHi();svg.classList.add('focused');var keep={};keep[id]=1;(adj[id]||[]).forEach(function(n){keep[n]=1;});document.querySelectorAll('.node').forEach(function(g){if(keep[g.getAttribute('data-id')])g.classList.add('hi');});document.querySelectorAll('.edge').forEach(function(l){if(l.getAttribute('data-from')===id||l.getAttribute('data-to')===id)l.classList.add('hi');});}",
204
+ "})();",
205
+ ].join("\n");
206
+ const STYLE = [
207
+ "html,body{margin:0;height:100%;background:#0b1020;font-family:ui-sans-serif,system-ui,sans-serif}",
208
+ "#g{width:100vw;height:100vh;cursor:grab}",
209
+ "#g:active{cursor:grabbing}",
210
+ ".edge{stroke:#475569;stroke-width:1}",
211
+ ".node circle{stroke:#0b1020;stroke-width:1.5}",
212
+ ".node text{fill:#e2e8f0;font-size:11px;pointer-events:none}",
213
+ ".node{cursor:pointer}",
214
+ "#g.focused .node{opacity:.18}",
215
+ "#g.focused .edge{opacity:.07}",
216
+ "#g.focused .node.hi{opacity:1}",
217
+ "#g.focused .edge.hi{opacity:1;stroke:#e2e8f0}",
218
+ ".legend{position:fixed;top:10px;left:12px;color:#94a3b8;font-size:12px;line-height:1.6}",
219
+ ".legend b{color:#e2e8f0}",
220
+ ].join("\n");
221
+ /**
222
+ * Render the model as a SELF-CONTAINED, dependency-free, offline interactive HTML
223
+ * node-link diagram. Positions are computed server-side (deterministic). User
224
+ * strings are escaped per sink: SVG `<text>`/`<title>` via `escapeHtml`; the model
225
+ * is inlined in a `<script type="application/json">` block with `<`/`>`/`&`
226
+ * unicode-escaped so a `</script>` inside an entity name cannot break out (the
227
+ * client `JSON.parse`s it back and never `innerHTML`s a user string).
228
+ */
229
+ export function renderGraphHtml(model) {
230
+ const pos = layoutGraph(model);
231
+ const color = (t) => NODE_COLORS[t] ?? '#64748b';
232
+ const edgeSvg = model.edges
233
+ .map((e) => {
234
+ const a = pos.get(e.from);
235
+ const b = pos.get(e.to);
236
+ if (!a || !b)
237
+ return '';
238
+ return (`<line class="edge" data-from="${e.from}" data-to="${e.to}" ` +
239
+ `x1="${a.x.toFixed(1)}" y1="${a.y.toFixed(1)}" x2="${b.x.toFixed(1)}" y2="${b.y.toFixed(1)}"/>`);
240
+ })
241
+ .join('');
242
+ const nodeSvg = model.nodes
243
+ .map((node) => {
244
+ const p = pos.get(node.id);
245
+ if (!p)
246
+ return '';
247
+ const label = node.name.length > 22 ? node.name.slice(0, 21) + '…' : node.name;
248
+ return (`<g class="node" data-id="${node.id}" transform="translate(${p.x.toFixed(1)},${p.y.toFixed(1)})">` +
249
+ `<title>${escapeHtml(node.type + ': ' + node.name)}</title>` +
250
+ `<circle r="7" fill="${color(node.type)}"/>` +
251
+ `<text x="10" y="4">${escapeHtml(label)}</text>` +
252
+ `</g>`);
253
+ })
254
+ .join('');
255
+ // Safe inline JSON for the client: unicode-escape the HTML-significant chars so
256
+ // the content cannot terminate the <script> block; JSON.parse restores them.
257
+ const dataJson = JSON.stringify(model)
258
+ .replace(/</g, '\\u003c')
259
+ .replace(/>/g, '\\u003e')
260
+ .replace(/&/g, '\\u0026');
261
+ const types = [...new Set(model.nodes.map((nd) => nd.type))];
262
+ const legend = types
263
+ .map((t) => `<span style="color:${color(t)}">●</span> ${escapeHtml(t)}`)
264
+ .join(' ');
265
+ return [
266
+ '<!doctype html>',
267
+ '<html lang="en"><head><meta charset="utf-8">',
268
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
269
+ '<title>hippo graph</title>',
270
+ '<style>' + STYLE + '</style></head><body>',
271
+ `<div class="legend"><b>hippo graph</b> · ${model.nodes.length} entities · ${model.edges.length} relations` +
272
+ (model.truncated ? ' · <b style="color:#f59e0b">truncated</b>' : '') +
273
+ `<br>${legend}<br><span style="color:#64748b">drag to pan · scroll to zoom · click a node to focus</span></div>`,
274
+ `<svg id="g" viewBox="0 0 ${LAYOUT_W} ${LAYOUT_H}" preserveAspectRatio="xMidYMid meet">`,
275
+ `<g id="edges">${edgeSvg}</g><g id="nodes">${nodeSvg}</g>`,
276
+ '</svg>',
277
+ `<script type="application/json" id="graph-data">${dataJson}</script>`,
278
+ '<script>' + CLIENT_JS + '</script>',
279
+ '</body></html>',
280
+ ].join('\n');
281
+ }
282
+ /**
283
+ * Render the model as a JSON Canvas (jsoncanvas.org) document — `nodes[]` of
284
+ * type `text` positioned by the same deterministic layout, `edges[]` linking
285
+ * them by id. Opens natively in Obsidian. Pure JSON; entity names live in the
286
+ * `text` field (Obsidian renders/sanitizes them).
287
+ */
288
+ export function renderGraphCanvas(model) {
289
+ const pos = layoutGraph(model, { width: 2400, height: 1600 });
290
+ const nodes = model.nodes.map((node) => {
291
+ const p = pos.get(node.id) ?? { x: 0, y: 0 };
292
+ return {
293
+ id: `n${node.id}`,
294
+ type: 'text',
295
+ x: Math.round(p.x),
296
+ y: Math.round(p.y),
297
+ width: 240,
298
+ height: 60,
299
+ text: `**${node.type}**\n${node.name}`,
300
+ };
301
+ });
302
+ const edges = model.edges.map((e, i) => ({
303
+ id: `e${i}`,
304
+ fromNode: `n${e.from}`,
305
+ toNode: `n${e.to}`,
306
+ label: e.relType,
307
+ }));
308
+ return JSON.stringify({ nodes, edges }, null, 2);
309
+ }
310
+ //# sourceMappingURL=graph-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-view.js","sourceRoot":"","sources":["../../src/graph-view.ts"],"names":[],"mappings":"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"}