backpack-viewer 0.3.0 → 0.4.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.
package/README.md CHANGED
@@ -18,24 +18,45 @@ npx backpack-viewer
18
18
 
19
19
  Opens http://localhost:5173. Click any learning graph in the sidebar to visualize it.
20
20
 
21
+ **Using Claude Code?** The [backpack-ontology-plugin](https://github.com/NoahIrzinger/backpack-ontology-plugin) bundles the MCP server with usage skills (`backpack-guide`, `backpack-mine`) and is the recommended install path. The viewer works against whatever backpack data the plugin or standalone MCP writes, no extra wiring needed.
22
+
21
23
  ## Features
22
24
 
23
- - **Live reload**: add knowledge via Claude and watch it appear in real time
24
- - **Pan and zoom**: click-drag to pan, scroll to zoom
25
- - **Inspect**: click any item to see its properties, connections, and metadata
26
- - **Edit**: rename graphs, edit node types and properties, add or remove items inline
27
- - **Search**: filter by name in the sidebar, filter by type with chips
25
+ ### Exploration
26
+ - **Live reload** — add knowledge via Claude and watch it appear in real time
27
+ - **Pan and zoom** click-drag to pan, scroll to zoom
28
+ - **Focus mode** select nodes and isolate their N-hop subgraph
29
+ - **Walk mode** traverse the graph node-by-node with a highlighted trail
30
+ - **Path finding** — select two nodes, shortest path highlighted
31
+ - **Search** — filter by name in the sidebar, filter by type with chips
32
+ - **Node history** — back/forward navigation through inspected nodes
33
+ - **Graph snippets** — save walk trails as named, reusable subgraphs
34
+
35
+ ### Editing
36
+ - **Inspect** — click any item to see properties, connections, and metadata
37
+ - **Inline edit** — rename graphs, change node types and properties, add/remove items
38
+ - **Star nodes** — mark important nodes with a gold star indicator
39
+
40
+ ### Collaboration awareness (0.3.0+)
41
+ - **Lock heartbeat badge** — each graph in the sidebar shows `editing: <author>` when another writer is actively editing (within the last 5 minutes). Backed by a batched `/api/locks` endpoint.
42
+ - **Remote graphs section** — subscribe to learning graphs hosted at HTTPS URLs and view them read-only alongside your local graphs.
43
+
44
+ ### Versioning
45
+ - **Branches and snapshots** — the underlying storage is event-sourced; the viewer exposes branch switching and snapshot/rollback UI via the MCP tools.
28
46
 
29
47
  ## How it works
30
48
 
31
- The viewer reads learning graph data from the same local files that the MCP server writes to. Changes appear automatically, no refresh needed.
49
+ The viewer reads learning graph data from the same local event log that the MCP server writes to. Changes appear automatically, no refresh needed.
32
50
 
33
51
  ```
34
- backpack-ontology (MCP) ──writes──> ~/.local/share/backpack/ontologies/
35
-
52
+ backpack-ontology (MCP) ──writes──> ~/.local/share/backpack/graphs/<name>/
53
+ branches/<branch>/events.jsonl
54
+ │ branches/<branch>/snapshot.json
36
55
  backpack-viewer ──reads──────────────────┘
37
56
  ```
38
57
 
58
+ Old-format graphs (pre-0.3.0) are migrated automatically on MCP startup — the viewer reads the new format on first launch after upgrade, no manual step.
59
+
39
60
  ## Configuration
40
61
 
41
62
  The viewer reads an optional config file for customizing keybindings and other settings. The config file follows the [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/latest/) convention:
package/bin/serve.js CHANGED
@@ -14,11 +14,30 @@ const hasDistBuild = fs.existsSync(path.join(distDir, "index.html"));
14
14
 
15
15
  if (hasDistBuild) {
16
16
  // --- Production: static file server + API (zero native deps) ---
17
- const { JsonFileBackend, dataDir, RemoteRegistry } = await import("backpack-ontology");
17
+ const {
18
+ JsonFileBackend,
19
+ dataDir,
20
+ RemoteRegistry,
21
+ listBackpacks,
22
+ getActiveBackpack,
23
+ setActiveBackpack,
24
+ registerBackpack,
25
+ unregisterBackpack,
26
+ } = await import("backpack-ontology");
18
27
  const { loadViewerConfig } = await import("../dist/config.js");
19
28
 
20
- const storage = new JsonFileBackend();
21
- await storage.initialize();
29
+ // Storage points at the active backpack. Wrapped in a mutable
30
+ // holder so a `/api/backpacks/switch` POST can swap it out in place
31
+ // without restarting the whole server.
32
+ async function makeBackend() {
33
+ const entry = await getActiveBackpack();
34
+ const backend = new JsonFileBackend(undefined, {
35
+ graphsDirOverride: entry.path,
36
+ });
37
+ await backend.initialize();
38
+ return { backend, entry };
39
+ }
40
+ let { backend: storage, entry: activeEntry } = await makeBackend();
22
41
  const remoteRegistry = new RemoteRegistry();
23
42
  await remoteRegistry.initialize();
24
43
  const viewerConfig = loadViewerConfig();
@@ -327,6 +346,100 @@ if (hasDistBuild) {
327
346
  return;
328
347
  }
329
348
 
349
+ // --- Backpacks (meta: list, active, switch) ---
350
+ if (url === "/api/backpacks" && req.method === "GET") {
351
+ try {
352
+ const list = await listBackpacks();
353
+ const active = await getActiveBackpack();
354
+ res.writeHead(200, { "Content-Type": "application/json" });
355
+ res.end(
356
+ JSON.stringify(
357
+ list.map((b) => ({ ...b, active: b.name === active.name })),
358
+ ),
359
+ );
360
+ } catch (err) {
361
+ res.writeHead(500, { "Content-Type": "application/json" });
362
+ res.end(JSON.stringify({ error: err.message }));
363
+ }
364
+ return;
365
+ }
366
+
367
+ if (url === "/api/backpacks/active" && req.method === "GET") {
368
+ try {
369
+ const active = await getActiveBackpack();
370
+ res.writeHead(200, { "Content-Type": "application/json" });
371
+ res.end(JSON.stringify(active));
372
+ } catch (err) {
373
+ res.writeHead(500, { "Content-Type": "application/json" });
374
+ res.end(JSON.stringify({ error: err.message }));
375
+ }
376
+ return;
377
+ }
378
+
379
+ if (url === "/api/backpacks/switch" && req.method === "POST") {
380
+ let body = "";
381
+ req.on("data", (chunk) => { body += chunk.toString(); });
382
+ req.on("end", async () => {
383
+ try {
384
+ const { name } = JSON.parse(body);
385
+ await setActiveBackpack(name);
386
+ const swapped = await makeBackend();
387
+ storage = swapped.backend;
388
+ activeEntry = swapped.entry;
389
+ res.writeHead(200, { "Content-Type": "application/json" });
390
+ res.end(JSON.stringify({ ok: true, active: activeEntry }));
391
+ } catch (err) {
392
+ res.writeHead(400, { "Content-Type": "application/json" });
393
+ res.end(JSON.stringify({ error: err.message }));
394
+ }
395
+ });
396
+ return;
397
+ }
398
+
399
+ if (url === "/api/backpacks" && req.method === "POST") {
400
+ let body = "";
401
+ req.on("data", (chunk) => { body += chunk.toString(); });
402
+ req.on("end", async () => {
403
+ try {
404
+ const { name, path: p, activate } = JSON.parse(body);
405
+ const entry = await registerBackpack(name, p);
406
+ if (activate) {
407
+ await setActiveBackpack(name);
408
+ const swapped = await makeBackend();
409
+ storage = swapped.backend;
410
+ activeEntry = swapped.entry;
411
+ }
412
+ res.writeHead(200, { "Content-Type": "application/json" });
413
+ res.end(JSON.stringify({ ok: true, entry }));
414
+ } catch (err) {
415
+ res.writeHead(400, { "Content-Type": "application/json" });
416
+ res.end(JSON.stringify({ error: err.message }));
417
+ }
418
+ });
419
+ return;
420
+ }
421
+
422
+ const backpackDeleteMatch = url.match(/^\/api\/backpacks\/(.+)$/);
423
+ if (backpackDeleteMatch && req.method === "DELETE") {
424
+ const name = decodeURIComponent(backpackDeleteMatch[1]);
425
+ try {
426
+ await unregisterBackpack(name);
427
+ // If we just removed the active one, the registry switched us;
428
+ // rebuild the backend to match.
429
+ if (activeEntry && activeEntry.name === name) {
430
+ const swapped = await makeBackend();
431
+ storage = swapped.backend;
432
+ activeEntry = swapped.entry;
433
+ }
434
+ res.writeHead(200, { "Content-Type": "application/json" });
435
+ res.end(JSON.stringify({ ok: true }));
436
+ } catch (err) {
437
+ res.writeHead(400, { "Content-Type": "application/json" });
438
+ res.end(JSON.stringify({ error: err.message }));
439
+ }
440
+ return;
441
+ }
442
+
330
443
  // --- Lock heartbeat ---
331
444
  if (url === "/api/locks" && req.method === "GET") {
332
445
  // Batch endpoint: returns { graphName: lockInfo|null } for all graphs.
@@ -0,0 +1,34 @@
1
+ var En=Object.defineProperty;var wn=(t,e,s)=>e in t?En(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s;var ht=(t,e,s)=>wn(t,typeof e!="symbol"?e+"":e,s);(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const y of l)if(y.type==="childList")for(const k of y.addedNodes)k.tagName==="LINK"&&k.rel==="modulepreload"&&r(k)}).observe(document,{childList:!0,subtree:!0});function s(l){const y={};return l.integrity&&(y.integrity=l.integrity),l.referrerPolicy&&(y.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?y.credentials="include":l.crossOrigin==="anonymous"?y.credentials="omit":y.credentials="same-origin",y}function r(l){if(l.ep)return;l.ep=!0;const y=s(l);fetch(l.href,y)}})();async function ct(){const t=await fetch("/api/ontologies");return t.ok?t.json():[]}async function ft(t){const e=await fetch(`/api/ontologies/${encodeURIComponent(t)}`);if(!e.ok)throw new Error(`Failed to load ontology: ${t}`);return e.json()}async function kn(){const t=await fetch("/api/remotes");return t.ok?t.json():[]}async function Sn(t){const e=await fetch(`/api/remotes/${encodeURIComponent(t)}`);if(!e.ok)throw new Error(`Failed to load remote graph: ${t}`);return e.json()}async function wt(t,e){if(!(await fetch(`/api/ontologies/${encodeURIComponent(t)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).ok)throw new Error(`Failed to save ontology: ${t}`)}async function Nn(t,e){if(!(await fetch(`/api/ontologies/${encodeURIComponent(t)}/rename`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e})})).ok)throw new Error(`Failed to rename ontology: ${t}`)}async function Ln(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches`);return e.ok?e.json():[]}async function Yt(t,e,s){const r=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,from:s})});if(!r.ok){const l=await r.json().catch(()=>({}));throw new Error(l.error||"Failed to create branch")}}async function Xt(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches/switch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e})});if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||"Failed to switch branch")}}async function In(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches/${encodeURIComponent(e)}`,{method:"DELETE"});if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||"Failed to delete branch")}}async function Mn(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/snapshots`);return e.ok?e.json():[]}async function Tn(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/snapshots`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:e})});if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||"Failed to create snapshot")}}async function An(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/rollback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({version:e})});if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||"Failed to rollback")}}async function Bn(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets`);return e.ok?e.json():[]}async function _t(t,e,s,r,l){const y=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:e,description:l,nodeIds:s,edgeIds:r})});if(!y.ok)throw new Error("Failed to save snippet");return(await y.json()).id}async function Rn(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets/${encodeURIComponent(e)}`);if(!s.ok)throw new Error("Snippet not found");return s.json()}async function Pn(t,e){if(!(await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets/${encodeURIComponent(e)}`,{method:"DELETE"})).ok)throw new Error("Failed to delete snippet")}const Gt="bp-dialog-overlay";function un(){var e;(e=document.querySelector(`.${Gt}`))==null||e.remove();const t=document.createElement("div");return t.className=Gt,document.body.appendChild(t),t}function mn(t,e){const s=document.createElement("div");s.className="bp-dialog";const r=document.createElement("h4");return r.className="bp-dialog-title",r.textContent=e,s.appendChild(r),t.appendChild(s),t.addEventListener("click",l=>{l.target===t&&t.remove()}),s}function hn(t,e){const s=document.createElement("div");s.className="bp-dialog-buttons";for(const r of e){const l=document.createElement("button");l.className="bp-dialog-btn",r.accent&&l.classList.add("bp-dialog-btn-accent"),r.danger&&l.classList.add("bp-dialog-btn-danger"),l.textContent=r.label,l.addEventListener("click",r.onClick),s.appendChild(l)}t.appendChild(s)}function Tt(t,e){return new Promise(s=>{var k;const r=un(),l=mn(r,t),y=document.createElement("p");y.className="bp-dialog-message",y.textContent=e,l.appendChild(y),hn(l,[{label:"Cancel",onClick:()=>{r.remove(),s(!1)}},{label:"Confirm",accent:!0,onClick:()=>{r.remove(),s(!0)}}]),(k=l.querySelector(".bp-dialog-btn-accent"))==null||k.focus()})}function tt(t,e,s){return new Promise(r=>{const l=un(),y=mn(l,t),k=document.createElement("input");k.type="text",k.className="bp-dialog-input",k.placeholder=e??"",k.value=s??"",y.appendChild(k);const c=()=>{const o=k.value.trim();l.remove(),r(o||null)};k.addEventListener("keydown",o=>{o.key==="Enter"&&c(),o.key==="Escape"&&(l.remove(),r(null))}),hn(y,[{label:"Cancel",onClick:()=>{l.remove(),r(null)}},{label:"OK",accent:!0,onClick:c}]),k.focus(),k.select()})}function Vt(t){return t>=1e3?`${(t/1e3).toFixed(1)}k tokens`:`${t} tokens`}function Jt(t,e){return t*50+e*25+50}function Fn(t,e){const s=typeof e=="function"?{onSelect:e}:e,r=document.createElement("h2");r.textContent="Backpack Viewer";const l=document.createElement("input");l.type="text",l.placeholder="Filter...",l.id="filter";const y=document.createElement("ul");y.id="ontology-list";const k=document.createElement("h3");k.className="sidebar-section-heading",k.textContent="REMOTE GRAPHS",k.hidden=!0;const c=document.createElement("ul");c.id="remote-list",c.className="remote-list",c.hidden=!0;const o=document.createElement("div");o.className="sidebar-footer",o.innerHTML='<a href="mailto:support@backpackontology.com">support@backpackontology.com</a><span>Feedback & support</span><span class="sidebar-version">v0.4.0</span>';const P=document.createElement("button");P.className="sidebar-collapse-btn",P.title="Toggle sidebar (Tab)",P.innerHTML='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>';let i=!1;function a(){i=!i,t.classList.toggle("sidebar-collapsed",i),J.classList.toggle("hidden",!i)}P.addEventListener("click",a);const T=document.createElement("div");T.className="sidebar-heading-row",T.appendChild(r),T.appendChild(P),t.appendChild(T);const F=document.createElement("button");F.className="backpack-picker-pill",F.type="button",F.setAttribute("aria-haspopup","listbox"),F.setAttribute("aria-expanded","false");const b=document.createElement("span");b.className="backpack-picker-dot";const U=document.createElement("span");U.className="backpack-picker-name",U.textContent="...";const X=document.createElement("span");X.className="backpack-picker-caret",X.textContent="▾",F.appendChild(b),F.appendChild(U),F.appendChild(X);const te=document.createElement("div");te.className="backpack-picker-dropdown",te.hidden=!0,te.setAttribute("role","listbox");const z=document.createElement("div");z.className="backpack-picker-container",z.appendChild(F),z.appendChild(te),t.appendChild(z);let ce=!1;function V(){ce=!1,te.hidden=!0,F.setAttribute("aria-expanded","false")}function B(){ce=!0,te.hidden=!1,F.setAttribute("aria-expanded","true")}F.addEventListener("click",_=>{_.stopPropagation(),ce?V():B()}),document.addEventListener("click",_=>{z.contains(_.target)||V()});let Q=[],K=null;function ae(){te.replaceChildren();for(const D of Q){const E=document.createElement("button");E.className="backpack-picker-item",E.type="button",E.setAttribute("role","option"),D.active&&E.classList.add("active");const d=document.createElement("span");d.className="backpack-picker-item-dot",d.style.setProperty("--backpack-color",D.color);const m=document.createElement("span");m.className="backpack-picker-item-name",m.textContent=D.name;const g=document.createElement("span");g.className="backpack-picker-item-path",g.textContent=D.path,E.appendChild(d),E.appendChild(m),E.appendChild(g),E.addEventListener("click",C=>{C.stopPropagation(),V(),!D.active&&s.onBackpackSwitch&&s.onBackpackSwitch(D.name)}),te.appendChild(E)}const _=document.createElement("div");_.className="backpack-picker-divider",te.appendChild(_);const W=document.createElement("button");W.className="backpack-picker-item backpack-picker-add",W.type="button",W.textContent="+ Add new backpack…",W.addEventListener("click",async D=>{if(D.stopPropagation(),V(),!s.onBackpackRegister)return;const E=await tt("Backpack name","Short kebab-case name (e.g. work, family, project-alpha)","");if(!E)return;const d=await tt("Backpack path","Absolute or tilde-expanded path to the graphs directory","");if(!d)return;const m=await Tt("Switch to new backpack?",`Make "${E}" the active backpack immediately?`);s.onBackpackRegister(E,d,m)}),te.appendChild(W)}const J=document.createElement("button");J.className="tools-pane-toggle hidden",J.title="Show sidebar (Tab)",J.innerHTML='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 7 18 12 13 17"/><polyline points="6 7 11 12 6 17"/></svg>',J.addEventListener("click",a),t.appendChild(l),t.appendChild(y),t.appendChild(k),t.appendChild(c),t.appendChild(o);let ie=[],ee=[],ye="";return l.addEventListener("input",()=>{const _=l.value.toLowerCase();for(const W of ie){const D=W.dataset.name??"";W.style.display=D.includes(_)?"":"none"}for(const W of ee){const D=W.dataset.name??"";W.style.display=D.includes(_)?"":"none"}}),{setBackpacks(_){Q=_.slice();const W=_.find(D=>D.active)??null;K=W,W&&(U.textContent=W.name,b.style.setProperty("--backpack-color",W.color),t.style.setProperty("--backpack-color",W.color)),ae()},setActiveBackpack(_){K=_,Q=Q.map(W=>({...W,active:W.name===_.name})),Q.some(W=>W.name===_.name)||Q.push({..._,active:!0}),U.textContent=_.name,b.style.setProperty("--backpack-color",_.color),t.style.setProperty("--backpack-color",_.color),ae()},getActiveBackpack(){return K},setSummaries(_){y.innerHTML="";const W=fetch("/api/locks").then(D=>D.json()).catch(()=>({}));ie=_.map(D=>{const E=document.createElement("li");E.className="ontology-item",E.dataset.name=D.name;const d=document.createElement("span");d.className="name",d.textContent=D.name;const m=document.createElement("span");m.className="stats";const g=Jt(D.nodeCount,D.edgeCount);m.textContent=`${D.nodeCount} nodes, ${D.edgeCount} edges · ~${Vt(g)}`;const C=document.createElement("span");C.className="sidebar-branch",C.dataset.graph=D.name;const S=document.createElement("span");if(S.className="sidebar-lock-badge",S.dataset.graph=D.name,W.then(u=>{if(!S.isConnected)return;const x=u[D.name];x&&typeof x=="object"&&x.author&&(S.textContent=`editing: ${x.author}`,S.title=`Last activity: ${x.lastActivity??""}`,S.classList.add("active"))}),E.appendChild(d),E.appendChild(m),E.appendChild(S),E.appendChild(C),s.onRename){const u=document.createElement("button");u.className="sidebar-edit-btn",u.textContent="✎",u.title="Rename";const x=s.onRename;u.addEventListener("click",R=>{R.stopPropagation();const I=document.createElement("input");I.type="text",I.className="sidebar-rename-input",I.value=D.name,d.textContent="",d.appendChild(I),u.style.display="none",I.focus(),I.select();const O=()=>{const w=I.value.trim();w&&w!==D.name?x(D.name,w):(d.textContent=D.name,u.style.display="")};I.addEventListener("blur",O),I.addEventListener("keydown",w=>{w.key==="Enter"&&I.blur(),w.key==="Escape"&&(I.value=D.name,I.blur())})}),E.appendChild(u)}return E.addEventListener("click",()=>s.onSelect(D.name)),y.appendChild(E),E}),ye&&this.setActive(ye)},setActive(_){ye=_;for(const W of ie)W.classList.toggle("active",W.dataset.name===_);for(const W of ee)W.classList.toggle("active",W.dataset.name===_)},setRemotes(_){c.replaceChildren(),ee=_.map(D=>{const E=document.createElement("li");E.className="ontology-item ontology-item-remote",E.dataset.name=D.name;const d=document.createElement("div");d.className="remote-name-row";const m=document.createElement("span");m.className="name",m.textContent=D.name;const g=document.createElement("span");g.className="remote-badge",g.textContent=D.pinned?"remote · pinned":"remote",g.title=`Source: ${D.source??D.url}`,d.appendChild(m),d.appendChild(g);const C=document.createElement("span");C.className="stats";const S=Jt(D.nodeCount,D.edgeCount);C.textContent=`${D.nodeCount} nodes, ${D.edgeCount} edges · ~${Vt(S)}`;const u=document.createElement("span");return u.className="remote-source",u.textContent=D.source??new URL(D.url).hostname,u.title=D.url,E.appendChild(d),E.appendChild(C),E.appendChild(u),E.addEventListener("click",()=>s.onSelect(D.name)),c.appendChild(E),E});const W=_.length>0;k.hidden=!W,c.hidden=!W,ye&&this.setActive(ye)},setActiveBranch(_,W,D){const E=y.querySelectorAll(`.sidebar-branch[data-graph="${_}"]`);for(const d of E){d.textContent=`/ ${W}`,d.title="Click to switch branch",d.style.cursor="pointer";const m=d.cloneNode(!0);d.replaceWith(m),m.addEventListener("click",g=>{g.stopPropagation(),ke(_,m,D??[])})}},setSnippets(_,W){var d;const D=ie.find(m=>m.dataset.name===_);if(!D||((d=D.querySelector(".sidebar-snippets"))==null||d.remove(),W.length===0))return;const E=document.createElement("div");E.className="sidebar-snippets";for(const m of W){const g=document.createElement("div");g.className="sidebar-snippet";const C=document.createElement("span");C.className="sidebar-snippet-label",C.textContent=`◆ ${m.label}`,C.title=`${m.nodeCount} nodes — click to load`;const S=document.createElement("button");S.className="sidebar-snippet-delete",S.textContent="×",S.title="Delete snippet",S.addEventListener("click",u=>{var x;u.stopPropagation(),(x=s.onSnippetDelete)==null||x.call(s,_,m.id)}),g.appendChild(C),g.appendChild(S),g.addEventListener("click",u=>{var x;u.stopPropagation(),(x=s.onSnippetLoad)==null||x.call(s,_,m.id)}),E.appendChild(g)}D.appendChild(E)},toggle:a,expandBtn:J};function ke(_,W,D){const E=t.querySelector(".branch-picker");E&&E.remove();const d=document.createElement("div");d.className="branch-picker";for(const g of D){const C=document.createElement("div");C.className="branch-picker-item",g.active&&C.classList.add("branch-picker-active");const S=document.createElement("span");if(S.textContent=g.name,C.appendChild(S),!g.active&&s.onBranchDelete){const u=document.createElement("button");u.className="branch-picker-delete",u.textContent="×",u.title=`Delete ${g.name}`,u.addEventListener("click",x=>{x.stopPropagation(),Tt("Delete branch",`Delete branch "${g.name}"?`).then(R=>{R&&(s.onBranchDelete(_,g.name),d.remove())})}),C.appendChild(u)}g.active||C.addEventListener("click",()=>{var u;(u=s.onBranchSwitch)==null||u.call(s,_,g.name),d.remove()}),d.appendChild(C)}if(s.onBranchCreate){const g=document.createElement("div");g.className="branch-picker-item branch-picker-create",g.textContent="+ New branch",g.addEventListener("click",()=>{tt("New branch","Branch name").then(C=>{C&&(s.onBranchCreate(_,C),d.remove())})}),d.appendChild(g)}W.after(d);const m=g=>{d.contains(g.target)||(d.remove(),document.removeEventListener("click",m))};setTimeout(()=>document.addEventListener("click",m),0)}}function At(t,e,s,r){return{x0:t,y0:e,x1:s,y1:r,cx:0,cy:0,mass:0,children:[null,null,null,null],body:null}}function Kt(t,e,s){const r=(t.x0+t.x1)/2,l=(t.y0+t.y1)/2;return(e<r?0:1)+(s<l?0:2)}function Zt(t,e){const s=(t.x0+t.x1)/2,r=(t.y0+t.y1)/2;switch(e){case 0:return[t.x0,t.y0,s,r];case 1:return[s,t.y0,t.x1,r];case 2:return[t.x0,r,s,t.y1];default:return[s,r,t.x1,t.y1]}}function Bt(t,e){if(t.mass===0&&t.body===null){t.body=e,t.cx=e.x,t.cy=e.y,t.mass=1;return}if(t.body!==null){const l=t.body;t.body=null,l.x===e.x&&l.y===e.y&&(e.x+=(Math.random()-.5)*.1,e.y+=(Math.random()-.5)*.1);const y=Kt(t,l.x,l.y);if(t.children[y]===null){const[k,c,o,P]=Zt(t,y);t.children[y]=At(k,c,o,P)}Bt(t.children[y],l)}const s=Kt(t,e.x,e.y);if(t.children[s]===null){const[l,y,k,c]=Zt(t,s);t.children[s]=At(l,y,k,c)}Bt(t.children[s],e);const r=t.mass+1;t.cx=(t.cx*t.mass+e.x)/r,t.cy=(t.cy*t.mass+e.y)/r,t.mass=r}function $n(t){if(t.length===0)return null;let e=1/0,s=1/0,r=-1/0,l=-1/0;for(const i of t)i.x<e&&(e=i.x),i.y<s&&(s=i.y),i.x>r&&(r=i.x),i.y>l&&(l=i.y);const y=Math.max(r-e,l-s)*.1+50,k=(e+r)/2,c=(s+l)/2,o=Math.max(r-e,l-s)/2+y,P=At(k-o,c-o,k+o,c+o);for(const i of t)Bt(P,i);return P}function Dn(t,e,s,r,l,y){fn(t,e,s,r,l,y)}function fn(t,e,s,r,l,y){if(t.mass===0)return;const k=t.cx-e.x,c=t.cy-e.y,o=k*k+c*c;if(t.body!==null){if(t.body!==e){let i=Math.sqrt(o);i<y&&(i=y);const a=r*l/(i*i),T=k/i*a,F=c/i*a;e.vx-=T,e.vy-=F}return}const P=t.x1-t.x0;if(P*P/o<s*s){let i=Math.sqrt(o);i<y&&(i=y);const a=r*t.mass*l/(i*i),T=k/i*a,F=c/i*a;e.vx-=T,e.vy-=F;return}for(let i=0;i<4;i++)t.children[i]!==null&&fn(t.children[i],e,s,r,l,y)}const gn={clusterStrength:.08,spacing:1.5},Qt=6e3,On=12e3,Wn=.004,yn=140,Cn=350,en=.9,tn=.01,it=30,kt=50;let Xe={...gn};function Qe(t){t.clusterStrength!==void 0&&(Xe.clusterStrength=t.clusterStrength),t.spacing!==void 0&&(Xe.spacing=t.spacing)}function dt(){return{...Xe}}function Hn(t){if(t<=30)return{...gn};const e=Math.log2(t/30);return{clusterStrength:Math.min(.5,.08+.06*e),spacing:Math.min(15,1.5+1.2*e)}}function zn(t,e){for(const s of Object.values(t))if(typeof s=="string")return s;return e}function nn(t,e,s){const r=new Set(e);let l=new Set(e);for(let y=0;y<s;y++){const k=new Set;for(const c of t.edges)l.has(c.sourceId)&&!r.has(c.targetId)&&k.add(c.targetId),l.has(c.targetId)&&!r.has(c.sourceId)&&k.add(c.sourceId);for(const c of k)r.add(c);if(l=k,k.size===0)break}return{nodes:t.nodes.filter(y=>r.has(y.id)),edges:t.edges.filter(y=>r.has(y.sourceId)&&r.has(y.targetId)),metadata:t.metadata}}function St(t){const e=new Map,s=[...new Set(t.nodes.map(o=>o.type))],r=Math.sqrt(s.length)*Cn*.6*Math.max(1,Xe.spacing),l=new Map,y=new Map;for(const o of t.nodes)y.set(o.type,(y.get(o.type)??0)+1);const k=t.nodes.map(o=>{const P=s.indexOf(o.type),i=2*Math.PI*P/Math.max(s.length,1),a=Math.cos(i)*r,T=Math.sin(i)*r,F=l.get(o.type)??0;l.set(o.type,F+1);const b=y.get(o.type)??1,U=2*Math.PI*F/b,X=yn*.6,te={id:o.id,x:a+Math.cos(U)*X,y:T+Math.sin(U)*X,vx:0,vy:0,label:zn(o.properties,o.id),type:o.type};return e.set(o.id,te),te}),c=t.edges.map(o=>({sourceId:o.sourceId,targetId:o.targetId,type:o.type}));return{nodes:k,edges:c,nodeMap:e}}const jn=.7,Un=80;function qn(t,e){const{nodes:s,edges:r,nodeMap:l}=t,y=On*Xe.spacing;if(s.length>=Un){const c=$n(s);if(c)for(const P of s)Dn(c,P,jn,y,e,it);const o=y-Qt;if(o>0){const P=new Map;for(const i of s){let a=P.get(i.type);a||(a=[],P.set(i.type,a)),a.push(i)}for(const i of P.values())for(let a=0;a<i.length;a++)for(let T=a+1;T<i.length;T++){const F=i[a],b=i[T];let U=b.x-F.x,X=b.y-F.y,te=Math.sqrt(U*U+X*X);te<it&&(te=it);const z=o*e/(te*te),ce=U/te*z,V=X/te*z;F.vx+=ce,F.vy+=V,b.vx-=ce,b.vy-=V}}}else for(let c=0;c<s.length;c++)for(let o=c+1;o<s.length;o++){const P=s[c],i=s[o];let a=i.x-P.x,T=i.y-P.y,F=Math.sqrt(a*a+T*T);F<it&&(F=it);const U=(P.type===i.type?Qt:y)*e/(F*F),X=a/F*U,te=T/F*U;P.vx-=X,P.vy-=te,i.vx+=X,i.vy+=te}for(const c of r){const o=l.get(c.sourceId),P=l.get(c.targetId);if(!o||!P)continue;const i=P.x-o.x,a=P.y-o.y,T=Math.sqrt(i*i+a*a);if(T===0)continue;const F=o.type===P.type?yn*Xe.spacing:Cn*Xe.spacing,b=Wn*(T-F)*e,U=i/T*b,X=a/T*b;o.vx+=U,o.vy+=X,P.vx-=U,P.vy-=X}for(const c of s)c.vx-=c.x*tn*e,c.vy-=c.y*tn*e;const k=new Map;for(const c of s){const o=k.get(c.type)??{x:0,y:0,count:0};o.x+=c.x,o.y+=c.y,o.count++,k.set(c.type,o)}for(const c of k.values())c.x/=c.count,c.y/=c.count;for(const c of s){const o=k.get(c.type);c.vx+=(o.x-c.x)*Xe.clusterStrength*e,c.vy+=(o.y-c.y)*Xe.clusterStrength*e}for(const c of s){c.vx*=en,c.vy*=en;const o=Math.sqrt(c.vx*c.vx+c.vy*c.vy);o>kt&&(c.vx=c.vx/o*kt,c.vy=c.vy/o*kt),c.x+=c.vx,c.y+=c.vy}return e*.995}const on=["#d4a27f","#c17856","#b07a5e","#d4956b","#a67c5a","#cc9e7c","#c4866a","#cb8e6c","#b8956e","#a88a70","#d9b08c","#c4a882","#e8b898","#b5927a","#a8886e","#d1a990"],sn=new Map;function Se(t){const e=sn.get(t);if(e)return e;let s=0;for(let l=0;l<t.length;l++)s=(s<<5)-s+t.charCodeAt(l)|0;const r=on[Math.abs(s)%on.length];return sn.set(t,r),r}class Yn{constructor(e){ht(this,"cells",new Map);ht(this,"cellSize");ht(this,"invCell");this.cellSize=e,this.invCell=1/e}key(e,s){const r=e+32768|0,l=s+32768|0;return r*73856093^l*19349663}clear(){this.cells.clear()}insert(e){const s=Math.floor(e.x*this.invCell),r=Math.floor(e.y*this.invCell),l=this.key(s,r),y=this.cells.get(l);y?y.push(e):this.cells.set(l,[e])}rebuild(e){this.cells.clear();for(const s of e)this.insert(s)}query(e,s,r){const l=r*r,y=Math.floor((e-r)*this.invCell),k=Math.floor((e+r)*this.invCell),c=Math.floor((s-r)*this.invCell),o=Math.floor((s+r)*this.invCell);let P=null,i=l;for(let a=y;a<=k;a++)for(let T=c;T<=o;T++){const F=this.cells.get(this.key(a,T));if(F)for(const b of F){const U=b.x-e,X=b.y-s,te=U*U+X*X;te<=i&&(i=te,P=b)}}return P}}const et=new Map,Xn=2e3;function _n(t,e,s){return`${t}|${e}|${s}`}const Gn=new OffscreenCanvas(1,1),an=Gn.getContext("2d");function Vn(t,e,s){an.font=e;const r=an.measureText(t),l=Math.ceil(r.width)+2,y=Math.ceil(r.actualBoundingBoxAscent+r.actualBoundingBoxDescent)+4,k=new OffscreenCanvas(l,y),c=k.getContext("2d");return c.font=e,c.fillStyle=s,c.textAlign="left",c.textBaseline="top",c.fillText(t,1,1),{canvas:k,width:l,height:y}}function cn(t,e,s,r,l,y,k){const c=_n(e,l,y);let o=et.get(c);if(!o){if(et.size>=Xn){const a=et.keys().next().value;a!==void 0&&et.delete(a)}o=Vn(e,l,y),et.set(c,o)}const P=s-o.width/2,i=k==="top"?r:r-o.height;t.drawImage(o.canvas,P,i)}function Jn(){et.clear()}function Ce(t){return getComputedStyle(document.documentElement).getPropertyValue(t).trim()}const Fe=20,Nt=.001,Kn={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15,dotNodes:.1,hullsOnly:.05},Zn={zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300};function gt(t,e,s,r,l,y=100){const k=(t-s.x)*s.scale,c=(e-s.y)*s.scale;return k>=-y&&k<=r+y&&c>=-y&&c<=l+y}function Qn(t,e,s,r){const l={...Kn,...(r==null?void 0:r.lod)??{}},y={...Zn,...(r==null?void 0:r.navigation)??{}},k={pulseSpeed:.02,...(r==null?void 0:r.walk)??{}},c=t.querySelector("canvas"),o=c.getContext("2d"),P=window.devicePixelRatio||1;let i={x:0,y:0,scale:1},a=null,T=1,F=0,b=new Set,U=null,X=!0,te=!0,z=!0,ce=!0,V=null,B=null,Q=1,K=null,ae=null,J=null,ie=!1,ee=[],ye=0,ke=0;const _=400,W=new Yn(Fe*2);let D=0;function E(){I(),D||(D=requestAnimationFrame(()=>{D=0,p()}))}const d=150;let m=null,g=!1;function C(){if(!m)try{m=new Worker(new URL("/assets/layout-worker-BZXiBoiC.js",import.meta.url),{type:"module"}),m.onmessage=S,m.onerror=()=>{g=!1,m=null,xe()}}catch{return g=!1,null}return m}function S(h){const v=h.data;if(v.type==="tick"&&a){const A=v.positions,$=a.nodes;for(let H=0;H<$.length;H++)$[H].x=A[H*4],$[H].y=A[H*4+1],$[H].vx=A[H*4+2],$[H].vy=A[H*4+3];T=v.alpha,W.rebuild($),p()}v.type==="settled"&&(T=0,ie&&ee.length>0&&!le&&(le=requestAnimationFrame(ge)))}let u=null,x=null,R=!0;function I(){R=!0}let O=null,w=null;const f=y.panAnimationMs;function N(){c.width=c.clientWidth*P,c.height=c.clientHeight*P,I(),E()}const j=new ResizeObserver(N);j.observe(t),N();function oe(h,v){return[h/i.scale+i.x,v/i.scale+i.y]}function pe(h,v){if(!a)return null;const[A,$]=oe(h,v);return W.query(A,$,Fe)}function n(){if(!a)return;ye+=k.pulseSpeed;const h=new Set(ee);o.save(),o.setTransform(P,0,0,P,0,0),u&&(o.clearRect(0,0,c.clientWidth,c.clientHeight),o.drawImage(u,0,0,c.clientWidth,c.clientHeight)),o.save(),o.translate(-i.x*i.scale,-i.y*i.scale),o.scale(i.scale,i.scale);const v=Ce("--canvas-walk-edge")||"#1a1a1a",A=[];for(const Y of a.edges){if(!h.has(Y.sourceId)||!h.has(Y.targetId)||Y.sourceId===Y.targetId)continue;const q=a.nodeMap.get(Y.sourceId),Z=a.nodeMap.get(Y.targetId);!q||!Z||A.push(q.x,q.y,Z.x,Z.y)}if(A.length>0){o.beginPath();for(let Y=0;Y<A.length;Y+=4)o.moveTo(A[Y],A[Y+1]),o.lineTo(A[Y+2],A[Y+3]);o.strokeStyle=v,o.lineWidth=3,o.globalAlpha=.5+.5*Math.sin(ye),o.stroke(),o.globalAlpha=1}const $=i.scale<l.smallNodes?Fe*.5:Fe,H=Ce("--accent")||"#d4a27f";for(const Y of ee){const q=a.nodeMap.get(Y);if(!q||!gt(q.x,q.y,i,c.clientWidth,c.clientHeight))continue;const Z=Y===ee[ee.length-1],ue=.5+.5*Math.sin(ye);o.strokeStyle=H,o.lineWidth=Z?3:2,o.globalAlpha=Z?.5+.5*ue:.3+.4*ue,o.beginPath(),o.arc(q.x,q.y,$+(Z?6:4),0,Math.PI*2),o.stroke()}o.globalAlpha=1,o.restore(),ce&&a.nodes.length>1&&M(),o.restore()}function p(){var Wt;if(!a){o.clearRect(0,0,c.width,c.height);return}if(!R&&u&&ie&&ee.length>0&&T<Nt){n();return}const h=T<Nt&&ie&&ee.length>0;ie&&ee.length>0&&(ye+=k.pulseSpeed);const v=ie&&!h?new Set(ee):null,A=Ce("--canvas-edge"),$=Ce("--canvas-edge-highlight"),H=Ce("--canvas-edge-dim"),Y=Ce("--canvas-edge-label"),q=Ce("--canvas-edge-label-highlight"),Z=Ce("--canvas-edge-label-dim"),ue=Ce("--canvas-arrow"),he=Ce("--canvas-arrow-highlight"),fe=Ce("--canvas-node-label"),be=Ce("--canvas-node-label-dim"),Oe=Ce("--canvas-type-badge"),Me=Ce("--canvas-type-badge-dim"),Ie=Ce("--canvas-selection-border"),je=Ce("--canvas-node-border");if(o.save(),o.setTransform(P,0,0,P,0,0),o.clearRect(0,0,c.clientWidth,c.clientHeight),o.save(),o.translate(-i.x*i.scale,-i.y*i.scale),o.scale(i.scale,i.scale),z&&i.scale>=l.smallNodes){const ne=new Map;for(const Ne of a.nodes){if(U!==null&&!U.has(Ne.id))continue;const Be=ne.get(Ne.type)??[];Be.push(Ne),ne.set(Ne.type,Be)}for(const[Ne,Be]of ne){if(Be.length<2)continue;const Ve=Se(Ne),_e=Fe*2.5;let We=1/0,ze=1/0,Re=-1/0,Ye=-1/0;for(const Pe of Be)Pe.x<We&&(We=Pe.x),Pe.y<ze&&(ze=Pe.y),Pe.x>Re&&(Re=Pe.x),Pe.y>Ye&&(Ye=Pe.y);o.beginPath();const se=(Re-We)/2+_e,we=(Ye-ze)/2+_e,ve=(We+Re)/2,$e=(ze+Ye)/2;o.ellipse(ve,$e,se,we,0,0,Math.PI*2),o.fillStyle=Ve,o.globalAlpha=.05,o.fill(),o.strokeStyle=Ve,o.globalAlpha=.12,o.lineWidth=1,o.setLineDash([4,4]),o.stroke(),o.setLineDash([]),o.globalAlpha=1}}let Te=null;if(b.size>0){Te=new Set;for(const ne of a.edges)b.has(ne.sourceId)&&Te.add(ne.targetId),b.has(ne.targetId)&&Te.add(ne.sourceId)}const vt=Ce("--accent")||"#d4a27f",xt=Ce("--canvas-walk-edge")||"#1a1a1a",Ze=i.scale>=l.hideArrows,ut=te&&i.scale>=l.hideEdgeLabels;if(X){const ne=[],Ne=[],Be=[],Ve=[],_e=[],We=[];for(const se of a.edges){const we=a.nodeMap.get(se.sourceId),ve=a.nodeMap.get(se.targetId);if(!we||!ve||!gt(we.x,we.y,i,c.clientWidth,c.clientHeight,200)&&!gt(ve.x,ve.y,i,c.clientWidth,c.clientHeight,200))continue;const $e=U===null||U.has(se.sourceId),Pe=U===null||U.has(se.targetId),Ht=$e&&Pe;if(U!==null&&!$e&&!Pe)continue;const Et=b.size>0&&(b.has(se.sourceId)||b.has(se.targetId))||U!==null&&Ht,zt=U!==null&&!Ht,jt=v!==null&&v.has(se.sourceId)&&v.has(se.targetId),Ut=J?V==null?void 0:V.edges.find(mt=>mt.sourceId===se.sourceId&&mt.targetId===se.targetId||mt.targetId===se.sourceId&&mt.sourceId===se.targetId):null,qt=!!(J&&Ut&&J.edgeIds.has(Ut.id));if(se.sourceId===se.targetId){re(we,se.type,Et,A,$,Y,q);continue}(qt?_e:jt?Ve:Et?Ne:zt?Be:ne).push(we.x,we.y,ve.x,ve.y),(Ze||ut)&&We.push({sx:we.x,sy:we.y,tx:ve.x,ty:ve.y,type:se.type,highlighted:Et,edgeDimmed:zt,isPathEdge:qt,isWalkEdge:jt})}const ze=Ze?1.5:1,Ye=[{lines:ne,color:A,width:ze,alpha:1},{lines:Be,color:H,width:ze,alpha:1},{lines:Ne,color:$,width:Ze?2.5:1,alpha:1},{lines:_e,color:vt,width:3,alpha:1},{lines:Ve,color:xt,width:3,alpha:.5+.5*Math.sin(ye)}];for(const se of Ye)if(se.lines.length!==0){o.beginPath();for(let we=0;we<se.lines.length;we+=4)o.moveTo(se.lines[we],se.lines[we+1]),o.lineTo(se.lines[we+2],se.lines[we+3]);o.strokeStyle=se.color,o.lineWidth=se.width,o.globalAlpha=se.alpha,o.stroke()}o.globalAlpha=1;for(const se of We)if(Ze&&G(se.sx,se.sy,se.tx,se.ty,se.highlighted||se.isPathEdge,ue,he),ut){const we=(se.sx+se.tx)/2,ve=(se.sy+se.ty)/2;o.fillStyle=se.highlighted?q:se.edgeDimmed?Z:Y,o.font="9px system-ui, sans-serif",o.textAlign="center",o.textBaseline="bottom",o.fillText(se.type,we,ve-4)}}const bt=performance.now()-ke,Ee=Math.min(1,bt/_),Ue=1-(1-Ee)*(1-Ee),qe=Ee<1,Ot=i.scale<l.hullsOnly,bn=!Ot&&i.scale<l.dotNodes;if(!Ot)for(const ne of a.nodes){if(!gt(ne.x,ne.y,i,c.clientWidth,c.clientHeight))continue;const Ne=Se(ne.type);if(bn){const ve=U!==null&&!U.has(ne.id);o.fillStyle=Ne;const $e=ve?.1:.8;o.globalAlpha=qe?$e*Ue:$e,o.fillRect(ne.x-2,ne.y-2,4,4);continue}const Be=b.has(ne.id),Ve=Te!==null&&Te.has(ne.id),_e=U!==null&&!U.has(ne.id),We=_e||b.size>0&&!Be&&!Ve,ze=i.scale<l.smallNodes?Fe*.5:Fe,Re=qe?ze*Ue:ze;if(v!=null&&v.has(ne.id)){const ve=ee[ee.length-1]===ne.id,$e=.5+.5*Math.sin(ye),Pe=Ce("--accent")||"#d4a27f";o.save(),o.strokeStyle=Pe,o.lineWidth=ve?3:2,o.globalAlpha=ve?.5+.5*$e:.3+.4*$e,o.beginPath(),o.arc(ne.x,ne.y,Re+(ve?6:4),0,Math.PI*2),o.stroke(),o.restore()}Be&&(o.save(),o.shadowColor=Ne,o.shadowBlur=20,o.beginPath(),o.arc(ne.x,ne.y,Re+3,0,Math.PI*2),o.fillStyle=Ne,o.globalAlpha=.3,o.fill(),o.restore()),o.beginPath(),o.arc(ne.x,ne.y,Re,0,Math.PI*2),o.fillStyle=Ne;const Ye=_e?.1:We?.3:1;o.globalAlpha=qe?Ye*Ue:Ye,o.fill(),o.strokeStyle=Be?Ie:je,o.lineWidth=Be?3:1.5,o.stroke(),o.globalAlpha=1,J&&J.nodeIds.has(ne.id)&&!Be&&(o.save(),o.shadowColor=Ce("--accent")||"#d4a27f",o.shadowBlur=15,o.beginPath(),o.arc(ne.x,ne.y,Re+2,0,Math.PI*2),o.strokeStyle=Ce("--accent")||"#d4a27f",o.globalAlpha=.5,o.lineWidth=2,o.stroke(),o.restore());const se=V==null?void 0:V.nodes.find(ve=>ve.id===ne.id);if(((Wt=se==null?void 0:se.properties)==null?void 0:Wt._starred)===!0&&(o.fillStyle="#ffd700",o.font="10px system-ui, sans-serif",o.textAlign="left",o.textBaseline="bottom",o.fillText("★",ne.x+Re-2,ne.y-Re+2)),i.scale>=l.hideLabels){const ve=ne.label.length>24?ne.label.slice(0,22)+"...":ne.label,$e=We?be:fe;cn(o,ve,ne.x,ne.y+Re+4,"11px system-ui, sans-serif",$e,"top")}if(i.scale>=l.hideBadges){const ve=We?Me:Oe;cn(o,ne.type,ne.x,ne.y-Re-3,"9px system-ui, sans-serif",ve,"bottom")}o.globalAlpha=1}if(o.restore(),o.restore(),h){const ne=c.width,Ne=c.height;(!u||u.width!==ne||u.height!==Ne)&&(u=new OffscreenCanvas(ne,Ne),x=u.getContext("2d")),x&&(x.clearRect(0,0,ne,Ne),x.drawImage(c,0,0),R=!1),n();return}ce&&a.nodes.length>1&&M()}function M(){if(!a)return;const h=140,v=100,A=8,$=c.clientWidth-h-16,H=c.clientHeight-v-16;let Y=1/0,q=1/0,Z=-1/0,ue=-1/0;for(const Ee of a.nodes)Ee.x<Y&&(Y=Ee.x),Ee.y<q&&(q=Ee.y),Ee.x>Z&&(Z=Ee.x),Ee.y>ue&&(ue=Ee.y);const he=Z-Y||1,fe=ue-q||1,be=Math.min((h-A*2)/he,(v-A*2)/fe),Oe=$+A+(h-A*2-he*be)/2,Me=H+A+(v-A*2-fe*be)/2;o.save(),o.setTransform(P,0,0,P,0,0),o.fillStyle=Ce("--bg-surface")||"#1a1a1a",o.globalAlpha=.85,o.beginPath(),o.roundRect($,H,h,v,8),o.fill(),o.strokeStyle=Ce("--border")||"#2a2a2a",o.globalAlpha=1,o.lineWidth=1,o.stroke(),o.globalAlpha=.15,o.strokeStyle=Ce("--canvas-edge")||"#555",o.lineWidth=.5;for(const Ee of a.edges){const Ue=a.nodeMap.get(Ee.sourceId),qe=a.nodeMap.get(Ee.targetId);!Ue||!qe||Ee.sourceId===Ee.targetId||(o.beginPath(),o.moveTo(Oe+(Ue.x-Y)*be,Me+(Ue.y-q)*be),o.lineTo(Oe+(qe.x-Y)*be,Me+(qe.y-q)*be),o.stroke())}o.globalAlpha=.8;for(const Ee of a.nodes){const Ue=Oe+(Ee.x-Y)*be,qe=Me+(Ee.y-q)*be;o.beginPath(),o.arc(Ue,qe,2,0,Math.PI*2),o.fillStyle=Se(Ee.type),o.fill()}const Ie=i.x,je=i.y,Te=i.x+c.clientWidth/i.scale,vt=i.y+c.clientHeight/i.scale,xt=Oe+(Ie-Y)*be,Ze=Me+(je-q)*be,ut=(Te-Ie)*be,bt=(vt-je)*be;o.globalAlpha=.3,o.strokeStyle=Ce("--accent")||"#d4a27f",o.lineWidth=1.5,o.strokeRect(Math.max($,Math.min(xt,$+h)),Math.max(H,Math.min(Ze,H+v)),Math.min(ut,h),Math.min(bt,v)),o.globalAlpha=1,o.restore()}function G(h,v,A,$,H,Y,q){const Z=Math.atan2($-v,A-h),ue=A-Math.cos(Z)*Fe,he=$-Math.sin(Z)*Fe,fe=8;o.beginPath(),o.moveTo(ue,he),o.lineTo(ue-fe*Math.cos(Z-.4),he-fe*Math.sin(Z-.4)),o.lineTo(ue-fe*Math.cos(Z+.4),he-fe*Math.sin(Z+.4)),o.closePath(),o.fillStyle=H?q:Y,o.fill()}function re(h,v,A,$,H,Y,q){const Z=h.x+Fe+15,ue=h.y-Fe-15;o.beginPath(),o.arc(Z,ue,15,0,Math.PI*2),o.strokeStyle=A?H:$,o.lineWidth=A?2.5:1.5,o.stroke(),te&&(o.fillStyle=A?q:Y,o.font="9px system-ui, sans-serif",o.textAlign="center",o.fillText(v,Z,ue-18))}function me(){if(!O||!w)return;const h=performance.now()-w.time,v=Math.min(h/f,1),A=1-Math.pow(1-v,3);i.x=w.x+(O.x-w.x)*A,i.y=w.y+(O.y-w.y)*A,I(),p(),v<1?requestAnimationFrame(me):(O=null,w=null)}let le=0;function ge(){if(!ie||ee.length===0){le=0;return}p(),le=requestAnimationFrame(ge)}function Le(){if(!a||a.nodes.length===0)return;let h=1/0,v=1/0,A=-1/0,$=-1/0;for(const he of a.nodes)he.x<h&&(h=he.x),he.y<v&&(v=he.y),he.x>A&&(A=he.x),he.y>$&&($=he.y);const H=Fe*4,Y=A-h+H*2,q=$-v+H*2,Z=c.clientWidth/Math.max(Y,1),ue=c.clientHeight/Math.max(q,1);i.scale=Math.min(Z,ue,2),i.x=(h+A)/2-c.clientWidth/(2*i.scale),i.y=(v+$)/2-c.clientHeight/(2*i.scale),E()}function xe(){if(!a||T<Nt){ie&&ee.length>0&&!le&&(le=requestAnimationFrame(ge));return}T=qn(a,T),W.rebuild(a.nodes),p(),F=requestAnimationFrame(xe)}let He=!1,yt=!1,Je=0,Ke=0;c.addEventListener("mousedown",h=>{He=!0,yt=!1,Je=h.clientX,Ke=h.clientY}),c.addEventListener("mousemove",h=>{if(!He)return;const v=h.clientX-Je,A=h.clientY-Ke;(Math.abs(v)>5||Math.abs(A)>5)&&(yt=!0),i.x-=v/i.scale,i.y-=A/i.scale,Je=h.clientX,Ke=h.clientY,E()}),c.addEventListener("mouseup",h=>{if(He=!1,yt)return;const v=c.getBoundingClientRect(),A=h.clientX-v.left,$=h.clientY-v.top,H=pe(A,$),Y=h.ctrlKey||h.metaKey;if(ie&&B&&H&&a){const q=ee.length>0?ee[ee.length-1]:B[0],Z=new Set([q]),ue=[{id:q,path:[q]}];let he=null;for(;ue.length>0;){const{id:Me,path:Ie}=ue.shift();if(Me===H.id){he=Ie;break}for(const je of a.edges){let Te=null;je.sourceId===Me?Te=je.targetId:je.targetId===Me&&(Te=je.sourceId),Te&&!Z.has(Te)&&(Z.add(Te),ue.push({id:Te,path:[...Ie,Te]}))}}if(!he)return;for(const Me of he.slice(1))ee.includes(Me)||ee.push(Me);B=[H.id];const fe=Math.max(1,Q);Q=fe;const be=nn(V,[H.id],fe);cancelAnimationFrame(F),m&&m.postMessage({type:"stop"}),a=St(be),W.rebuild(a.nodes),T=1,b=new Set([H.id]),U=null,i={x:0,y:0,scale:1},g=be.nodes.length>=d;const Oe=g?C():null;Oe?Oe.postMessage({type:"start",data:be}):(g=!1,xe()),setTimeout(()=>{a&&Le()},300),s==null||s({seedNodeIds:[H.id],hops:fe,totalNodes:be.nodes.length}),e==null||e([H.id]);return}if(H){Y?b.has(H.id)?b.delete(H.id):b.add(H.id):b.size===1&&b.has(H.id)?b.clear():(b.clear(),b.add(H.id));const q=[...b];e==null||e(q.length>0?q:null)}else b.clear(),e==null||e(null);E()}),c.addEventListener("mouseleave",()=>{He=!1}),c.addEventListener("wheel",h=>{h.preventDefault();const v=c.getBoundingClientRect(),A=h.clientX-v.left,$=h.clientY-v.top,[H,Y]=oe(A,$),q=h.ctrlKey?1-h.deltaY*.01:h.deltaY>0?.9:1.1;i.scale=Math.max(y.zoomMin,Math.min(y.zoomMax,i.scale*q)),i.x=H-A/i.scale,i.y=Y-$/i.scale,E()},{passive:!1});let De=[],Rt=0,Pt=1,Ft=0,$t=0,Ct=!1;c.addEventListener("touchstart",h=>{h.preventDefault(),De=Array.from(h.touches),De.length===2?(Rt=Dt(De[0],De[1]),Pt=i.scale):De.length===1&&(Je=De[0].clientX,Ke=De[0].clientY,Ft=De[0].clientX,$t=De[0].clientY,Ct=!1)},{passive:!1}),c.addEventListener("touchmove",h=>{h.preventDefault();const v=Array.from(h.touches);if(v.length===2&&De.length===2){const $=Dt(v[0],v[1])/Rt;i.scale=Math.max(y.zoomMin,Math.min(y.zoomMax,Pt*$)),E()}else if(v.length===1){const A=v[0].clientX-Je,$=v[0].clientY-Ke;(Math.abs(v[0].clientX-Ft)>10||Math.abs(v[0].clientY-$t)>10)&&(Ct=!0),i.x-=A/i.scale,i.y-=$/i.scale,Je=v[0].clientX,Ke=v[0].clientY,E()}De=v},{passive:!1}),c.addEventListener("touchend",h=>{if(h.preventDefault(),Ct||h.changedTouches.length!==1)return;const v=h.changedTouches[0],A=c.getBoundingClientRect(),$=v.clientX-A.left,H=v.clientY-A.top,Y=pe($,H);if(Y){b.size===1&&b.has(Y.id)?b.clear():(b.clear(),b.add(Y.id));const q=[...b];e==null||e(q.length>0?q:null)}else b.clear(),e==null||e(null);E()},{passive:!1}),c.addEventListener("gesturestart",h=>h.preventDefault()),c.addEventListener("gesturechange",h=>h.preventDefault());function Dt(h,v){const A=h.clientX-v.clientX,$=h.clientY-v.clientY;return Math.sqrt(A*A+$*$)}const nt=document.createElement("div");nt.className="zoom-controls";const ot=document.createElement("button");ot.className="zoom-btn",ot.textContent="+",ot.title="Zoom in",ot.addEventListener("click",()=>{const h=c.clientWidth/2,v=c.clientHeight/2,[A,$]=oe(h,v);i.scale=Math.min(y.zoomMax,i.scale*y.zoomFactor),i.x=A-h/i.scale,i.y=$-v/i.scale,E()});const st=document.createElement("button");st.className="zoom-btn",st.textContent="−",st.title="Zoom out",st.addEventListener("click",()=>{const h=c.clientWidth/2,v=c.clientHeight/2,[A,$]=oe(h,v);i.scale=Math.max(y.zoomMin,i.scale/y.zoomFactor),i.x=A-h/i.scale,i.y=$-v/i.scale,E()});const at=document.createElement("button");at.className="zoom-btn",at.textContent="○",at.title="Reset zoom",at.addEventListener("click",()=>{if(a){if(i={x:0,y:0,scale:1},a.nodes.length>0){let h=1/0,v=1/0,A=-1/0,$=-1/0;for(const q of a.nodes)q.x<h&&(h=q.x),q.y<v&&(v=q.y),q.x>A&&(A=q.x),q.y>$&&($=q.y);const H=(h+A)/2,Y=(v+$)/2;i.x=H-c.clientWidth/2,i.y=Y-c.clientHeight/2}E()}}),nt.appendChild(ot),nt.appendChild(at),nt.appendChild(st),t.appendChild(nt);const Ae=document.createElement("div");Ae.className="node-tooltip",Ae.style.display="none",t.appendChild(Ae);let pt=null,Ge=null;return c.addEventListener("mousemove",h=>{if(He){Ae.style.display!=="none"&&(Ae.style.display="none",pt=null);return}const v=c.getBoundingClientRect(),A=h.clientX-v.left,$=h.clientY-v.top,H=pe(A,$),Y=(H==null?void 0:H.id)??null;Y!==pt?(pt=Y,Ae.style.display="none",Ge&&clearTimeout(Ge),Ge=null,Y&&H&&(Ge=setTimeout(()=>{if(!a||!V)return;const q=a.edges.filter(Z=>Z.sourceId===Y||Z.targetId===Y).length;Ae.textContent=`${H.label} · ${H.type} · ${q} edge${q!==1?"s":""}`,Ae.style.left=`${h.clientX-v.left+12}px`,Ae.style.top=`${h.clientY-v.top-8}px`,Ae.style.display="block"},200))):Y&&Ae.style.display==="block"&&(Ae.style.left=`${h.clientX-v.left+12}px`,Ae.style.top=`${h.clientY-v.top-8}px`)}),c.addEventListener("mouseleave",()=>{Ae.style.display="none",pt=null,Ge&&clearTimeout(Ge),Ge=null}),{loadGraph(h){if(cancelAnimationFrame(F),m&&m.postMessage({type:"stop"}),Jn(),V=h,B=null,K=null,ae=null,ke=performance.now(),a=St(h),W.rebuild(a.nodes),T=1,b=new Set,U=null,i={x:0,y:0,scale:1},a.nodes.length>0){let A=1/0,$=1/0,H=-1/0,Y=-1/0;for(const fe of a.nodes)fe.x<A&&(A=fe.x),fe.y<$&&($=fe.y),fe.x>H&&(H=fe.x),fe.y>Y&&(Y=fe.y);const q=(A+H)/2,Z=($+Y)/2,ue=c.clientWidth,he=c.clientHeight;i.x=q-ue/2,i.y=Z-he/2}g=h.nodes.length>=d;const v=g?C():null;v?v.postMessage({type:"start",data:h}):(g=!1,xe())},setFilteredNodeIds(h){U=h,E()},panToNode(h){this.panToNodes([h])},panToNodes(h){if(!a||h.length===0)return;const v=h.map(H=>a.nodeMap.get(H)).filter(Boolean);if(v.length===0)return;b=new Set(h),e==null||e(h);const A=c.clientWidth,$=c.clientHeight;if(v.length===1)w={x:i.x,y:i.y,time:performance.now()},O={x:v[0].x-A/(2*i.scale),y:v[0].y-$/(2*i.scale)};else{let H=1/0,Y=1/0,q=-1/0,Z=-1/0;for(const Ie of v)Ie.x<H&&(H=Ie.x),Ie.y<Y&&(Y=Ie.y),Ie.x>q&&(q=Ie.x),Ie.y>Z&&(Z=Ie.y);const ue=Fe*4,he=q-H+ue*2,fe=Z-Y+ue*2,be=Math.min(A/he,$/fe,i.scale);i.scale=be;const Oe=(H+q)/2,Me=(Y+Z)/2;w={x:i.x,y:i.y,time:performance.now()},O={x:Oe-A/(2*i.scale),y:Me-$/(2*i.scale)}}me()},setEdges(h){X=h,E()},setEdgeLabels(h){te=h,E()},setTypeHulls(h){z=h,E()},setMinimap(h){ce=h,E()},centerView(){Le()},panBy(h,v){i.x+=h/i.scale,i.y+=v/i.scale,E()},zoomBy(h){const v=c.clientWidth/2,A=c.clientHeight/2,[$,H]=oe(v,A);i.scale=Math.max(y.zoomMin,Math.min(y.zoomMax,i.scale*h)),i.x=$-v/i.scale,i.y=H-A/i.scale,E()},reheat(){g&&m?m.postMessage({type:"params",params:dt()}):(T=.5,cancelAnimationFrame(F),xe())},exportImage(h){if(!a)return"";const v=c.width,A=c.height;if(h==="png"){const q=document.createElement("canvas");q.width=v,q.height=A;const Z=q.getContext("2d");return Z.fillStyle=Ce("--bg")||"#141414",Z.fillRect(0,0,v,A),Z.drawImage(c,0,0),xn(Z,v,A),q.toDataURL("image/png")}const $=c.toDataURL("image/png"),H=Math.max(16,Math.round(v/80)),Y=`<svg xmlns="http://www.w3.org/2000/svg" width="${v}" height="${A}">
2
+ <image href="${$}" width="${v}" height="${A}"/>
3
+ <text x="${v-20}" y="${A-16}" text-anchor="end" font-family="system-ui, sans-serif" font-size="${H}" fill="#ffffff" opacity="0.4">backpackontology.com</text>
4
+ </svg>`;return"data:image/svg+xml;charset=utf-8,"+encodeURIComponent(Y)},enterFocus(h,v){if(!V||!a)return;B||(K=a,ae={...i}),B=h,Q=v;const A=nn(V,h,v);cancelAnimationFrame(F),m&&m.postMessage({type:"stop"}),a=St(A),W.rebuild(a.nodes),T=1,b=new Set(h),U=null,i={x:0,y:0,scale:1},g=A.nodes.length>=d;const $=g?C():null;$?$.postMessage({type:"start",data:A}):(g=!1,xe()),setTimeout(()=>{!a||!B||Le()},300),s==null||s({seedNodeIds:h,hops:v,totalNodes:A.nodes.length})},exitFocus(){!B||!K||(cancelAnimationFrame(F),m&&m.postMessage({type:"stop"}),a=K,W.rebuild(a.nodes),i=ae??{x:0,y:0,scale:1},B=null,K=null,ae=null,b=new Set,U=null,E(),s==null||s(null))},isFocused(){return B!==null},getFocusInfo(){return!B||!a?null:{seedNodeIds:B,hops:Q,totalNodes:a.nodes.length}},findPath(h,v){if(!a)return null;const A=new Set([h]),$=[{nodeId:h,path:[h],edges:[]}];for(;$.length>0;){const{nodeId:H,path:Y,edges:q}=$.shift();if(H===v)return{nodeIds:Y,edgeIds:q};for(const Z of a.edges){let ue=null;if(Z.sourceId===H?ue=Z.targetId:Z.targetId===H&&(ue=Z.sourceId),ue&&!A.has(ue)){A.add(ue);const he=V==null?void 0:V.edges.find(fe=>fe.sourceId===Z.sourceId&&fe.targetId===Z.targetId||fe.targetId===Z.sourceId&&fe.sourceId===Z.targetId);$.push({nodeId:ue,path:[...Y,ue],edges:[...q,(he==null?void 0:he.id)??""]})}}}return null},setHighlightedPath(h,v){h&&v?J={nodeIds:new Set(h),edgeIds:new Set(v)}:J=null,E()},clearHighlightedPath(){J=null,E()},setWalkMode(h){ie=h,h?(ee=B?[...B]:[...b],le||(le=requestAnimationFrame(ge))):(ee=[],le&&(cancelAnimationFrame(le),le=0)),E()},getWalkMode(){return ie},getWalkTrail(){return[...ee]},getFilteredNodeIds(){return U},removeFromWalkTrail(h){ee=ee.filter(v=>v!==h),E()},nodeAtScreen(h,v){return pe(h,v)},getNodeIds(){if(!a)return[];if(B){const h=new Set(B),v=a.nodes.filter($=>h.has($.id)).map($=>$.id),A=a.nodes.filter($=>!h.has($.id)).map($=>$.id);return[...v,...A]}return a.nodes.map(h=>h.id)},destroy(){cancelAnimationFrame(F),D&&(cancelAnimationFrame(D),D=0),le&&(cancelAnimationFrame(le),le=0),m&&(m.terminate(),m=null),u=null,x=null,j.disconnect()}};function xn(h,v,A){const $=Math.max(16,Math.round(v/80));h.save(),h.font=`${$}px system-ui, sans-serif`,h.fillStyle="rgba(255, 255, 255, 0.4)",h.textAlign="right",h.textBaseline="bottom",h.fillText("backpackontology.com",v-20,A-16),h.restore()}}function lt(t){for(const e of Object.values(t.properties))if(typeof e=="string")return e;return t.id}const eo="✎";function to(t,e,s,r){const l=document.createElement("div");l.id="info-panel",l.className="info-panel hidden",t.appendChild(l);let y=!1,k=[],c=-1,o=!1,P=null,i=[],a=!1,T=[],F=-1;function b(){l.classList.add("hidden"),l.classList.remove("info-panel-maximized"),l.innerHTML="",y=!1,k=[],c=-1}function U(B){!P||!s||(c<k.length-1&&(k=k.slice(0,c+1)),k.push(B),c=k.length-1,o=!0,s(B),o=!1)}function X(){if(c<=0||!P)return;c--,o=!0;const B=k[c];s==null||s(B),ce(B,P),o=!1}function te(){if(c>=k.length-1||!P)return;c++,o=!0;const B=k[c];s==null||s(B),ce(B,P),o=!1}function z(){const B=document.createElement("div");B.className="info-panel-toolbar";const Q=document.createElement("button");Q.className="info-toolbar-btn",Q.textContent="←",Q.title="Back",Q.disabled=c<=0,Q.addEventListener("click",X),B.appendChild(Q);const K=document.createElement("button");if(K.className="info-toolbar-btn",K.textContent="→",K.title="Forward",K.disabled=c>=k.length-1,K.addEventListener("click",te),B.appendChild(K),r&&i.length>0){const ie=document.createElement("button");ie.className="info-toolbar-btn info-focus-btn",ie.textContent="◎",ie.title="Focus on neighborhood (F)",ie.disabled=a,a&&(ie.style.opacity="0.3"),ie.addEventListener("click",()=>{a||r(i)}),B.appendChild(ie)}const ae=document.createElement("button");ae.className="info-toolbar-btn",ae.textContent=y?"⎘":"⛶",ae.title=y?"Restore":"Maximize",ae.addEventListener("click",()=>{y=!y,l.classList.toggle("info-panel-maximized",y),ae.textContent=y?"⎘":"⛶",ae.title=y?"Restore":"Maximize"}),B.appendChild(ae);const J=document.createElement("button");return J.className="info-toolbar-btn info-close-btn",J.textContent="×",J.title="Close",J.addEventListener("click",b),B.appendChild(J),B}function ce(B,Q){const K=Q.nodes.find(u=>u.id===B);if(!K)return;const ae=Q.edges.filter(u=>u.sourceId===B||u.targetId===B);T=ae.map(u=>u.sourceId===B?u.targetId:u.sourceId),F=-1,l.innerHTML="",l.classList.remove("hidden"),y&&l.classList.add("info-panel-maximized");const J=document.createElement("div");J.className="info-panel-header",J.appendChild(z());const ie=document.createElement("div");ie.className="info-header";const ee=document.createElement("span");if(ee.className="info-type-badge",ee.textContent=K.type,ee.style.backgroundColor=Se(K.type),e){ee.classList.add("info-editable");const u=document.createElement("button");u.className="info-inline-edit",u.textContent=eo,u.addEventListener("click",x=>{x.stopPropagation();const R=document.createElement("input");R.type="text",R.className="info-edit-inline-input",R.value=K.type,ee.textContent="",ee.appendChild(R),R.focus(),R.select();const I=()=>{const O=R.value.trim();O&&O!==K.type?e.onChangeNodeType(B,O):(ee.textContent=K.type,ee.appendChild(u))};R.addEventListener("blur",I),R.addEventListener("keydown",O=>{O.key==="Enter"&&R.blur(),O.key==="Escape"&&(R.value=K.type,R.blur())})}),ee.appendChild(u)}const ye=document.createElement("h3");ye.className="info-label",ye.textContent=lt(K);const ke=document.createElement("span");ke.className="info-id",ke.textContent=K.id,ie.appendChild(ee),ie.appendChild(ye),ie.appendChild(ke),J.appendChild(ie),l.appendChild(J);const _=document.createElement("div");_.className="info-panel-body";const W=Object.keys(K.properties),D=rt("Properties");if(W.length>0){const u=document.createElement("dl");u.className="info-props";for(const x of W){const R=document.createElement("dt");R.textContent=x;const I=document.createElement("dd");if(e){const O=Lt(K.properties[x]),w=document.createElement("textarea");w.className="info-edit-input",w.value=O,w.rows=1,w.addEventListener("input",()=>ln(w)),w.addEventListener("keydown",N=>{N.key==="Enter"&&!N.shiftKey&&(N.preventDefault(),w.blur())}),w.addEventListener("blur",()=>{const N=w.value;N!==O&&e.onUpdateNode(B,{[x]:oo(N)})}),I.appendChild(w),requestAnimationFrame(()=>ln(w));const f=document.createElement("button");f.className="info-delete-prop",f.textContent="×",f.title=`Remove ${x}`,f.addEventListener("click",()=>{const N={...K.properties};delete N[x],e.onUpdateNode(B,N)}),I.appendChild(f)}else I.appendChild(no(K.properties[x]));u.appendChild(R),u.appendChild(I)}D.appendChild(u)}if(e){const u=document.createElement("button");u.className="info-add-btn",u.textContent="+ Add property",u.addEventListener("click",()=>{const x=document.createElement("div");x.className="info-add-row";const R=document.createElement("input");R.type="text",R.className="info-edit-input",R.placeholder="key";const I=document.createElement("input");I.type="text",I.className="info-edit-input",I.placeholder="value";const O=document.createElement("button");O.className="info-add-save",O.textContent="Add",O.addEventListener("click",()=>{R.value&&e.onAddProperty(B,R.value,I.value)}),x.appendChild(R),x.appendChild(I),x.appendChild(O),D.appendChild(x),R.focus()}),D.appendChild(u)}if(_.appendChild(D),ae.length>0){const u=rt(`Connections (${ae.length})`),x=document.createElement("ul");x.className="info-connections";for(const R of ae){const I=R.sourceId===B,O=I?R.targetId:R.sourceId,w=Q.nodes.find(p=>p.id===O),f=w?lt(w):O,N=document.createElement("li");if(N.className="info-connection",s&&w&&(N.classList.add("info-connection-link"),N.addEventListener("click",p=>{p.target.closest(".info-delete-edge")||U(O)})),w){const p=document.createElement("span");p.className="info-target-dot",p.style.backgroundColor=Se(w.type),N.appendChild(p)}const j=document.createElement("span");j.className="info-arrow",j.textContent=I?"→":"←";const oe=document.createElement("span");oe.className="info-edge-type",oe.textContent=R.type;const pe=document.createElement("span");pe.className="info-target",pe.textContent=f,N.appendChild(j),N.appendChild(oe),N.appendChild(pe);const n=Object.keys(R.properties);if(n.length>0){const p=document.createElement("div");p.className="info-edge-props";for(const M of n){const G=document.createElement("span");G.className="info-edge-prop",G.textContent=`${M}: ${Lt(R.properties[M])}`,p.appendChild(G)}N.appendChild(p)}if(e){const p=document.createElement("button");p.className="info-delete-edge",p.textContent="×",p.title="Remove connection",p.addEventListener("click",M=>{M.stopPropagation(),e.onDeleteEdge(R.id)}),N.appendChild(p)}x.appendChild(N)}u.appendChild(x),_.appendChild(u)}const E=rt("Timestamps"),d=document.createElement("dl");d.className="info-props";const m=document.createElement("dt");m.textContent="created";const g=document.createElement("dd");g.textContent=rn(K.createdAt);const C=document.createElement("dt");C.textContent="updated";const S=document.createElement("dd");if(S.textContent=rn(K.updatedAt),d.appendChild(m),d.appendChild(g),d.appendChild(C),d.appendChild(S),E.appendChild(d),_.appendChild(E),e){const u=document.createElement("div");u.className="info-section info-danger";const x=document.createElement("button");x.className="info-delete-node",x.textContent="Delete node",x.addEventListener("click",()=>{e.onDeleteNode(B),b()}),u.appendChild(x),_.appendChild(u)}l.appendChild(_)}function V(B,Q){const K=new Set(B),ae=Q.nodes.filter(E=>K.has(E.id));if(ae.length===0)return;const J=Q.edges.filter(E=>K.has(E.sourceId)&&K.has(E.targetId));l.innerHTML="",l.classList.remove("hidden"),y&&l.classList.add("info-panel-maximized"),l.appendChild(z());const ie=document.createElement("div");ie.className="info-header";const ee=document.createElement("h3");ee.className="info-label",ee.textContent=`${ae.length} nodes selected`,ie.appendChild(ee);const ye=document.createElement("div");ye.className="info-badge-row";const ke=new Map;for(const E of ae)ke.set(E.type,(ke.get(E.type)??0)+1);for(const[E,d]of ke){const m=document.createElement("span");m.className="info-type-badge",m.style.backgroundColor=Se(E),m.textContent=d>1?`${E} (${d})`:E,ye.appendChild(m)}ie.appendChild(ye),l.appendChild(ie);const _=rt("Selected Nodes"),W=document.createElement("ul");W.className="info-connections";for(const E of ae){const d=document.createElement("li");d.className="info-connection",s&&(d.classList.add("info-connection-link"),d.addEventListener("click",()=>{U(E.id)}));const m=document.createElement("span");m.className="info-target-dot",m.style.backgroundColor=Se(E.type);const g=document.createElement("span");g.className="info-target",g.textContent=lt(E);const C=document.createElement("span");C.className="info-edge-type",C.textContent=E.type,d.appendChild(m),d.appendChild(g),d.appendChild(C),W.appendChild(d)}_.appendChild(W),l.appendChild(_);const D=rt(J.length>0?`Connections Between Selected (${J.length})`:"Connections Between Selected");if(J.length===0){const E=document.createElement("p");E.className="info-empty-message",E.textContent="No direct connections between selected nodes",D.appendChild(E)}else{const E=document.createElement("ul");E.className="info-connections";for(const d of J){const m=Q.nodes.find(N=>N.id===d.sourceId),g=Q.nodes.find(N=>N.id===d.targetId),C=m?lt(m):d.sourceId,S=g?lt(g):d.targetId,u=document.createElement("li");if(u.className="info-connection",m){const N=document.createElement("span");N.className="info-target-dot",N.style.backgroundColor=Se(m.type),u.appendChild(N)}const x=document.createElement("span");x.className="info-target",x.textContent=C;const R=document.createElement("span");R.className="info-arrow",R.textContent="→";const I=document.createElement("span");I.className="info-edge-type",I.textContent=d.type;const O=document.createElement("span");if(O.className="info-arrow",O.textContent="→",u.appendChild(x),u.appendChild(R),u.appendChild(I),u.appendChild(O),g){const N=document.createElement("span");N.className="info-target-dot",N.style.backgroundColor=Se(g.type),u.appendChild(N)}const w=document.createElement("span");w.className="info-target",w.textContent=S,u.appendChild(w);const f=Object.keys(d.properties);if(f.length>0){const N=document.createElement("div");N.className="info-edge-props";for(const j of f){const oe=document.createElement("span");oe.className="info-edge-prop",oe.textContent=`${j}: ${Lt(d.properties[j])}`,N.appendChild(oe)}u.appendChild(N)}E.appendChild(u)}D.appendChild(E)}l.appendChild(D)}return{show(B,Q){if(P=Q,i=B,B.length===1&&!o){const K=B[0];k[c]!==K&&(c<k.length-1&&(k=k.slice(0,c+1)),k.push(K),c=k.length-1)}B.length===1?ce(B[0],Q):B.length>1&&V(B,Q)},hide:b,goBack:X,goForward:te,cycleConnection(B){if(T.length===0)return null;F===-1?F=B===1?0:T.length-1:(F+=B,F>=T.length&&(F=0),F<0&&(F=T.length-1));const Q=l.querySelectorAll(".info-connection");return Q.forEach((K,ae)=>{K.classList.toggle("info-connection-active",ae===F)}),F>=0&&Q[F]&&Q[F].scrollIntoView({block:"nearest"}),T[F]??null},setFocusDisabled(B){a=B;const Q=l.querySelector(".info-focus-btn");Q&&(Q.disabled=B,Q.style.opacity=B?"0.3":"")},get visible(){return!l.classList.contains("hidden")}}}function rt(t){const e=document.createElement("div");e.className="info-section";const s=document.createElement("h4");return s.className="info-section-title",s.textContent=t,e.appendChild(s),e}function no(t){if(Array.isArray(t)){const s=document.createElement("div");s.className="info-array";for(const r of t){const l=document.createElement("span");l.className="info-tag",l.textContent=String(r),s.appendChild(l)}return s}if(t!==null&&typeof t=="object"){const s=document.createElement("pre");return s.className="info-json",s.textContent=JSON.stringify(t,null,2),s}const e=document.createElement("span");return e.className="info-value",e.textContent=String(t??""),e}function Lt(t){return Array.isArray(t)?t.map(String).join(", "):t!==null&&typeof t=="object"?JSON.stringify(t):String(t??"")}function oo(t){const e=t.trim();if(e==="true")return!0;if(e==="false")return!1;if(e!==""&&!isNaN(Number(e)))return Number(e);if(e.startsWith("[")&&e.endsWith("]")||e.startsWith("{")&&e.endsWith("}"))try{return JSON.parse(e)}catch{return t}return t}function ln(t){t.style.height="auto",t.style.height=t.scrollHeight+"px"}function rn(t){try{return new Date(t).toLocaleString()}catch{return t}}function vn(t){for(const e of Object.values(t.properties))if(typeof e=="string")return e;return t.id}function dn(t,e){const s=e.toLowerCase();if(vn(t).toLowerCase().includes(s)||t.type.toLowerCase().includes(s))return!0;for(const r of Object.values(t.properties))if(typeof r=="string"&&r.toLowerCase().includes(s))return!0;return!1}function so(t,e){const s=(e==null?void 0:e.maxResults)??8,r=(e==null?void 0:e.debounceMs)??150;let l=null,y=null,k=null,c=null;const o=document.createElement("div");o.className="search-overlay hidden";const P=document.createElement("div");P.className="search-input-wrap";const i=document.createElement("input");i.className="search-input",i.type="text",i.placeholder="Search nodes...",i.setAttribute("autocomplete","off"),i.setAttribute("spellcheck","false");const a=document.createElement("kbd");a.className="search-kbd",a.textContent="/",P.appendChild(i),P.appendChild(a);const T=document.createElement("ul");T.className="search-results hidden",o.appendChild(P),o.appendChild(T),t.appendChild(o);function F(){if(!l)return null;const z=i.value.trim();if(z.length===0)return null;const ce=new Set;for(const V of l.nodes)dn(V,z)&&ce.add(V.id);return ce}function b(){const z=F();y==null||y(z),U()}function U(){T.innerHTML="",X=-1;const z=i.value.trim();if(!l||z.length===0){T.classList.add("hidden");return}const ce=[];for(const V of l.nodes)if(dn(V,z)&&(ce.push(V),ce.length>=s))break;if(ce.length===0){T.classList.add("hidden");return}for(const V of ce){const B=document.createElement("li");B.className="search-result-item";const Q=document.createElement("span");Q.className="search-result-dot",Q.style.backgroundColor=Se(V.type);const K=document.createElement("span");K.className="search-result-label";const ae=vn(V);K.textContent=ae.length>36?ae.slice(0,34)+"...":ae;const J=document.createElement("span");J.className="search-result-type",J.textContent=V.type,B.appendChild(Q),B.appendChild(K),B.appendChild(J),B.addEventListener("click",()=>{k==null||k(V.id),i.value="",T.classList.add("hidden"),b()}),T.appendChild(B)}T.classList.remove("hidden")}i.addEventListener("input",()=>{c&&clearTimeout(c),c=setTimeout(b,r)});let X=-1;function te(){const z=T.querySelectorAll(".search-result-item");z.forEach((ce,V)=>{ce.classList.toggle("search-result-active",V===X)}),X>=0&&z[X]&&z[X].scrollIntoView({block:"nearest"})}return i.addEventListener("keydown",z=>{const ce=T.querySelectorAll(".search-result-item");z.key==="ArrowDown"?(z.preventDefault(),ce.length>0&&(X=Math.min(X+1,ce.length-1),te())):z.key==="ArrowUp"?(z.preventDefault(),ce.length>0&&(X=Math.max(X-1,0),te())):z.key==="Enter"?(z.preventDefault(),X>=0&&ce[X]?ce[X].click():ce.length>0&&ce[0].click(),i.blur()):z.key==="Escape"&&(i.value="",i.blur(),T.classList.add("hidden"),X=-1,b())}),document.addEventListener("click",z=>{o.contains(z.target)||T.classList.add("hidden")}),i.addEventListener("focus",()=>a.classList.add("hidden")),i.addEventListener("blur",()=>{i.value.length===0&&a.classList.remove("hidden")}),{setLearningGraphData(z){l=z,i.value="",T.classList.add("hidden"),l&&l.nodes.length>0?o.classList.remove("hidden"):o.classList.add("hidden")},onFilterChange(z){y=z},onNodeSelect(z){k=z},clear(){i.value="",T.classList.add("hidden"),y==null||y(null)},focus(){i.focus()}}}function ao(t,e){let s=null,r=null,l=!0,y=null,k=!0,c=!0,o=!0,P="types",i="",a="",T=[],F=[];const b={types:new Set,nodeIds:new Set};function U(){if(!s)return[];const d=new Set;for(const m of s.nodes)b.types.has(m.type)&&d.add(m.id);for(const m of b.nodeIds)d.add(m);return[...d]}function X(d){if(b.nodeIds.has(d))return!0;const m=s==null?void 0:s.nodes.find(g=>g.id===d);return m?b.types.has(m.type):!1}function te(){return b.types.size===0&&b.nodeIds.size===0}function z(){const d=U();e.onFocusChange(d.length>0?d:null)}const ce=document.createElement("button");ce.className="tools-pane-toggle hidden",ce.title="Graph Inspector",ce.innerHTML='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16"/><path d="M4 12h16"/><path d="M4 17h10"/></svg>';const V=document.createElement("div");V.className="tools-pane-content hidden",t.appendChild(ce),t.appendChild(V),ce.addEventListener("click",()=>{var d;l=!l,V.classList.toggle("hidden",l),ce.classList.toggle("active",!l),l||(d=e.onOpen)==null||d.call(e)});function B(){if(V.innerHTML="",!r)return;const d=document.createElement("div");if(d.className="tools-pane-summary",d.innerHTML=`<span>${r.nodeCount} nodes</span><span class="tools-pane-sep">&middot;</span><span>${r.edgeCount} edges</span><span class="tools-pane-sep">&middot;</span><span>${r.types.length} types</span>`,V.appendChild(d),s&&r.nodeCount>0){const S=Math.ceil(JSON.stringify(s).length/4),u=Math.round(S/r.nodeCount),x=Math.max(10,Math.round(u*.3)*Math.min(5,r.nodeCount)),R=S>x?Math.round((1-x/S)*100):0,I=document.createElement("div");I.className="tools-pane-token-card";const O=Math.min(100,R),w=document.createElement("div");w.className="token-card-label",w.textContent="Token Efficiency",I.appendChild(w);const f=document.createElement("div");f.className="token-card-stat",f.textContent=`~${S.toLocaleString()} tokens stored`,I.appendChild(f);const N=document.createElement("div");N.className="token-card-bar";const j=document.createElement("div");j.className="token-card-bar-fill",j.style.width=`${O}%`,N.appendChild(j),I.appendChild(N);const oe=document.createElement("div");oe.className="token-card-stat",oe.textContent=`A search returns ~${x} tokens instead of ~${S.toLocaleString()} (${R}% reduction)`,I.appendChild(oe),V.appendChild(I)}const m=document.createElement("div");m.className="tools-pane-tabs";const g=[{id:"types",label:"Types"},{id:"insights",label:"Insights"},{id:"controls",label:"Controls"}];for(const S of g){const u=document.createElement("button");u.className="tools-pane-tab",P===S.id&&u.classList.add("tools-pane-tab-active"),u.textContent=S.label,u.addEventListener("click",()=>{P=S.id,B()}),m.appendChild(u)}V.appendChild(m),te()||ae(),F.length>0&&K(),P==="types"&&r.types.length>5?V.appendChild(ie("Filter types...",i,S=>{i=S,Q()})):P==="insights"&&r.orphans.length+r.singletons.length+r.emptyNodes.length>5&&V.appendChild(ie("Filter issues...",a,u=>{a=u,Q()}));const C=document.createElement("div");C.className="tools-pane-tab-content",V.appendChild(C),Q()}function Q(){const d=V.querySelector(".tools-pane-tab-content");d&&(d.innerHTML="",P==="types"?ee(d):P==="insights"?ye(d):P==="controls"&&ke(d))}function K(){V.appendChild(W(`Walk Trail (${F.length})`,d=>{for(let g=0;g<F.length;g++){const C=F[g],S=g===F.length-1;if(C.edgeType){const f=document.createElement("div");f.className="walk-trail-edge",f.textContent=`↓ ${C.edgeType}`,d.appendChild(f)}const u=document.createElement("div");u.className="tools-pane-row tools-pane-clickable",S&&(u.style.fontWeight="600");const x=document.createElement("span");x.className="tools-pane-count",x.style.minWidth="18px",x.textContent=`${g+1}`;const R=document.createElement("span");R.className="tools-pane-dot",R.style.backgroundColor=Se(C.type);const I=document.createElement("span");I.className="tools-pane-name",I.textContent=C.label;const O=document.createElement("span");O.className="tools-pane-count",O.textContent=C.type;const w=document.createElement("button");w.className="tools-pane-edit",w.style.opacity="1",w.textContent="×",w.title="Remove from trail",w.addEventListener("click",f=>{var N;f.stopPropagation(),(N=e.onWalkTrailRemove)==null||N.call(e,C.id)}),u.appendChild(x),u.appendChild(R),u.appendChild(I),u.appendChild(O),u.appendChild(w),u.addEventListener("click",()=>{e.onNavigateToNode(C.id)}),d.appendChild(u)}const m=document.createElement("div");if(m.className="tools-pane-export-row",e.onWalkIsolate){const g=document.createElement("button");g.className="tools-pane-export-btn",g.textContent="Isolate (I)",g.addEventListener("click",()=>e.onWalkIsolate()),m.appendChild(g)}if(e.onWalkSaveSnippet&&F.length>=2){const g=document.createElement("button");g.className="tools-pane-export-btn",g.textContent="Save snippet",g.addEventListener("click",()=>{tt("Save snippet","Name for this snippet").then(C=>{C&&e.onWalkSaveSnippet(C)})}),m.appendChild(g)}d.appendChild(m)}))}function ae(){if(!r||!s)return;const d=U();V.appendChild(W("Focused",m=>{for(const u of b.types){const x=r.types.find(N=>N.name===u);if(!x)continue;const R=document.createElement("div");R.className="tools-pane-row tools-pane-clickable";const I=document.createElement("span");I.className="tools-pane-dot",I.style.backgroundColor=Se(x.name);const O=document.createElement("span");O.className="tools-pane-name",O.textContent=x.name;const w=document.createElement("span");w.className="tools-pane-count",w.textContent=`${x.count} nodes`;const f=document.createElement("button");f.className="tools-pane-edit tools-pane-focus-active",f.style.opacity="1",f.textContent="×",f.title=`Remove ${x.name} from focus`,R.appendChild(I),R.appendChild(O),R.appendChild(w),R.appendChild(f),f.addEventListener("click",N=>{N.stopPropagation(),b.types.delete(x.name),z(),B()}),m.appendChild(R)}for(const u of b.nodeIds){const x=s.nodes.find(j=>j.id===u);if(!x)continue;const R=pn(x.properties)??x.id,I=document.createElement("div");I.className="tools-pane-row tools-pane-clickable";const O=document.createElement("span");O.className="tools-pane-dot",O.style.backgroundColor=Se(x.type);const w=document.createElement("span");w.className="tools-pane-name",w.textContent=R;const f=document.createElement("span");f.className="tools-pane-count",f.textContent=x.type;const N=document.createElement("button");N.className="tools-pane-edit tools-pane-focus-active",N.style.opacity="1",N.textContent="×",N.title=`Remove ${R} from focus`,I.appendChild(O),I.appendChild(w),I.appendChild(f),I.appendChild(N),I.addEventListener("click",j=>{j.target.closest(".tools-pane-edit")||e.onNavigateToNode(u)}),N.addEventListener("click",j=>{j.stopPropagation(),b.nodeIds.delete(u),z(),B()}),m.appendChild(I)}const g=document.createElement("div");g.className="tools-pane-row tools-pane-clickable tools-pane-focus-clear";const C=document.createElement("span");C.className="tools-pane-name",C.style.color="var(--accent)",C.textContent=`${d.length} total`;const S=document.createElement("span");S.className="tools-pane-badge",S.textContent="clear all",g.appendChild(C),g.appendChild(S),g.addEventListener("click",()=>{b.types.clear(),b.nodeIds.clear(),z(),B()}),m.appendChild(g)}))}function J(d){const m=document.createElement("div");m.className="tools-pane-row tools-pane-clickable",y===d.name&&m.classList.add("active");const g=document.createElement("span");g.className="tools-pane-dot",g.style.backgroundColor=Se(d.name);const C=document.createElement("span");C.className="tools-pane-name",C.textContent=d.name;const S=document.createElement("span");S.className="tools-pane-count",S.textContent=String(d.count);const u=document.createElement("button");u.className="tools-pane-edit tools-pane-focus-toggle",b.types.has(d.name)&&u.classList.add("tools-pane-focus-active"),u.textContent="◎",u.title=b.types.has(d.name)?`Remove ${d.name} from focus`:`Add ${d.name} to focus`;const x=document.createElement("button");return x.className="tools-pane-edit",x.textContent="✎",x.title=`Rename all ${d.name} nodes`,m.appendChild(g),m.appendChild(C),m.appendChild(S),m.appendChild(u),m.appendChild(x),m.addEventListener("click",R=>{R.target.closest(".tools-pane-edit")||(y===d.name?(y=null,e.onFilterByType(null)):(y=d.name,e.onFilterByType(d.name)),B())}),u.addEventListener("click",R=>{R.stopPropagation(),b.types.has(d.name)?b.types.delete(d.name):b.types.add(d.name),z(),B()}),x.addEventListener("click",R=>{R.stopPropagation(),D(m,d.name,I=>{I&&I!==d.name&&e.onRenameNodeType(d.name,I)})}),m}function ie(d,m,g){const C=document.createElement("input");return C.type="text",C.className="tools-pane-search",C.placeholder=d,C.value=m,C.addEventListener("input",()=>g(C.value)),C}function ee(d){if(!r)return;const m=i.toLowerCase();if(r.types.length){const C=r.types.filter(S=>!b.types.has(S.name)).filter(S=>!m||S.name.toLowerCase().includes(m));C.length>0&&d.appendChild(W("Node Types",S=>{for(const u of C)S.appendChild(J(u))}))}const g=r.edgeTypes.filter(C=>!m||C.name.toLowerCase().includes(m));g.length&&d.appendChild(W("Edge Types",C=>{for(const S of g){const u=document.createElement("div");u.className="tools-pane-row tools-pane-clickable";const x=document.createElement("span");x.className="tools-pane-name",x.textContent=S.name;const R=document.createElement("span");R.className="tools-pane-count",R.textContent=String(S.count);const I=document.createElement("button");I.className="tools-pane-edit",I.textContent="✎",I.title=`Rename all ${S.name} edges`,u.appendChild(x),u.appendChild(R),u.appendChild(I),I.addEventListener("click",O=>{O.stopPropagation(),D(u,S.name,w=>{w&&w!==S.name&&e.onRenameEdgeType(S.name,w)})}),C.appendChild(u)}}))}function ye(d){if(!r)return;const m=a.toLowerCase(),g=r.starred.filter(w=>!m||w.label.toLowerCase().includes(m)||w.type.toLowerCase().includes(m));g.length&&d.appendChild(W("★ Starred",w=>{for(const j of g){const oe=document.createElement("div");oe.className="tools-pane-row tools-pane-clickable";const pe=document.createElement("span");pe.className="tools-pane-dot",pe.style.backgroundColor=Se(j.type);const n=document.createElement("span");n.className="tools-pane-name",n.textContent=j.label;const p=document.createElement("button");p.className="tools-pane-edit tools-pane-focus-toggle",X(j.id)&&p.classList.add("tools-pane-focus-active"),p.textContent="◎",p.title=X(j.id)?`Remove ${j.label} from focus`:`Add ${j.label} to focus`,oe.appendChild(pe),oe.appendChild(n),oe.appendChild(p),oe.addEventListener("click",M=>{M.target.closest(".tools-pane-edit")||e.onNavigateToNode(j.id)}),p.addEventListener("click",M=>{M.stopPropagation(),b.nodeIds.has(j.id)?b.nodeIds.delete(j.id):b.nodeIds.add(j.id),z(),B()}),w.appendChild(oe)}const f=document.createElement("div");f.className="tools-pane-row tools-pane-actions";const N=document.createElement("button");if(N.className="tools-pane-action-btn",N.textContent="Focus all",N.title="Enter focus mode with all starred nodes",N.addEventListener("click",()=>{b.nodeIds.clear(),b.types.clear();for(const j of r.starred)b.nodeIds.add(j.id);z(),B()}),f.appendChild(N),e.onStarredSaveSnippet){const j=document.createElement("button");j.className="tools-pane-action-btn",j.textContent="Save as snippet",j.title="Save starred nodes as a reusable snippet",j.addEventListener("click",async()=>{const oe=await tt("Snippet name","starred");oe&&e.onStarredSaveSnippet(oe,r.starred.map(pe=>pe.id))}),f.appendChild(j)}w.appendChild(f)}));const C=r.mostConnected.filter(w=>!m||w.label.toLowerCase().includes(m)||w.type.toLowerCase().includes(m));C.length&&d.appendChild(W("Most Connected",w=>{for(const f of C){const N=document.createElement("div");N.className="tools-pane-row tools-pane-clickable";const j=document.createElement("span");j.className="tools-pane-dot",j.style.backgroundColor=Se(f.type);const oe=document.createElement("span");oe.className="tools-pane-name",oe.textContent=f.label;const pe=document.createElement("span");pe.className="tools-pane-count",pe.textContent=`${f.connections}`;const n=document.createElement("button");n.className="tools-pane-edit tools-pane-focus-toggle",X(f.id)&&n.classList.add("tools-pane-focus-active"),n.textContent="◎",n.title=X(f.id)?`Remove ${f.label} from focus`:`Add ${f.label} to focus`,N.appendChild(j),N.appendChild(oe),N.appendChild(pe),N.appendChild(n),N.addEventListener("click",p=>{p.target.closest(".tools-pane-edit")||e.onNavigateToNode(f.id)}),n.addEventListener("click",p=>{p.stopPropagation(),b.nodeIds.has(f.id)?b.nodeIds.delete(f.id):b.nodeIds.add(f.id),z(),B()}),w.appendChild(N)}}));const S=r.orphans.filter(w=>!m||w.label.toLowerCase().includes(m)||w.type.toLowerCase().includes(m)),u=r.singletons.filter(w=>!m||w.name.toLowerCase().includes(m)),x=r.emptyNodes.filter(w=>!m||w.label.toLowerCase().includes(m)||w.type.toLowerCase().includes(m)),R=S.length>0,I=u.length>0,O=x.length>0;if(!R&&!I&&!O){const w=document.createElement("div");w.className="tools-pane-empty-msg",w.textContent="No issues found",d.appendChild(w);return}R&&d.appendChild(W("Orphans",w=>{for(const f of S.slice(0,5)){const N=document.createElement("div");N.className="tools-pane-row tools-pane-clickable tools-pane-issue";const j=document.createElement("span");j.className="tools-pane-dot",j.style.backgroundColor=Se(f.type);const oe=document.createElement("span");oe.className="tools-pane-name",oe.textContent=f.label;const pe=document.createElement("span");pe.className="tools-pane-badge",pe.textContent="orphan";const n=document.createElement("button");n.className="tools-pane-edit tools-pane-focus-toggle",X(f.id)&&n.classList.add("tools-pane-focus-active"),n.textContent="◎",n.title=X(f.id)?`Remove ${f.label} from focus`:`Add ${f.label} to focus`,N.appendChild(j),N.appendChild(oe),N.appendChild(pe),N.appendChild(n),N.addEventListener("click",p=>{p.target.closest(".tools-pane-edit")||e.onNavigateToNode(f.id)}),n.addEventListener("click",p=>{p.stopPropagation(),b.nodeIds.has(f.id)?b.nodeIds.delete(f.id):b.nodeIds.add(f.id),z(),B()}),w.appendChild(N)}if(S.length>5){const f=document.createElement("div");f.className="tools-pane-more",f.textContent=`+ ${S.length-5} more orphans`,w.appendChild(f)}})),I&&d.appendChild(W("Singletons",w=>{for(const f of u.slice(0,5)){const N=document.createElement("div");N.className="tools-pane-row tools-pane-issue";const j=document.createElement("span");j.className="tools-pane-dot",j.style.backgroundColor=Se(f.name);const oe=document.createElement("span");oe.className="tools-pane-name",oe.textContent=f.name;const pe=document.createElement("span");pe.className="tools-pane-badge",pe.textContent="1 node",N.appendChild(j),N.appendChild(oe),N.appendChild(pe),w.appendChild(N)}})),O&&d.appendChild(W("Empty Nodes",w=>{for(const f of x.slice(0,5)){const N=document.createElement("div");N.className="tools-pane-row tools-pane-issue";const j=document.createElement("span");j.className="tools-pane-dot",j.style.backgroundColor=Se(f.type);const oe=document.createElement("span");oe.className="tools-pane-name",oe.textContent=f.label;const pe=document.createElement("span");pe.className="tools-pane-badge",pe.textContent="empty",N.appendChild(j),N.appendChild(oe),N.appendChild(pe),w.appendChild(N)}if(r.emptyNodes.length>5){const f=document.createElement("div");f.className="tools-pane-more",f.textContent=`+ ${r.emptyNodes.length-5} more empty nodes`,w.appendChild(f)}}))}function ke(d){d.appendChild(W("Display",m=>{const g=document.createElement("div");g.className="tools-pane-row tools-pane-clickable";const C=document.createElement("input");C.type="checkbox",C.checked=k,C.className="tools-pane-checkbox";const S=document.createElement("span");S.className="tools-pane-name",S.textContent="Edge labels",g.appendChild(C),g.appendChild(S),g.addEventListener("click",f=>{f.target!==C&&(C.checked=!C.checked),k=C.checked,e.onToggleEdgeLabels(k)}),m.appendChild(g);const u=document.createElement("div");u.className="tools-pane-row tools-pane-clickable";const x=document.createElement("input");x.type="checkbox",x.checked=c,x.className="tools-pane-checkbox";const R=document.createElement("span");R.className="tools-pane-name",R.textContent="Type regions",u.appendChild(x),u.appendChild(R),u.addEventListener("click",f=>{f.target!==x&&(x.checked=!x.checked),c=x.checked,e.onToggleTypeHulls(c)}),m.appendChild(u);const I=document.createElement("div");I.className="tools-pane-row tools-pane-clickable";const O=document.createElement("input");O.type="checkbox",O.checked=o,O.className="tools-pane-checkbox";const w=document.createElement("span");w.className="tools-pane-name",w.textContent="Minimap",I.appendChild(O),I.appendChild(w),I.addEventListener("click",f=>{f.target!==O&&(O.checked=!O.checked),o=O.checked,e.onToggleMinimap(o)}),m.appendChild(I)})),d.appendChild(W("Layout",m=>{m.appendChild(_("Clustering",0,1,.02,.08,g=>{e.onLayoutChange("clusterStrength",g)})),m.appendChild(_("Spacing",.5,20,.5,1.5,g=>{e.onLayoutChange("spacing",g)})),m.appendChild(_("Pan speed",20,200,10,60,g=>{e.onPanSpeedChange(g)}))})),d.appendChild(W("Export",m=>{const g=document.createElement("div");g.className="tools-pane-export-row";const C=document.createElement("button");C.className="tools-pane-export-btn",C.textContent="Export PNG",C.addEventListener("click",()=>e.onExport("png"));const S=document.createElement("button");S.className="tools-pane-export-btn",S.textContent="Export SVG",S.addEventListener("click",()=>e.onExport("svg")),g.appendChild(C),g.appendChild(S),m.appendChild(g)})),(e.onSnapshot||e.onRollback)&&d.appendChild(W("Versions",m=>{const g=document.createElement("div");g.className="tools-pane-export-row";const C=document.createElement("button");if(C.className="tools-pane-export-btn",C.textContent="Save snapshot",C.addEventListener("click",()=>{tt("Save snapshot","Label (optional)").then(S=>{var u;(u=e.onSnapshot)==null||u.call(e,S||void 0)})}),g.appendChild(C),m.appendChild(g),T.length>0)for(const S of T){const u=document.createElement("div");u.className="tools-pane-row";const x=document.createElement("span");x.className="tools-pane-name";const R=co(S.timestamp);x.textContent=S.label?`#${S.version} ${S.label}`:`#${S.version}`,x.title=`${R} — ${S.nodeCount} nodes, ${S.edgeCount} edges`;const I=document.createElement("span");I.className="tools-pane-count",I.textContent=R;const O=document.createElement("button");O.className="tools-pane-edit",O.style.opacity="1",O.textContent="↩",O.title="Restore this snapshot",O.addEventListener("click",()=>{Tt("Restore snapshot",`Restore snapshot #${S.version}? Current state will be lost unless you save a snapshot first.`).then(w=>{var f;w&&((f=e.onRollback)==null||f.call(e,S.version))})}),u.appendChild(x),u.appendChild(I),u.appendChild(O),m.appendChild(u)}else{const S=document.createElement("div");S.className="tools-pane-empty-msg",S.textContent="No snapshots yet",m.appendChild(S)}}))}function _(d,m,g,C,S,u){const x=document.createElement("div");x.className="tools-pane-slider-row";const R=document.createElement("span");R.className="tools-pane-slider-label",R.textContent=d;const I=document.createElement("input");I.type="range",I.className="tools-pane-slider",I.min=String(m),I.max=String(g),I.step=String(C),I.value=String(S);const O=document.createElement("span");return O.className="tools-pane-slider-value",O.textContent=String(S),I.addEventListener("input",()=>{const w=parseFloat(I.value);O.textContent=w%1===0?String(w):w.toFixed(2),u(w)}),x.appendChild(R),x.appendChild(I),x.appendChild(O),x}function W(d,m){const g=document.createElement("div");g.className="tools-pane-section";const C=document.createElement("div");return C.className="tools-pane-heading",C.textContent=d,g.appendChild(C),m(g),g}function D(d,m,g){const C=document.createElement("input");C.className="tools-pane-inline-input",C.value=m,C.type="text";const S=d.innerHTML;d.innerHTML="",d.classList.add("tools-pane-editing"),d.appendChild(C),C.focus(),C.select();function u(){const x=C.value.trim();d.classList.remove("tools-pane-editing"),x&&x!==m?g(x):d.innerHTML=S}C.addEventListener("keydown",x=>{x.key==="Enter"&&(x.preventDefault(),u()),x.key==="Escape"&&(d.innerHTML=S,d.classList.remove("tools-pane-editing"))}),C.addEventListener("blur",u)}function E(d){const m=new Map,g=new Map,C=new Map,S=new Set;for(const f of d.nodes)m.set(f.type,(m.get(f.type)??0)+1);for(const f of d.edges)g.set(f.type,(g.get(f.type)??0)+1),C.set(f.sourceId,(C.get(f.sourceId)??0)+1),C.set(f.targetId,(C.get(f.targetId)??0)+1),S.add(f.sourceId),S.add(f.targetId);const u=f=>pn(f.properties)??f.id,x=d.nodes.filter(f=>f.properties._starred===!0).map(f=>({id:f.id,label:u(f),type:f.type})),R=d.nodes.filter(f=>!S.has(f.id)).map(f=>({id:f.id,label:u(f),type:f.type})),I=[...m.entries()].filter(([,f])=>f===1).map(([f])=>({name:f})),O=d.nodes.filter(f=>Object.keys(f.properties).length===0).map(f=>({id:f.id,label:f.id,type:f.type})),w=d.nodes.map(f=>({id:f.id,label:u(f),type:f.type,connections:C.get(f.id)??0})).filter(f=>f.connections>0).sort((f,N)=>N.connections-f.connections).slice(0,5);return{nodeCount:d.nodes.length,edgeCount:d.edges.length,types:[...m.entries()].sort((f,N)=>N[1]-f[1]).map(([f,N])=>({name:f,count:N})),edgeTypes:[...g.entries()].sort((f,N)=>N[1]-f[1]).map(([f,N])=>({name:f,count:N})),starred:x,orphans:R,singletons:I,emptyNodes:O,mostConnected:w}}return{collapse(){l=!0,V.classList.add("hidden"),ce.classList.remove("active")},addToFocusSet(d){for(const m of d)b.nodeIds.add(m);z(),B()},clearFocusSet(){b.types.clear(),b.nodeIds.clear(),z(),B()},setData(d){s=d,y=null,b.types.clear(),b.nodeIds.clear(),s&&s.nodes.length>0?(r=E(s),ce.classList.remove("hidden"),B()):(r=null,ce.classList.add("hidden"),V.classList.add("hidden"))},setSnapshots(d){T=d,P==="controls"&&Q()},setWalkTrail(d){F=d,B()}}}function co(t){const e=Date.now()-new Date(t).getTime(),s=Math.floor(e/6e4);if(s<1)return"just now";if(s<60)return`${s}m ago`;const r=Math.floor(s/60);return r<24?`${r}h ago`:`${Math.floor(r/24)}d ago`}function pn(t){for(const e of Object.values(t))if(typeof e=="string")return e;return null}function io(t,e){const s=e.split("+"),r=s.pop(),l=s.map(o=>o.toLowerCase()),y=l.includes("ctrl")||l.includes("cmd")||l.includes("meta"),k=l.includes("shift"),c=l.includes("alt");return y!==(t.ctrlKey||t.metaKey)||!y&&(t.ctrlKey||t.metaKey)||k&&!t.shiftKey||c!==t.altKey?!1:r.toLowerCase()==="escape"?t.key==="Escape":r.toLowerCase()==="tab"?t.key==="Tab":l.length>0?t.key.toLowerCase()===r.toLowerCase():t.key===r}function lo(){return{search:"Focus search",searchAlt:"Focus search (alt)",undo:"Undo",redo:"Redo",help:"Toggle help",escape:"Exit focus / close panel",focus:"Focus on selected / exit focus",toggleEdges:"Toggle edges on/off",center:"Center view on graph",nextNode:"Next node in view",prevNode:"Previous node in view",nextConnection:"Next connection",prevConnection:"Previous connection",historyBack:"Node history back",historyForward:"Node history forward",hopsIncrease:"Increase hops",hopsDecrease:"Decrease hops",panLeft:"Pan left",panDown:"Pan down",panUp:"Pan up",panRight:"Pan right",panFastLeft:"Pan fast left",zoomOut:"Zoom out",zoomIn:"Zoom in",panFastRight:"Pan fast right",spacingDecrease:"Decrease spacing",spacingIncrease:"Increase spacing",clusteringDecrease:"Decrease clustering",clusteringIncrease:"Increase clustering",toggleSidebar:"Toggle sidebar",walkMode:"Toggle walk mode (in focus)",walkIsolate:"Isolate walk trail nodes"}}const ro=[{key:"Click",description:"Select node"},{key:"Ctrl+Click",description:"Multi-select nodes"},{key:"Drag",description:"Pan canvas"},{key:"Scroll",description:"Zoom in/out"}],po=["search","searchAlt","undo","redo","help","focus","toggleEdges","center","nextNode","prevNode","nextConnection","prevConnection","historyBack","historyForward","hopsIncrease","hopsDecrease","panLeft","panDown","panUp","panRight","panFastLeft","panFastRight","zoomIn","zoomOut","spacingDecrease","spacingIncrease","clusteringDecrease","clusteringIncrease","toggleSidebar","walkMode","walkIsolate","escape"];function uo(t){return t.split("+").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("+")}function mo(t,e){const s=lo(),r=document.createElement("div");r.className="shortcuts-overlay hidden";const l=document.createElement("div");l.className="shortcuts-modal";const y=document.createElement("h3");y.className="shortcuts-title",y.textContent="Keyboard Shortcuts";const k=document.createElement("div");k.className="shortcuts-list";for(const a of po){const T=e[a];if(!T)continue;const F=document.createElement("div");F.className="shortcuts-row";const b=document.createElement("div");b.className="shortcuts-keys";const U=document.createElement("kbd");U.textContent=uo(T),b.appendChild(U);const X=document.createElement("span");X.className="shortcuts-desc",X.textContent=s[a],F.appendChild(b),F.appendChild(X),k.appendChild(F)}for(const a of ro){const T=document.createElement("div");T.className="shortcuts-row";const F=document.createElement("div");F.className="shortcuts-keys";const b=document.createElement("kbd");b.textContent=a.key,F.appendChild(b);const U=document.createElement("span");U.className="shortcuts-desc",U.textContent=a.description,T.appendChild(F),T.appendChild(U),k.appendChild(T)}const c=document.createElement("button");c.className="shortcuts-close",c.textContent="×",l.appendChild(c),l.appendChild(y),l.appendChild(k),r.appendChild(l),t.appendChild(r);function o(){r.classList.remove("hidden")}function P(){r.classList.add("hidden")}function i(){r.classList.toggle("hidden")}return c.addEventListener("click",P),r.addEventListener("click",a=>{a.target===r&&P()}),{show:o,hide:P,toggle:i}}function ho(t){const e=document.createElement("div");return e.className="empty-state",e.innerHTML=`
5
+ <div class="empty-state-bg">
6
+ <div class="empty-state-circle c1"></div>
7
+ <div class="empty-state-circle c2"></div>
8
+ <div class="empty-state-circle c3"></div>
9
+ <div class="empty-state-circle c4"></div>
10
+ <div class="empty-state-circle c5"></div>
11
+ <svg class="empty-state-lines" viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
12
+ <line x1="80" y1="60" x2="220" y2="140" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
13
+ <line x1="220" y1="140" x2="320" y2="80" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
14
+ <line x1="220" y1="140" x2="160" y2="240" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
15
+ <line x1="160" y1="240" x2="300" y2="220" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
16
+ </svg>
17
+ </div>
18
+ <div class="empty-state-content">
19
+ <div class="empty-state-icon">
20
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
21
+ <path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 002 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0022 16z"/>
22
+ <polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
23
+ <line x1="12" y1="22.08" x2="12" y2="12"/>
24
+ </svg>
25
+ </div>
26
+ <h2 class="empty-state-title">No learning graphs yet</h2>
27
+ <p class="empty-state-desc">Connect Backpack to Claude, then start a conversation. Claude will build your first learning graph automatically.</p>
28
+ <div class="empty-state-setup">
29
+ <div class="empty-state-label">Add Backpack to Claude Code:</div>
30
+ <code class="empty-state-code">claude mcp add backpack-local -s user -- npx backpack-ontology@latest</code>
31
+ </div>
32
+ <p class="empty-state-hint">Press <kbd>?</kbd> for keyboard shortcuts</p>
33
+ </div>
34
+ `,t.appendChild(e),{show(){e.classList.remove("hidden")},hide(){e.classList.add("hidden")}}}const fo=30;function go(){let t=[],e=[];return{push(s){t.push(JSON.stringify(s)),t.length>fo&&t.shift(),e=[]},undo(s){return t.length===0?null:(e.push(JSON.stringify(s)),JSON.parse(t.pop()))},redo(s){return e.length===0?null:(t.push(JSON.stringify(s)),JSON.parse(e.pop()))},canUndo(){return t.length>0},canRedo(){return e.length>0},clear(){t=[],e=[]}}}function yo(t,e){let s=null;function r(k,c,o,P,i){l(),s=document.createElement("div"),s.className="context-menu",s.style.left=`${P}px`,s.style.top=`${i}px`;const a=[{label:o?"★ Unstar":"☆ Star",action:()=>e.onStar(k),premium:!1},{label:"◎ Focus on node",action:()=>e.onFocusNode(k),premium:!1},{label:"⑂ Explore in branch",action:()=>e.onExploreInBranch(k),premium:!1},{label:"⎘ Copy ID",action:()=>e.onCopyId(k),premium:!1}];e.onExpand&&a.push({label:"⊕ Expand node",action:()=>e.onExpand(k),premium:!0}),e.onExplainPath&&a.push({label:"↔ Explain path to…",action:()=>e.onExplainPath(k),premium:!0}),e.onEnrich&&a.push({label:"≡ Enrich from web",action:()=>e.onEnrich(k),premium:!0});let T=!1;for(const b of a){if(!T&&b.premium){const X=document.createElement("div");X.className="context-menu-separator",s.appendChild(X),T=!0}const U=document.createElement("div");U.className="context-menu-item",U.textContent=b.label,U.addEventListener("click",()=>{b.action(),l()}),s.appendChild(U)}t.appendChild(s);const F=s.getBoundingClientRect();F.right>window.innerWidth&&(s.style.left=`${P-F.width}px`),F.bottom>window.innerHeight&&(s.style.top=`${i-F.height}px`),setTimeout(()=>document.addEventListener("click",l),0),document.addEventListener("keydown",y)}function l(){s&&(s.remove(),s=null),document.removeEventListener("click",l),document.removeEventListener("keydown",y)}function y(k){k.key==="Escape"&&l()}return{show:r,hide:l}}const Co={search:"/",searchAlt:"ctrl+k",undo:"ctrl+z",redo:"ctrl+shift+z",help:"?",escape:"Escape",focus:"f",toggleEdges:"e",center:"c",nextNode:".",prevNode:",",nextConnection:">",prevConnection:"<",historyBack:"(",historyForward:")",hopsIncrease:"=",hopsDecrease:"-",panLeft:"h",panDown:"j",panUp:"k",panRight:"l",panFastLeft:"H",zoomOut:"J",zoomIn:"K",panFastRight:"L",spacingDecrease:"[",spacingIncrease:"]",clusteringDecrease:"{",clusteringIncrease:"}",toggleSidebar:"Tab",walkMode:"w",walkIsolate:"i"},vo={edges:!0,edgeLabels:!0,typeHulls:!0,minimap:!0,theme:"system"},xo={spacing:1.5,clustering:.08},bo={panSpeed:60,panFastMultiplier:3,zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300},Eo={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15},wo={pulseSpeed:.02},ko={maxSearchResults:8,maxQualityItems:5,maxMostConnected:5,searchDebounceMs:150},So={keybindings:Co,display:vo,layout:xo,navigation:bo,lod:Eo,walk:wo,limits:ko};let de="",L=null,It=new Set,Mt=!1;async function No(){const t=document.getElementById("canvas-container"),e={...So};try{const n=await fetch("/api/config");if(n.ok){const p=await n.json();Object.assign(e.keybindings,p.keybindings??{}),Object.assign(e.display,p.display??{}),Object.assign(e.layout,p.layout??{}),Object.assign(e.navigation,p.navigation??{}),Object.assign(e.lod,p.lod??{}),Object.assign(e.limits,p.limits??{})}}catch{}const s=e.keybindings,r=window.matchMedia("(prefers-color-scheme: dark)"),l=e.display.theme==="system"?r.matches?"dark":"light":e.display.theme,k=localStorage.getItem("backpack-theme")??l;document.documentElement.setAttribute("data-theme",k);const c=document.createElement("button");c.className="theme-toggle",c.textContent=k==="light"?"☾":"☼",c.title="Toggle light/dark mode",c.addEventListener("click",()=>{const p=document.documentElement.getAttribute("data-theme")==="light"?"dark":"light";document.documentElement.setAttribute("data-theme",p),localStorage.setItem("backpack-theme",p),c.textContent=p==="light"?"☾":"☼"}),t.appendChild(c);const o=go();async function P(){if(!de||!L)return;L.metadata.updatedAt=new Date().toISOString(),await wt(de,L),a.loadGraph(L),ae.setLearningGraphData(L),J.setData(L);const n=await ct();E.setSummaries(n)}async function i(n){L=n,await wt(de,L),a.loadGraph(L),ae.setLearningGraphData(L),J.setData(L);const p=await ct();E.setSummaries(p)}let a;const T=to(t,{onUpdateNode(n,p){if(!L)return;o.push(L);const M=L.nodes.find(G=>G.id===n);M&&(M.properties={...M.properties,...p},M.updatedAt=new Date().toISOString(),P().then(()=>T.show([n],L)))},onChangeNodeType(n,p){if(!L)return;o.push(L);const M=L.nodes.find(G=>G.id===n);M&&(M.type=p,M.updatedAt=new Date().toISOString(),P().then(()=>T.show([n],L)))},onDeleteNode(n){L&&(o.push(L),L.nodes=L.nodes.filter(p=>p.id!==n),L.edges=L.edges.filter(p=>p.sourceId!==n&&p.targetId!==n),P())},onDeleteEdge(n){var M;if(!L)return;o.push(L);const p=(M=L.edges.find(G=>G.id===n))==null?void 0:M.sourceId;L.edges=L.edges.filter(G=>G.id!==n),P().then(()=>{p&&L&&T.show([p],L)})},onAddProperty(n,p,M){if(!L)return;o.push(L);const G=L.nodes.find(re=>re.id===n);G&&(G.properties[p]=M,G.updatedAt=new Date().toISOString(),P().then(()=>T.show([n],L)))}},n=>{a.panToNode(n)},n=>{J.addToFocusSet(n)}),F=window.matchMedia("(max-width: 768px)");let b=[],U=e.display.edges,X=e.navigation.panSpeed,te=-1,z=null;function ce(n){z&&z.remove(),z=document.createElement("div"),z.className="focus-indicator";const p=document.createElement("span");p.className="focus-indicator-label",p.textContent=`Focused: ${n.totalNodes} nodes`;const M=document.createElement("span");M.className="focus-indicator-hops",M.textContent=`${n.hops}`;const G=document.createElement("button");G.className="focus-indicator-btn",G.textContent="−",G.title="Fewer hops",G.disabled=n.hops===0,G.addEventListener("click",()=>{a.enterFocus(n.seedNodeIds,Math.max(0,n.hops-1))});const re=document.createElement("button");re.className="focus-indicator-btn",re.textContent="+",re.title="More hops",re.disabled=!1,re.addEventListener("click",()=>{a.enterFocus(n.seedNodeIds,n.hops+1)});const me=document.createElement("button");me.className="focus-indicator-btn focus-indicator-exit",me.textContent="×",me.title="Exit focus (Esc)",me.addEventListener("click",()=>J.clearFocusSet());const le=document.createElement("button");le.className="walk-indicator",a.getWalkMode()&&le.classList.add("active"),le.textContent="Walk",le.title="Toggle walk mode (W) — click nodes to traverse",le.addEventListener("click",()=>{a.setWalkMode(!a.getWalkMode()),le.classList.toggle("active",a.getWalkMode())}),z.appendChild(p),z.appendChild(G),z.appendChild(M),z.appendChild(re),z.appendChild(le),z.appendChild(me)}function V(){z&&(z.remove(),z=null)}const B=document.createElement("div");B.className="path-bar hidden",t.appendChild(B);function Q(n){if(B.innerHTML="",!L)return;for(let M=0;M<n.nodeIds.length;M++){const G=n.nodeIds[M],re=L.nodes.find(ge=>ge.id===G);if(!re)continue;const me=Object.values(re.properties).find(ge=>typeof ge=="string")??re.id;if(M>0){const ge=n.edgeIds[M-1],Le=L.edges.find(He=>He.id===ge),xe=document.createElement("span");xe.className="path-bar-edge",xe.textContent=Le?`→ ${Le.type} →`:"→",B.appendChild(xe)}const le=document.createElement("span");le.className="path-bar-node",le.textContent=me,le.addEventListener("click",()=>a.panToNode(G)),B.appendChild(le)}const p=document.createElement("button");p.className="path-bar-close",p.textContent="×",p.addEventListener("click",K),B.appendChild(p),B.classList.remove("hidden")}function K(){B.classList.add("hidden"),B.innerHTML="",a.clearHighlightedPath()}a=Qn(t,n=>{if(b=n??[],!a.getWalkMode())if(n&&n.length===2){const p=a.findPath(n[0],n[1]);p&&p.nodeIds.length>0?(a.setHighlightedPath(p.nodeIds,p.edgeIds),Q(p)):K()}else K();n&&n.length>0&&L?(T.show(n,L),F.matches&&J.collapse(),I(de,n)):(T.hide(),de&&I(de))},n=>{if(n){ce(n);const p=t.querySelector(".canvas-top-left");p&&z&&p.appendChild(z),I(de,n.seedNodeIds),T.setFocusDisabled(n.hops===0),m()}else V(),T.setFocusDisabled(!1),de&&I(de),m()},{lod:e.lod,navigation:e.navigation,walk:e.walk});const ae=so(t,{maxResults:e.limits.maxSearchResults,debounceMs:e.limits.searchDebounceMs}),J=ao(t,{onFilterByType(n){if(L)if(n===null)a.setFilteredNodeIds(null);else{const p=new Set(((L==null?void 0:L.nodes)??[]).filter(M=>M.type===n).map(M=>M.id));a.setFilteredNodeIds(p)}},onNavigateToNode(n){a.panToNode(n),L&&T.show([n],L)},onWalkTrailRemove(n){a.removeFromWalkTrail(n),m()},onWalkIsolate(){if(!L)return;const n=a.getWalkTrail();n.length!==0&&a.enterFocus(n,0)},async onWalkSaveSnippet(n){if(!de||!L)return;const p=a.getWalkTrail();if(p.length<2)return;const M=new Set(p),G=L.edges.filter(re=>M.has(re.sourceId)&&M.has(re.targetId)).map(re=>re.id);await _t(de,n,p,G),await S(de)},async onStarredSaveSnippet(n,p){if(!de||!L)return;const M=new Set(p),G=L.edges.filter(re=>M.has(re.sourceId)&&M.has(re.targetId)).map(re=>re.id);await _t(de,n,p,G),await S(de)},onFocusChange(n){n&&n.length>0?a.enterFocus(n,0):a.isFocused()&&a.exitFocus()},onRenameNodeType(n,p){if(L){o.push(L);for(const M of L.nodes)M.type===n&&(M.type=p,M.updatedAt=new Date().toISOString());P()}},onRenameEdgeType(n,p){if(L){o.push(L);for(const M of L.edges)M.type===n&&(M.type=p);P()}},onToggleEdgeLabels(n){a.setEdgeLabels(n)},onToggleTypeHulls(n){a.setTypeHulls(n)},onToggleMinimap(n){a.setMinimap(n)},onLayoutChange(n,p){Qe({[n]:p}),a.reheat()},onPanSpeedChange(n){X=n},onExport(n){const p=a.exportImage(n);if(!p)return;const M=document.createElement("a");M.download=`${de||"graph"}.${n}`,M.href=p,M.click()},onSnapshot:async n=>{de&&(await Tn(de,n),await C(de))},onRollback:async n=>{de&&(await An(de,n),L=await ft(de),a.loadGraph(L),ae.setLearningGraphData(L),J.setData(L),await C(de))},onOpen(){F.matches&&T.hide()}}),ie=document.createElement("div");ie.className="canvas-top-bar";const ee=document.createElement("div");ee.className="canvas-top-left";const ye=document.createElement("div");ye.className="canvas-top-center";const ke=document.createElement("div");ke.className="canvas-top-right";const _=t.querySelector(".tools-pane-toggle");_&&ee.appendChild(_);const W=t.querySelector(".search-overlay");W&&ye.appendChild(W);const D=t.querySelector(".zoom-controls");D&&ke.appendChild(D),ke.appendChild(c),ie.appendChild(ee),ie.appendChild(ye),ie.appendChild(ke),t.appendChild(ie),ae.onFilterChange(n=>{a.setFilteredNodeIds(n)}),ae.onNodeSelect(n=>{a.isFocused()&&J.clearFocusSet(),a.panToNode(n),L&&T.show([n],L)});const E=Fn(document.getElementById("sidebar"),{onSelect:n=>w(n),onRename:async(n,p)=>{await Nn(n,p),de===n&&(de=p);const M=await ct();E.setSummaries(M),E.setActive(de),de===p&&(L=await ft(p),a.loadGraph(L),ae.setLearningGraphData(L),J.setData(L))},onBranchSwitch:async(n,p)=>{await Xt(n,p),await g(n),L=await ft(n),a.loadGraph(L),ae.setLearningGraphData(L),J.setData(L),await C(n)},onBranchCreate:async(n,p)=>{await Yt(n,p),await g(n)},onBranchDelete:async(n,p)=>{await In(n,p),await g(n)},onSnippetLoad:async(n,p)=>{var G;const M=await Rn(n,p);((G=M==null?void 0:M.nodeIds)==null?void 0:G.length)>0&&a.enterFocus(M.nodeIds,0)},onSnippetDelete:async(n,p)=>{await Pn(n,p),await S(n)},onBackpackSwitch:async n=>{await fetch("/api/backpacks/switch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n})}),await d()},onBackpackRegister:async(n,p,M)=>{await fetch("/api/backpacks",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n,path:p,activate:M})}),await d()}});async function d(){try{const p=await(await fetch("/api/backpacks")).json();E.setBackpacks(p)}catch{}try{const n=await ct();E.setSummaries(n),de&&!n.some(p=>p.name===de)&&(de="",L=null,a.loadGraph({metadata:{name:"",description:"",createdAt:"",updatedAt:""},nodes:[],edges:[]}))}catch{}}function m(){const n=a.getWalkTrail();if(!L||n.length===0){J.setWalkTrail([]),K();return}const p=[],M=n.map((G,re)=>{const me=L.nodes.find(ge=>ge.id===G);let le;if(re>0){const ge=n[re-1],Le=L.edges.find(xe=>xe.sourceId===ge&&xe.targetId===G||xe.targetId===ge&&xe.sourceId===G);le=Le==null?void 0:Le.type,Le&&p.push(Le.id)}return{id:G,label:me?Object.values(me.properties).find(ge=>typeof ge=="string")??me.id:G,type:(me==null?void 0:me.type)??"?",edgeType:le}});J.setWalkTrail(M),n.length>=2?(a.setHighlightedPath(n,p),Q({nodeIds:n,edgeIds:p})):K()}async function g(n){const p=await Ln(n),M=p.find(G=>G.active);M&&E.setActiveBranch(n,M.name,p)}async function C(n){const p=await Mn(n);J.setSnapshots(p)}async function S(n){const p=await Bn(n);E.setSnippets(n,p)}ee.insertBefore(E.expandBtn,ee.firstChild);const u=mo(t,s),x=ho(t),R=yo(t,{onStar(n){if(!L)return;const p=L.nodes.find(G=>G.id===n);if(!p)return;const M=p.properties._starred===!0;p.properties._starred=!M,wt(de,L),a.loadGraph(L)},onFocusNode(n){J.addToFocusSet([n])},onExploreInBranch(n){if(de){const p=`explore-${n.slice(0,8)}`;Yt(de,p).then(()=>{Xt(de,p).then(()=>{a.enterFocus([n],1)})})}},onCopyId(n){navigator.clipboard.writeText(n)}});t.addEventListener("contextmenu",n=>{n.preventDefault();const p=t.querySelector("canvas");if(!p||!L)return;const M=p.getBoundingClientRect(),G=n.clientX-M.left,re=n.clientY-M.top,me=a.nodeAtScreen(G,re);if(!me)return;const le=L.nodes.find(xe=>xe.id===me.id);if(!le)return;const ge=Object.values(le.properties).find(xe=>typeof xe=="string")??le.id,Le=le.properties._starred===!0;R.show(le.id,ge,Le,n.clientX-M.left,n.clientY-M.top)}),e.display.edges||a.setEdges(!1),e.display.edgeLabels||a.setEdgeLabels(!1),e.display.typeHulls||a.setTypeHulls(!1),e.display.minimap||a.setMinimap(!1);function I(n,p){const M=[];p!=null&&p.length&&M.push("node="+p.map(encodeURIComponent).join(","));const G=a.getFocusInfo();G&&(M.push("focus="+G.seedNodeIds.map(encodeURIComponent).join(",")),M.push("hops="+G.hops));const re="#"+encodeURIComponent(n)+(M.length?"?"+M.join("&"):"");history.replaceState(null,"",re)}function O(){const n=window.location.hash.slice(1);if(!n)return{graph:null,nodes:[],focus:[],hops:1};const[p,M]=n.split("?"),G=p?decodeURIComponent(p):null;let re=[],me=[],le=1;if(M){const ge=new URLSearchParams(M),Le=ge.get("node");Le&&(re=Le.split(",").map(decodeURIComponent));const xe=ge.get("focus");xe&&(me=xe.split(",").map(decodeURIComponent));const He=ge.get("hops");He&&(le=Math.max(0,parseInt(He,10)||1))}return{graph:G,nodes:re,focus:me,hops:le}}async function w(n,p,M,G){de=n,Mt=It.has(n),E.setActive(n),T.hide(),V(),ae.clear(),o.clear(),L=Mt?await Sn(n):await ft(n);const re=Hn(L.nodes.length);if(Qe({spacing:Math.max(e.layout.spacing,re.spacing),clusterStrength:Math.max(e.layout.clustering,re.clusterStrength)}),a.loadGraph(L),ae.setLearningGraphData(L),J.setData(L),x.hide(),I(n),Mt||(await g(n),await C(n),await S(n)),M!=null&&M.length&&L){const me=M.filter(le=>L.nodes.some(ge=>ge.id===le));if(me.length){setTimeout(()=>{a.enterFocus(me,G??1)},500);return}}if(p!=null&&p.length&&L){const me=p.filter(le=>L.nodes.some(ge=>ge.id===le));me.length&&setTimeout(()=>{a.panToNodes(me),L&&T.show(me,L),I(n,me)},500)}}try{const p=await(await fetch("/api/backpacks")).json();E.setBackpacks(p)}catch{}const[f,N]=await Promise.all([ct(),kn().catch(()=>[])]);E.setSummaries(f),E.setRemotes(N),It=new Set(N.map(n=>n.name));const j=O(),oe=j.graph&&f.some(n=>n.name===j.graph)||j.graph&&It.has(j.graph)?j.graph:f.length>0?f[0].name:N.length>0?N[0].name:null;oe?await w(oe,j.nodes.length?j.nodes:void 0,j.focus.length?j.focus:void 0,j.hops):x.show();const pe={search(){ae.focus()},searchAlt(){ae.focus()},undo(){if(L){const n=o.undo(L);n&&i(n)}},redo(){if(L){const n=o.redo(L);n&&i(n)}},focus(){a.isFocused()?J.clearFocusSet():b.length>0&&J.addToFocusSet(b)},hopsDecrease(){const n=a.getFocusInfo();n&&n.hops>0&&a.enterFocus(n.seedNodeIds,n.hops-1)},hopsIncrease(){const n=a.getFocusInfo();n&&a.enterFocus(n.seedNodeIds,n.hops+1)},nextNode(){const n=a.getNodeIds();n.length>0&&(te=(te+1)%n.length,a.panToNode(n[te]),L&&T.show([n[te]],L))},prevNode(){const n=a.getNodeIds();n.length>0&&(te=te<=0?n.length-1:te-1,a.panToNode(n[te]),L&&T.show([n[te]],L))},nextConnection(){const n=T.cycleConnection(1);n&&a.panToNode(n)},prevConnection(){const n=T.cycleConnection(-1);n&&a.panToNode(n)},historyBack(){T.goBack()},historyForward(){T.goForward()},center(){a.centerView()},toggleEdges(){U=!U,a.setEdges(U)},panLeft(){a.panBy(-X,0)},panDown(){a.panBy(0,X)},panUp(){a.panBy(0,-X)},panRight(){a.panBy(X,0)},panFastLeft(){a.panBy(-X*e.navigation.panFastMultiplier,0)},zoomOut(){a.zoomBy(1/e.navigation.zoomFactor)},zoomIn(){a.zoomBy(e.navigation.zoomFactor)},panFastRight(){a.panBy(X*e.navigation.panFastMultiplier,0)},spacingDecrease(){const n=dt();Qe({spacing:Math.max(.5,n.spacing-.5)}),a.reheat()},spacingIncrease(){const n=dt();Qe({spacing:Math.min(20,n.spacing+.5)}),a.reheat()},clusteringDecrease(){const n=dt();Qe({clusterStrength:Math.max(0,n.clusterStrength-.03)}),a.reheat()},clusteringIncrease(){const n=dt();Qe({clusterStrength:Math.min(1,n.clusterStrength+.03)}),a.reheat()},help(){u.toggle()},toggleSidebar(){E.toggle()},walkIsolate(){if(!L)return;const n=a.getWalkTrail();n.length!==0&&a.enterFocus(n,0)},walkMode(){!a.isFocused()&&b.length>0&&J.addToFocusSet(b),a.setWalkMode(!a.getWalkMode());const n=t.querySelector(".walk-indicator");n&&n.classList.toggle("active",a.getWalkMode()),m()},escape(){a.isFocused()?J.clearFocusSet():u.hide()}};document.addEventListener("keydown",n=>{var p;if(!(n.target instanceof HTMLInputElement||n.target instanceof HTMLTextAreaElement)){for(const[M,G]of Object.entries(s))if(io(n,G)){(M==="search"||M==="searchAlt"||M==="undo"||M==="redo"||M==="toggleSidebar")&&n.preventDefault(),(p=pe[M])==null||p.call(pe);return}}}),window.addEventListener("hashchange",()=>{const n=O();if(n.graph&&n.graph!==de)w(n.graph,n.nodes.length?n.nodes:void 0,n.focus.length?n.focus:void 0,n.hops);else if(n.graph&&n.focus.length&&L)a.enterFocus(n.focus,n.hops);else if(n.graph&&n.nodes.length&&L){a.isFocused()&&a.exitFocus();const p=n.nodes.filter(M=>L.nodes.some(G=>G.id===M));p.length&&(a.panToNodes(p),T.show(p,L))}})}No();