loom-agent 1.2.36 → 1.2.37

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.
@@ -1,262 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <title>Loom Graph</title>
6
- <style>
7
- :root { color-scheme: dark; }
8
- * { box-sizing: border-box; }
9
- html, body { margin: 0; height: 100%; overflow: hidden;
10
- background: #0f1117; color: #cbd2e0; font: 13px/1.45 ui-monospace, Consolas, monospace; }
11
- #app { display: flex; height: 100%; }
12
- #side { width: 260px; min-width: 260px; border-right: 1px solid #232838;
13
- background: #141722; padding: 12px; overflow-y: auto; }
14
- #side h1 { font-size: 15px; margin: 0 0 2px; color: #fff; }
15
- #side .sub { color: #7c86a0; margin-bottom: 12px; }
16
- #side h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
17
- color: #7c86a0; margin: 14px 0 6px; }
18
- #search { width: 100%; padding: 6px 8px; border: 1px solid #2b3146; border-radius: 4px;
19
- background: #0f1117; color: #e6e9f2; outline: none; }
20
- #filters label { display: flex; align-items: center; gap: 7px; padding: 3px 0;
21
- cursor: pointer; }
22
- #filters input { accent-color: #4f9cf9; }
23
- .swatch { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
24
- #main { flex: 1; position: relative; }
25
- #canvas { width: 100%; height: 100%; display: block; cursor: grab; }
26
- #canvas.dragging { cursor: grabbing; }
27
- #panel { position: absolute; left: 16px; right: 16px; bottom: 16px;
28
- background: #161a26; border: 1px solid #2b3146; border-radius: 6px;
29
- padding: 12px 14px; display: none; max-height: 38%; overflow: auto; }
30
- #panel h2 { margin: 0 0 4px; font-size: 14px; color: #fff; }
31
- #panel .meta { color: #7c86a0; margin-bottom: 6px; }
32
- #panel pre { margin: 0; white-space: pre-wrap; color: #aeb7cc; }
33
- #stats { position: absolute; top: 10px; right: 14px; color: #7c86a0; font-size: 12px; }
34
- #hint { position: absolute; top: 10px; left: 14px; color: #7c86a0; font-size: 12px; }
35
- .pill { display: inline-block; padding: 1px 7px; margin: 1px 3px 1px 0;
36
- background: #232838; border-radius: 9px; color: #9fb0d0; font-size: 11px; }
37
- .empty { color: #7c86a0; padding: 8px 2px; }
38
- </style>
39
- </head>
40
- <body>
41
- <div id="app">
42
- <div id="side">
43
- <h1>Loom Graph</h1>
44
- <div class="sub" id="statsSide"></div>
45
- <input id="search" placeholder="Search nodes..." autocomplete="off">
46
- <h3>Types</h3>
47
- <div id="filters"></div>
48
- <h3>Selected</h3>
49
- <div id="selInfo" class="empty">Click a node to inspect it.</div>
50
- </div>
51
- <div id="main">
52
- <div id="hint">drag nodes &middot; wheel to zoom &middot; drag background to pan</div>
53
- <div id="stats"></div>
54
- <canvas id="canvas"></canvas>
55
- </div>
56
- </div>
57
- <script>
58
- "use strict";
59
- /* Graph data is injected by the Loom server (loom graph open). */
60
- var GRAPH = window.__GRAPH__ || { nodes: [], edges: [] };
61
- var TYPE_COLORS = {
62
- decision: "#4f9cf9", convention: "#3ecf8e", bugfix: "#f98f4f",
63
- superseded: "#6b7080", note: "#8b93a7",
64
- };
65
- function colorOf(t) { return TYPE_COLORS[t] || TYPE_COLORS.note; }
66
-
67
- /* ---- force-directed simulation (license-free vanilla) ---- */
68
- var W, H;
69
- var nodes = [];
70
- var links = [];
71
- var byId = {};
72
- var selected = null;
73
- var dragging = null;
74
- var zoom = 1, panX = 0, panY = 0;
75
-
76
- function init() {
77
- var seen = {};
78
- GRAPH.nodes.forEach(function (n, i) {
79
- var o = { id: n.id, label: n.title || n.id, type: n.type || "note",
80
- confidence: typeof n.confidence === "number" ? n.confidence : 0.5,
81
- tags: n.tags || [], body: n.body || "", source: n.source || "",
82
- x: 0, y: 0, vx: 0, vy: 0, r: 8 + 14 * (n.confidence || 0.5), fx: null, fy: null };
83
- if (!seen[o.id]) { seen[o.id] = 1; nodes.push(o); byId[o.id] = o; }
84
- });
85
- GRAPH.edges.forEach(function (e) {
86
- if (byId[e.source] && byId[e.target] && e.source !== e.target) {
87
- links.push({ a: byId[e.source], b: byId[e.target], type: e.type || "link" });
88
- }
89
- });
90
- var n = nodes.length;
91
- var R = Math.min(W, H) * 0.35;
92
- nodes.forEach(function (o, i) {
93
- var a = (i / Math.max(n, 1)) * Math.PI * 2;
94
- o.x = W / 2 + R * Math.cos(a);
95
- o.y = H / 2 + R * Math.sin(a);
96
- });
97
- buildFilters();
98
- tick();
99
- }
100
-
101
- var tickTimer;
102
- function tick() {
103
- var rep = 2400, spring = 0.04, grav = 0.02, damp = 0.85;
104
- nodes.forEach(function (a) {
105
- a.vx *= damp; a.vy *= damp;
106
- nodes.forEach(function (b) {
107
- if (a === b) return;
108
- var dx = a.x - b.x, dy = a.y - b.y;
109
- var d2 = dx * dx + dy * dy + 1;
110
- var f = rep / d2;
111
- var d = Math.sqrt(d2);
112
- a.vx += (dx / d) * f; a.vy += (dy / d) * f;
113
- });
114
- a.vx -= (a.x - W / 2) * grav; a.vy -= (a.y - H / 2) * grav;
115
- });
116
- links.forEach(function (l) {
117
- var dx = l.b.x - l.a.x, dy = l.b.y - l.a.y;
118
- var d = Math.sqrt(dx * dx + dy * dy) || 1;
119
- var want = 90, f = (d - want) * spring;
120
- var ux = dx / d, uy = dy / d;
121
- l.a.vx += ux * f; l.a.vy += uy * f;
122
- l.b.vx -= ux * f; l.b.vy -= uy * f;
123
- });
124
- nodes.forEach(function (a) {
125
- if (a.fx !== null) { a.x = a.fx; a.y = a.fy; a.vx = a.vy = 0; return; }
126
- a.x += a.vx; a.y += a.vy;
127
- a.x = Math.max(20, Math.min(W - 20, a.x));
128
- a.y = Math.max(20, Math.min(H - 20, a.y));
129
- });
130
- draw();
131
- tickTimer = requestAnimationFrame(tick);
132
- }
133
-
134
- /* ---- rendering ---- */
135
- var canvas = document.getElementById("canvas");
136
- var ctx = canvas.getContext("2d");
137
- function resize() {
138
- W = canvas.clientWidth; H = canvas.clientHeight;
139
- canvas.width = W * devicePixelRatio; canvas.height = H * devicePixelRatio;
140
- ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
141
- }
142
- window.addEventListener("resize", resize); resize();
143
-
144
- function tx(x, y) { return { x: x * zoom + panX, y: y * zoom + panY }; }
145
-
146
- function visible(o) {
147
- var q = document.getElementById("search").value.trim().toLowerCase();
148
- if (q && (o.label.toLowerCase().indexOf(q) < 0)) return false;
149
- var f = document.getElementById("f-" + o.type);
150
- return !f || f.checked;
151
- }
152
-
153
- function draw() {
154
- ctx.clearRect(0, 0, W, H);
155
- ctx.strokeStyle = "#2b3146"; ctx.lineWidth = 1;
156
- links.forEach(function (l) {
157
- if (!visible(l.a) || !visible(l.b)) return;
158
- var p1 = tx(l.a.x, l.a.y), p2 = tx(l.b.x, l.b.y);
159
- ctx.beginPath(); ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke();
160
- });
161
- nodes.forEach(function (o) {
162
- if (!visible(o)) return;
163
- var p = tx(o.x, o.y), r = o.r * zoom;
164
- ctx.beginPath(); ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
165
- ctx.fillStyle = colorOf(o.type); ctx.fill();
166
- ctx.strokeStyle = o === selected ? "#ffffff" : "#0f1117"; ctx.lineWidth = o === selected ? 2 : 1;
167
- ctx.stroke();
168
- ctx.fillStyle = "#e6e9f2"; ctx.font = "11px ui-monospace, Consolas, monospace";
169
- ctx.textAlign = "center";
170
- var label = o.label.length > 26 ? o.label.slice(0, 25) + ".." : o.label;
171
- ctx.fillText(label, p.x, p.y + r + 12);
172
- });
173
- var vis = nodes.filter(visible).length;
174
- document.getElementById("stats").textContent = vis + " / " + nodes.length + " nodes - " + links.length + " links";
175
- document.getElementById("statsSide").textContent = nodes.length + " nodes, " + links.length + " links";
176
- }
177
-
178
- /* ---- interaction ---- */
179
- function hitTest(mx, my) {
180
- for (var i = nodes.length - 1; i >= 0; i--) {
181
- var o = nodes[i];
182
- if (!visible(o)) continue;
183
- var p = tx(o.x, o.y);
184
- var dx = mx - p.x, dy = my - p.y;
185
- if (dx * dx + dy * dy <= (o.r * zoom + 4) * (o.r * zoom + 4)) return o;
186
- }
187
- return null;
188
- }
189
-
190
- canvas.addEventListener("mousedown", function (e) {
191
- var r = canvas.getBoundingClientRect();
192
- var mx = e.clientX - r.left, my = e.clientY - r.top;
193
- var o = hitTest(mx, my);
194
- if (o) {
195
- dragging = o;
196
- canvas.classList.add("dragging");
197
- o.fx = o.x; o.fy = o.y;
198
- select(o);
199
- } else {
200
- dragging = null;
201
- lastPan = { x: mx, y: my };
202
- }
203
- });
204
- var lastPan = null;
205
- window.addEventListener("mousemove", function (e) {
206
- var r = canvas.getBoundingClientRect();
207
- var mx = e.clientX - r.left, my = e.clientY - r.top;
208
- if (dragging) {
209
- dragging.fx = (mx - panX) / zoom; dragging.fy = (my - panY) / zoom;
210
- } else if (lastPan) {
211
- panX += mx - lastPan.x; panY += my - lastPan.y;
212
- lastPan = { x: mx, y: my };
213
- }
214
- });
215
- window.addEventListener("mouseup", function () {
216
- if (dragging) { dragging.fx = null; dragging.fy = null; dragging = null; }
217
- lastPan = null;
218
- canvas.classList.remove("dragging");
219
- });
220
- canvas.addEventListener("wheel", function (e) {
221
- e.preventDefault();
222
- var f = e.deltaY < 0 ? 1.1 : 0.9;
223
- zoom = Math.max(0.2, Math.min(3, zoom * f));
224
- }, { passive: false });
225
-
226
- /* ---- side panel + filters ---- */
227
- function select(o) {
228
- selected = o;
229
- var el = document.getElementById("selInfo");
230
- var html = '<div style="color:#fff;font-size:13px"><b>' + esc(o.label) + "</b></div>";
231
- html += '<div class="meta">' + esc(o.type) +
232
- (typeof o.confidence === "number" ? " &middot; confidence " + Math.round(o.confidence * 100) + "%" : "") + "</div>";
233
- if (o.tags && o.tags.length) html += o.tags.map(function (t) { return '<span class="pill">#' + esc(t) + "</span>"; }).join("");
234
- if (o.source) html += '<div class="meta" style="margin-top:6px">source: ' + esc(o.source) + "</div>";
235
- if (o.body) {
236
- var b = o.body.length > 600 ? o.body.slice(0, 600) + " …" : o.body;
237
- html += "<pre>" + esc(b) + "</pre>";
238
- }
239
- el.innerHTML = html;
240
- }
241
- function esc(s) {
242
- return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
243
- }
244
- function buildFilters() {
245
- var types = {};
246
- nodes.forEach(function (o) { types[o.type] = (types[o.type] || 0) + 1; });
247
- var html = "";
248
- Object.keys(types).sort().forEach(function (t) {
249
- html += '<label><input type="checkbox" id="f-' + esc(t) + '" checked> ' +
250
- '<span class="swatch" style="background:' + colorOf(t) + '"></span> ' +
251
- esc(t) + " (" + types[t] + ")</label>";
252
- });
253
- document.getElementById("filters").innerHTML = html;
254
- var boxes = document.querySelectorAll("#filters input");
255
- for (var i = 0; i < boxes.length; i++) boxes[i].addEventListener("change", draw);
256
- }
257
- document.getElementById("search").addEventListener("input", draw);
258
-
259
- init();
260
- </script>
261
- </body>
262
- </html>