backpack-viewer 0.2.20 → 0.2.21

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/bin/serve.js CHANGED
@@ -188,6 +188,68 @@ if (hasDistBuild) {
188
188
  return;
189
189
  }
190
190
 
191
+ // --- Snippet routes ---
192
+ const snippetItemMatch = url.match(/^\/api\/graphs\/(.+)\/snippets\/(.+)$/);
193
+ if (snippetItemMatch && req.method === "GET") {
194
+ const graphName = decodeURIComponent(snippetItemMatch[1]);
195
+ const snippetId = decodeURIComponent(snippetItemMatch[2]);
196
+ try {
197
+ const snippet = await storage.loadSnippet(graphName, snippetId);
198
+ res.writeHead(200, { "Content-Type": "application/json" });
199
+ res.end(JSON.stringify(snippet));
200
+ } catch {
201
+ res.writeHead(404, { "Content-Type": "application/json" });
202
+ res.end(JSON.stringify({ error: "Snippet not found" }));
203
+ }
204
+ return;
205
+ }
206
+
207
+ if (snippetItemMatch && req.method === "DELETE") {
208
+ const graphName = decodeURIComponent(snippetItemMatch[1]);
209
+ const snippetId = decodeURIComponent(snippetItemMatch[2]);
210
+ try {
211
+ await storage.deleteSnippet(graphName, snippetId);
212
+ res.writeHead(200, { "Content-Type": "application/json" });
213
+ res.end(JSON.stringify({ ok: true }));
214
+ } catch (err) {
215
+ res.writeHead(400, { "Content-Type": "application/json" });
216
+ res.end(JSON.stringify({ error: err.message }));
217
+ }
218
+ return;
219
+ }
220
+
221
+ const snippetMatch = url.match(/^\/api\/graphs\/(.+)\/snippets$/);
222
+ if (snippetMatch && req.method === "GET") {
223
+ const graphName = decodeURIComponent(snippetMatch[1]);
224
+ try {
225
+ const snippets = await storage.listSnippets(graphName);
226
+ res.writeHead(200, { "Content-Type": "application/json" });
227
+ res.end(JSON.stringify(snippets));
228
+ } catch {
229
+ res.writeHead(200, { "Content-Type": "application/json" });
230
+ res.end("[]");
231
+ }
232
+ return;
233
+ }
234
+
235
+ if (snippetMatch && req.method === "POST") {
236
+ const graphName = decodeURIComponent(snippetMatch[1]);
237
+ let body = "";
238
+ req.on("data", (chunk) => { body += chunk.toString(); });
239
+ req.on("end", async () => {
240
+ try {
241
+ const { label, description, nodeIds, edgeIds } = JSON.parse(body);
242
+ const id = await storage.saveSnippet(graphName, { label, description, nodeIds, edgeIds: edgeIds ?? [] });
243
+ res.writeHead(200, { "Content-Type": "application/json" });
244
+ res.end(JSON.stringify({ ok: true, id }));
245
+ } catch (err) {
246
+ res.writeHead(400, { "Content-Type": "application/json" });
247
+ res.end(JSON.stringify({ error: err.message }));
248
+ }
249
+ });
250
+ return;
251
+ }
252
+
191
253
  if (url === "/api/ontologies") {
192
254
  try {
193
255
  const summaries = await storage.listOntologies();
package/dist/api.d.ts CHANGED
@@ -30,3 +30,15 @@ export interface DiffResult {
30
30
  edgesRemoved: number;
31
31
  }
32
32
  export declare function diffSnapshot(graphName: string, version: number): Promise<DiffResult>;
33
+ export interface SnippetSummary {
34
+ id: string;
35
+ label: string;
36
+ description: string;
37
+ nodeCount: number;
38
+ edgeCount: number;
39
+ createdAt: string;
40
+ }
41
+ export declare function listSnippets(graphName: string): Promise<SnippetSummary[]>;
42
+ export declare function saveSnippet(graphName: string, label: string, nodeIds: string[], edgeIds: string[], description?: string): Promise<string>;
43
+ export declare function loadSnippet(graphName: string, snippetId: string): Promise<any>;
44
+ export declare function deleteSnippet(graphName: string, snippetId: string): Promise<void>;
package/dist/api.js CHANGED
@@ -99,3 +99,33 @@ export async function diffSnapshot(graphName, version) {
99
99
  throw new Error("Failed to compute diff");
100
100
  return res.json();
101
101
  }
102
+ export async function listSnippets(graphName) {
103
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/snippets`);
104
+ if (!res.ok)
105
+ return [];
106
+ return res.json();
107
+ }
108
+ export async function saveSnippet(graphName, label, nodeIds, edgeIds, description) {
109
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/snippets`, {
110
+ method: "POST",
111
+ headers: { "Content-Type": "application/json" },
112
+ body: JSON.stringify({ label, description, nodeIds, edgeIds }),
113
+ });
114
+ if (!res.ok)
115
+ throw new Error("Failed to save snippet");
116
+ const data = await res.json();
117
+ return data.id;
118
+ }
119
+ export async function loadSnippet(graphName, snippetId) {
120
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/snippets/${encodeURIComponent(snippetId)}`);
121
+ if (!res.ok)
122
+ throw new Error("Snippet not found");
123
+ return res.json();
124
+ }
125
+ export async function deleteSnippet(graphName, snippetId) {
126
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/snippets/${encodeURIComponent(snippetId)}`, {
127
+ method: "DELETE",
128
+ });
129
+ if (!res.ok)
130
+ throw new Error("Failed to delete snippet");
131
+ }
@@ -0,0 +1,21 @@
1
+ (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const p of document.querySelectorAll('link[rel="modulepreload"]'))f(p);new MutationObserver(p=>{for(const b of p)if(b.type==="childList")for(const m of b.addedNodes)m.tagName==="LINK"&&m.rel==="modulepreload"&&f(m)}).observe(document,{childList:!0,subtree:!0});function a(p){const b={};return p.integrity&&(b.integrity=p.integrity),p.referrerPolicy&&(b.referrerPolicy=p.referrerPolicy),p.crossOrigin==="use-credentials"?b.credentials="include":p.crossOrigin==="anonymous"?b.credentials="omit":b.credentials="same-origin",b}function f(p){if(p.ep)return;p.ep=!0;const b=a(p);fetch(p.href,b)}})();async function Ye(){const o=await fetch("/api/ontologies");return o.ok?o.json():[]}async function Xe(o){const t=await fetch(`/api/ontologies/${encodeURIComponent(o)}`);if(!t.ok)throw new Error(`Failed to load ontology: ${o}`);return t.json()}async function Ve(o,t){if(!(await fetch(`/api/ontologies/${encodeURIComponent(o)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).ok)throw new Error(`Failed to save ontology: ${o}`)}async function St(o,t){if(!(await fetch(`/api/ontologies/${encodeURIComponent(o)}/rename`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t})})).ok)throw new Error(`Failed to rename ontology: ${o}`)}async function Lt(o){const t=await fetch(`/api/graphs/${encodeURIComponent(o)}/branches`);return t.ok?t.json():[]}async function tt(o,t,a){const f=await fetch(`/api/graphs/${encodeURIComponent(o)}/branches`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t,from:a})});if(!f.ok){const p=await f.json().catch(()=>({}));throw new Error(p.error||"Failed to create branch")}}async function nt(o,t){const a=await fetch(`/api/graphs/${encodeURIComponent(o)}/branches/switch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t})});if(!a.ok){const f=await a.json().catch(()=>({}));throw new Error(f.error||"Failed to switch branch")}}async function Nt(o,t){const a=await fetch(`/api/graphs/${encodeURIComponent(o)}/branches/${encodeURIComponent(t)}`,{method:"DELETE"});if(!a.ok){const f=await a.json().catch(()=>({}));throw new Error(f.error||"Failed to delete branch")}}async function kt(o){const t=await fetch(`/api/graphs/${encodeURIComponent(o)}/snapshots`);return t.ok?t.json():[]}async function It(o,t){const a=await fetch(`/api/graphs/${encodeURIComponent(o)}/snapshots`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:t})});if(!a.ok){const f=await a.json().catch(()=>({}));throw new Error(f.error||"Failed to create snapshot")}}async function Mt(o,t){const a=await fetch(`/api/graphs/${encodeURIComponent(o)}/rollback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({version:t})});if(!a.ok){const f=await a.json().catch(()=>({}));throw new Error(f.error||"Failed to rollback")}}async function Tt(o){const t=await fetch(`/api/graphs/${encodeURIComponent(o)}/snippets`);return t.ok?t.json():[]}async function At(o,t,a,f,p){const b=await fetch(`/api/graphs/${encodeURIComponent(o)}/snippets`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:t,description:p,nodeIds:a,edgeIds:f})});if(!b.ok)throw new Error("Failed to save snippet");return(await b.json()).id}async function Bt(o,t){const a=await fetch(`/api/graphs/${encodeURIComponent(o)}/snippets/${encodeURIComponent(t)}`);if(!a.ok)throw new Error("Snippet not found");return a.json()}async function Ft(o,t){if(!(await fetch(`/api/graphs/${encodeURIComponent(o)}/snippets/${encodeURIComponent(t)}`,{method:"DELETE"})).ok)throw new Error("Failed to delete snippet")}const ot="bp-dialog-overlay";function ht(){var t;(t=document.querySelector(`.${ot}`))==null||t.remove();const o=document.createElement("div");return o.className=ot,document.body.appendChild(o),o}function ft(o,t){const a=document.createElement("div");a.className="bp-dialog";const f=document.createElement("h4");return f.className="bp-dialog-title",f.textContent=t,a.appendChild(f),o.appendChild(a),o.addEventListener("click",p=>{p.target===o&&o.remove()}),a}function gt(o,t){const a=document.createElement("div");a.className="bp-dialog-buttons";for(const f of t){const p=document.createElement("button");p.className="bp-dialog-btn",f.accent&&p.classList.add("bp-dialog-btn-accent"),f.danger&&p.classList.add("bp-dialog-btn-danger"),p.textContent=f.label,p.addEventListener("click",f.onClick),a.appendChild(p)}o.appendChild(a)}function yt(o,t){return new Promise(a=>{var m;const f=ht(),p=ft(f,o),b=document.createElement("p");b.className="bp-dialog-message",b.textContent=t,p.appendChild(b),gt(p,[{label:"Cancel",onClick:()=>{f.remove(),a(!1)}},{label:"Confirm",accent:!0,onClick:()=>{f.remove(),a(!0)}}]),(m=p.querySelector(".bp-dialog-btn-accent"))==null||m.focus()})}function et(o,t,a){return new Promise(f=>{const p=ht(),b=ft(p,o),m=document.createElement("input");m.type="text",m.className="bp-dialog-input",m.placeholder=t??"",m.value="",b.appendChild(m);const d=()=>{const n=m.value.trim();p.remove(),f(n||null)};m.addEventListener("keydown",n=>{n.key==="Enter"&&d(),n.key==="Escape"&&(p.remove(),f(null))}),gt(b,[{label:"Cancel",onClick:()=>{p.remove(),f(null)}},{label:"OK",accent:!0,onClick:d}]),m.focus(),m.select()})}function Pt(o,t){const a=typeof t=="function"?{onSelect:t}:t,f=document.createElement("h2");f.textContent="Backpack Viewer";const p=document.createElement("input");p.type="text",p.placeholder="Filter...",p.id="filter";const b=document.createElement("ul");b.id="ontology-list";const m=document.createElement("div");m.className="sidebar-footer",m.innerHTML='<a href="mailto:support@backpackontology.com">support@backpackontology.com</a><span>Feedback & support</span><span class="sidebar-version">v0.2.21</span>';const d=document.createElement("button");d.className="sidebar-collapse-btn",d.title="Toggle sidebar (Tab)",d.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 n=!1;function X(){n=!n,o.classList.toggle("sidebar-collapsed",n),s.classList.toggle("hidden",!n)}d.addEventListener("click",X);const l=document.createElement("div");l.className="sidebar-heading-row",l.appendChild(f),l.appendChild(d),o.appendChild(l);const s=document.createElement("button");s.className="tools-pane-toggle hidden",s.title="Show sidebar (Tab)",s.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>',s.addEventListener("click",X),o.appendChild(p),o.appendChild(b),o.appendChild(m);let M=[],j="";return p.addEventListener("input",()=>{const $=p.value.toLowerCase();for(const D of M){const K=D.dataset.name??"";D.style.display=K.includes($)?"":"none"}}),{setSummaries($){b.innerHTML="",M=$.map(D=>{const K=document.createElement("li");K.className="ontology-item",K.dataset.name=D.name;const F=document.createElement("span");F.className="name",F.textContent=D.name;const q=document.createElement("span");q.className="stats",q.textContent=`${D.nodeCount} nodes, ${D.edgeCount} edges`;const z=document.createElement("span");if(z.className="sidebar-branch",z.dataset.graph=D.name,K.appendChild(F),K.appendChild(q),K.appendChild(z),a.onRename){const h=document.createElement("button");h.className="sidebar-edit-btn",h.textContent="✎",h.title="Rename";const H=a.onRename;h.addEventListener("click",U=>{U.stopPropagation();const O=document.createElement("input");O.type="text",O.className="sidebar-rename-input",O.value=D.name,F.textContent="",F.appendChild(O),h.style.display="none",O.focus(),O.select();const Y=()=>{const J=O.value.trim();J&&J!==D.name?H(D.name,J):(F.textContent=D.name,h.style.display="")};O.addEventListener("blur",Y),O.addEventListener("keydown",J=>{J.key==="Enter"&&O.blur(),J.key==="Escape"&&(O.value=D.name,O.blur())})}),K.appendChild(h)}return K.addEventListener("click",()=>a.onSelect(D.name)),b.appendChild(K),K}),j&&this.setActive(j)},setActive($){j=$;for(const D of M)D.classList.toggle("active",D.dataset.name===$)},setActiveBranch($,D,K){const F=b.querySelectorAll(`.sidebar-branch[data-graph="${$}"]`);for(const q of F){q.textContent=`/ ${D}`,q.title="Click to switch branch",q.style.cursor="pointer";const z=q.cloneNode(!0);q.replaceWith(z),z.addEventListener("click",h=>{h.stopPropagation(),S($,z,K??[])})}},setSnippets($,D){var q;const K=M.find(z=>z.dataset.name===$);if(!K||((q=K.querySelector(".sidebar-snippets"))==null||q.remove(),D.length===0))return;const F=document.createElement("div");F.className="sidebar-snippets";for(const z of D){const h=document.createElement("div");h.className="sidebar-snippet";const H=document.createElement("span");H.className="sidebar-snippet-label",H.textContent=`◆ ${z.label}`,H.title=`${z.nodeCount} nodes — click to load`;const U=document.createElement("button");U.className="sidebar-snippet-delete",U.textContent="×",U.title="Delete snippet",U.addEventListener("click",O=>{var Y;O.stopPropagation(),(Y=a.onSnippetDelete)==null||Y.call(a,$,z.id)}),h.appendChild(H),h.appendChild(U),h.addEventListener("click",O=>{var Y;O.stopPropagation(),(Y=a.onSnippetLoad)==null||Y.call(a,$,z.id)}),F.appendChild(h)}K.appendChild(F)},toggle:X,expandBtn:s};function S($,D,K){const F=o.querySelector(".branch-picker");F&&F.remove();const q=document.createElement("div");q.className="branch-picker";for(const h of K){const H=document.createElement("div");H.className="branch-picker-item",h.active&&H.classList.add("branch-picker-active");const U=document.createElement("span");if(U.textContent=h.name,H.appendChild(U),!h.active&&a.onBranchDelete){const O=document.createElement("button");O.className="branch-picker-delete",O.textContent="×",O.title=`Delete ${h.name}`,O.addEventListener("click",Y=>{Y.stopPropagation(),yt("Delete branch",`Delete branch "${h.name}"?`).then(J=>{J&&(a.onBranchDelete($,h.name),q.remove())})}),H.appendChild(O)}h.active||H.addEventListener("click",()=>{var O;(O=a.onBranchSwitch)==null||O.call(a,$,h.name),q.remove()}),q.appendChild(H)}if(a.onBranchCreate){const h=document.createElement("div");h.className="branch-picker-item branch-picker-create",h.textContent="+ New branch",h.addEventListener("click",()=>{et("New branch","Branch name").then(H=>{H&&(a.onBranchCreate($,H),q.remove())})}),q.appendChild(h)}D.after(q);const z=h=>{q.contains(h.target)||(q.remove(),document.removeEventListener("click",z))};setTimeout(()=>document.addEventListener("click",z),0)}}const Ct={clusterStrength:.08,spacing:1.5},Rt=6e3,Dt=12e3,$t=.004,vt=140,Et=350,st=.9,at=.01,it=30,Ke=50;let $e={...Ct};function He(o){o.clusterStrength!==void 0&&($e.clusterStrength=o.clusterStrength),o.spacing!==void 0&&($e.spacing=o.spacing)}function _e(){return{...$e}}function Ot(o){if(o<=30)return{...Ct};const t=Math.log2(o/30);return{clusterStrength:Math.min(.5,.08+.06*t),spacing:Math.min(15,1.5+1.2*t)}}function Wt(o,t){for(const a of Object.values(o))if(typeof a=="string")return a;return t}function ct(o,t,a){const f=new Set(t);let p=new Set(t);for(let b=0;b<a;b++){const m=new Set;for(const d of o.edges)p.has(d.sourceId)&&!f.has(d.targetId)&&m.add(d.targetId),p.has(d.targetId)&&!f.has(d.sourceId)&&m.add(d.sourceId);for(const d of m)f.add(d);if(p=m,m.size===0)break}return{nodes:o.nodes.filter(b=>f.has(b.id)),edges:o.edges.filter(b=>f.has(b.sourceId)&&f.has(b.targetId)),metadata:o.metadata}}function Je(o){const t=new Map,a=[...new Set(o.nodes.map(n=>n.type))],f=Math.sqrt(a.length)*Et*.6*Math.max(1,$e.spacing),p=new Map,b=new Map;for(const n of o.nodes)b.set(n.type,(b.get(n.type)??0)+1);const m=o.nodes.map(n=>{const X=a.indexOf(n.type),l=2*Math.PI*X/Math.max(a.length,1),s=Math.cos(l)*f,M=Math.sin(l)*f,j=p.get(n.type)??0;p.set(n.type,j+1);const S=b.get(n.type)??1,$=2*Math.PI*j/S,D=vt*.6,K={id:n.id,x:s+Math.cos($)*D,y:M+Math.sin($)*D,vx:0,vy:0,label:Wt(n.properties,n.id),type:n.type};return t.set(n.id,K),K}),d=o.edges.map(n=>({sourceId:n.sourceId,targetId:n.targetId,type:n.type}));return{nodes:m,edges:d,nodeMap:t}}function zt(o,t){const{nodes:a,edges:f,nodeMap:p}=o;for(let m=0;m<a.length;m++)for(let d=m+1;d<a.length;d++){const n=a[m],X=a[d];let l=X.x-n.x,s=X.y-n.y,M=Math.sqrt(l*l+s*s);M<it&&(M=it);const S=(n.type===X.type?Rt:Dt*$e.spacing)*t/(M*M),$=l/M*S,D=s/M*S;n.vx-=$,n.vy-=D,X.vx+=$,X.vy+=D}for(const m of f){const d=p.get(m.sourceId),n=p.get(m.targetId);if(!d||!n)continue;const X=n.x-d.x,l=n.y-d.y,s=Math.sqrt(X*X+l*l);if(s===0)continue;const M=d.type===n.type?vt*$e.spacing:Et*$e.spacing,j=$t*(s-M)*t,S=X/s*j,$=l/s*j;d.vx+=S,d.vy+=$,n.vx-=S,n.vy-=$}for(const m of a)m.vx-=m.x*at*t,m.vy-=m.y*at*t;const b=new Map;for(const m of a){const d=b.get(m.type)??{x:0,y:0,count:0};d.x+=m.x,d.y+=m.y,d.count++,b.set(m.type,d)}for(const m of b.values())m.x/=m.count,m.y/=m.count;for(const m of a){const d=b.get(m.type);m.vx+=(d.x-m.x)*$e.clusterStrength*t,m.vy+=(d.y-m.y)*$e.clusterStrength*t}for(const m of a){m.vx*=st,m.vy*=st;const d=Math.sqrt(m.vx*m.vx+m.vy*m.vy);d>Ke&&(m.vx=m.vx/d*Ke,m.vy=m.vy/d*Ke),m.x+=m.vx,m.y+=m.vy}return t*.995}const lt=["#d4a27f","#c17856","#b07a5e","#d4956b","#a67c5a","#cc9e7c","#c4866a","#cb8e6c","#b8956e","#a88a70","#d9b08c","#c4a882","#e8b898","#b5927a","#a8886e","#d1a990"],dt=new Map;function Se(o){const t=dt.get(o);if(t)return t;let a=0;for(let p=0;p<o.length;p++)a=(a<<5)-a+o.charCodeAt(p)|0;const f=lt[Math.abs(a)%lt.length];return dt.set(o,f),f}function me(o){return getComputedStyle(document.documentElement).getPropertyValue(o).trim()}const Be=20,Ht=.001,Ut={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15},jt={zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300};function Ze(o,t,a,f,p,b=100){const m=(o-a.x)*a.scale,d=(t-a.y)*a.scale;return m>=-b&&m<=f+b&&d>=-b&&d<=p+b}function qt(o,t,a,f){const p={...Ut,...(f==null?void 0:f.lod)??{}},b={...jt,...(f==null?void 0:f.navigation)??{}},m={pulseSpeed:.02,...(f==null?void 0:f.walk)??{}},d=o.querySelector("canvas"),n=d.getContext("2d"),X=window.devicePixelRatio||1;let l={x:0,y:0,scale:1},s=null,M=1,j=0,S=new Set,$=null,D=!0,K=!0,F=!0,q=!0,z=null,h=null,H=1,U=null,O=null,Y=null,J=!1,te=[],Le=0,ge=null,re=null;const ue=b.panAnimationMs;function Ne(){d.width=d.clientWidth*X,d.height=d.clientHeight*X,g()}const Q=new ResizeObserver(Ne);Q.observe(o),Ne();function u(r,v){return[r/l.scale+l.x,v/l.scale+l.y]}function C(r,v){if(!s)return null;const[T,W]=u(r,v);for(let _=s.nodes.length-1;_>=0;_--){const ee=s.nodes[_],G=T-ee.x,Z=W-ee.y;if(G*G+Z*Z<=Be*Be)return ee}return null}function g(){var Me;if(!s){n.clearRect(0,0,d.width,d.height);return}J&&te.length>0&&(Le+=m.pulseSpeed);const r=J?new Set(te):null,v=me("--canvas-edge"),T=me("--canvas-edge-highlight"),W=me("--canvas-edge-dim"),_=me("--canvas-edge-label"),ee=me("--canvas-edge-label-highlight"),G=me("--canvas-edge-label-dim"),Z=me("--canvas-arrow"),le=me("--canvas-arrow-highlight"),se=me("--canvas-node-label"),Ce=me("--canvas-node-label-dim"),xe=me("--canvas-type-badge"),ke=me("--canvas-type-badge-dim"),Ae=me("--canvas-selection-border"),be=me("--canvas-node-border");if(n.save(),n.setTransform(X,0,0,X,0,0),n.clearRect(0,0,d.clientWidth,d.clientHeight),n.save(),n.translate(-l.x*l.scale,-l.y*l.scale),n.scale(l.scale,l.scale),F&&l.scale>=p.smallNodes){const V=new Map;for(const he of s.nodes){if($!==null&&!$.has(he.id))continue;const fe=V.get(he.type)??[];fe.push(he),V.set(he.type,fe)}for(const[he,fe]of V){if(fe.length<2)continue;const De=Se(he),Pe=Be*2.5;let Te=1/0,ce=1/0,ve=-1/0,Ie=-1/0;for(const Re of fe)Re.x<Te&&(Te=Re.x),Re.y<ce&&(ce=Re.y),Re.x>ve&&(ve=Re.x),Re.y>Ie&&(Ie=Re.y);n.beginPath();const we=(ve-Te)/2+Pe,Oe=(Ie-ce)/2+Pe,We=(Te+ve)/2,Ge=(ce+Ie)/2;n.ellipse(We,Ge,we,Oe,0,0,Math.PI*2),n.fillStyle=De,n.globalAlpha=.05,n.fill(),n.strokeStyle=De,n.globalAlpha=.12,n.lineWidth=1,n.setLineDash([4,4]),n.stroke(),n.setLineDash([]),n.globalAlpha=1}}if(D)for(const V of s.edges){const he=s.nodeMap.get(V.sourceId),fe=s.nodeMap.get(V.targetId);if(!he||!fe||!Ze(he.x,he.y,l,d.clientWidth,d.clientHeight,200)&&!Ze(fe.x,fe.y,l,d.clientWidth,d.clientHeight,200))continue;const De=$===null||$.has(V.sourceId),Pe=$===null||$.has(V.targetId),Te=De&&Pe;if($!==null&&!De&&!Pe)continue;const ve=S.size>0&&(S.has(V.sourceId)||S.has(V.targetId))||$!==null&&Te,Ie=$!==null&&!Te,we=r!==null&&r.has(V.sourceId)&&r.has(V.targetId),Oe=Y?z==null?void 0:z.edges.find(ze=>ze.sourceId===V.sourceId&&ze.targetId===V.targetId||ze.targetId===V.sourceId&&ze.sourceId===V.targetId):null,We=Y&&Oe&&Y.edgeIds.has(Oe.id);if(V.sourceId===V.targetId){y(he,V.type,ve,v,T,_,ee);continue}n.beginPath(),n.moveTo(he.x,he.y),n.lineTo(fe.x,fe.y);const Ge=me("--accent")||"#d4a27f",Re=me("--canvas-walk-edge")||"#1a1a1a";if(n.strokeStyle=We?Ge:we?Re:ve?T:Ie?W:v,n.lineWidth=We||we?3:l.scale<p.hideArrows?1:ve?2.5:1.5,we&&(n.globalAlpha=.5+.5*Math.sin(Le)),n.stroke(),n.globalAlpha=1,l.scale>=p.hideArrows&&A(he.x,he.y,fe.x,fe.y,ve,Z,le),K&&l.scale>=p.hideEdgeLabels){const ze=(he.x+fe.x)/2,wt=(he.y+fe.y)/2;n.fillStyle=ve?ee:Ie?G:_,n.font="9px system-ui, sans-serif",n.textAlign="center",n.textBaseline="bottom",n.fillText(V.type,ze,wt-4)}}for(const V of s.nodes){if(!Ze(V.x,V.y,l,d.clientWidth,d.clientHeight))continue;const he=Se(V.type),fe=S.has(V.id),De=S.size>0&&s.edges.some(we=>S.has(we.sourceId)&&we.targetId===V.id||S.has(we.targetId)&&we.sourceId===V.id),Pe=$!==null&&!$.has(V.id),Te=Pe||S.size>0&&!fe&&!De,ce=l.scale<p.smallNodes?Be*.5:Be;if(r!=null&&r.has(V.id)){const we=te[te.length-1]===V.id,Oe=.5+.5*Math.sin(Le),We=me("--accent")||"#d4a27f";n.save(),n.strokeStyle=We,n.lineWidth=we?3:2,n.globalAlpha=we?.5+.5*Oe:.3+.4*Oe,n.beginPath(),n.arc(V.x,V.y,ce+(we?6:4),0,Math.PI*2),n.stroke(),n.restore()}fe&&(n.save(),n.shadowColor=he,n.shadowBlur=20,n.beginPath(),n.arc(V.x,V.y,ce+3,0,Math.PI*2),n.fillStyle=he,n.globalAlpha=.3,n.fill(),n.restore()),n.beginPath(),n.arc(V.x,V.y,ce,0,Math.PI*2),n.fillStyle=he,n.globalAlpha=Pe?.1:Te?.3:1,n.fill(),n.strokeStyle=fe?Ae:be,n.lineWidth=fe?3:1.5,n.stroke(),Y&&Y.nodeIds.has(V.id)&&!fe&&(n.save(),n.shadowColor=me("--accent")||"#d4a27f",n.shadowBlur=15,n.beginPath(),n.arc(V.x,V.y,ce+2,0,Math.PI*2),n.strokeStyle=me("--accent")||"#d4a27f",n.globalAlpha=.5,n.lineWidth=2,n.stroke(),n.restore());const ve=z==null?void 0:z.nodes.find(we=>we.id===V.id);if(((Me=ve==null?void 0:ve.properties)==null?void 0:Me._starred)===!0&&(n.fillStyle="#ffd700",n.font="10px system-ui, sans-serif",n.textAlign="left",n.textBaseline="bottom",n.fillText("★",V.x+ce-2,V.y-ce+2)),l.scale>=p.hideLabels){const we=V.label.length>24?V.label.slice(0,22)+"...":V.label;n.fillStyle=Te?Ce:se,n.font="11px system-ui, sans-serif",n.textAlign="center",n.textBaseline="top",n.fillText(we,V.x,V.y+ce+4)}l.scale>=p.hideBadges&&(n.fillStyle=Te?ke:xe,n.font="9px system-ui, sans-serif",n.textBaseline="bottom",n.fillText(V.type,V.x,V.y-ce-3)),n.globalAlpha=1}n.restore(),n.restore(),q&&s.nodes.length>1&&x()}function x(){if(!s)return;const r=140,v=100,T=8,W=d.clientWidth-r-16,_=d.clientHeight-v-16;let ee=1/0,G=1/0,Z=-1/0,le=-1/0;for(const ce of s.nodes)ce.x<ee&&(ee=ce.x),ce.y<G&&(G=ce.y),ce.x>Z&&(Z=ce.x),ce.y>le&&(le=ce.y);const se=Z-ee||1,Ce=le-G||1,xe=Math.min((r-T*2)/se,(v-T*2)/Ce),ke=W+T+(r-T*2-se*xe)/2,Ae=_+T+(v-T*2-Ce*xe)/2;n.save(),n.setTransform(X,0,0,X,0,0),n.fillStyle=me("--bg-surface")||"#1a1a1a",n.globalAlpha=.85,n.beginPath(),n.roundRect(W,_,r,v,8),n.fill(),n.strokeStyle=me("--border")||"#2a2a2a",n.globalAlpha=1,n.lineWidth=1,n.stroke(),n.globalAlpha=.15,n.strokeStyle=me("--canvas-edge")||"#555",n.lineWidth=.5;for(const ce of s.edges){const ve=s.nodeMap.get(ce.sourceId),Ie=s.nodeMap.get(ce.targetId);!ve||!Ie||ce.sourceId===ce.targetId||(n.beginPath(),n.moveTo(ke+(ve.x-ee)*xe,Ae+(ve.y-G)*xe),n.lineTo(ke+(Ie.x-ee)*xe,Ae+(Ie.y-G)*xe),n.stroke())}n.globalAlpha=.8;for(const ce of s.nodes){const ve=ke+(ce.x-ee)*xe,Ie=Ae+(ce.y-G)*xe;n.beginPath(),n.arc(ve,Ie,2,0,Math.PI*2),n.fillStyle=Se(ce.type),n.fill()}const be=l.x,Me=l.y,V=l.x+d.clientWidth/l.scale,he=l.y+d.clientHeight/l.scale,fe=ke+(be-ee)*xe,De=Ae+(Me-G)*xe,Pe=(V-be)*xe,Te=(he-Me)*xe;n.globalAlpha=.3,n.strokeStyle=me("--accent")||"#d4a27f",n.lineWidth=1.5,n.strokeRect(Math.max(W,Math.min(fe,W+r)),Math.max(_,Math.min(De,_+v)),Math.min(Pe,r),Math.min(Te,v)),n.globalAlpha=1,n.restore()}function A(r,v,T,W,_,ee,G){const Z=Math.atan2(W-v,T-r),le=T-Math.cos(Z)*Be,se=W-Math.sin(Z)*Be,Ce=8;n.beginPath(),n.moveTo(le,se),n.lineTo(le-Ce*Math.cos(Z-.4),se-Ce*Math.sin(Z-.4)),n.lineTo(le-Ce*Math.cos(Z+.4),se-Ce*Math.sin(Z+.4)),n.closePath(),n.fillStyle=_?G:ee,n.fill()}function y(r,v,T,W,_,ee,G){const Z=r.x+Be+15,le=r.y-Be-15;n.beginPath(),n.arc(Z,le,15,0,Math.PI*2),n.strokeStyle=T?_:W,n.lineWidth=T?2.5:1.5,n.stroke(),K&&(n.fillStyle=T?G:ee,n.font="9px system-ui, sans-serif",n.textAlign="center",n.fillText(v,Z,le-18))}function L(){if(!ge||!re)return;const r=performance.now()-re.time,v=Math.min(r/ue,1),T=1-Math.pow(1-v,3);l.x=re.x+(ge.x-re.x)*T,l.y=re.y+(ge.y-re.y)*T,g(),v<1?requestAnimationFrame(L):(ge=null,re=null)}let N=0;function B(){if(!J||te.length===0){N=0;return}g(),N=requestAnimationFrame(B)}function k(){if(!s||s.nodes.length===0)return;let r=1/0,v=1/0,T=-1/0,W=-1/0;for(const se of s.nodes)se.x<r&&(r=se.x),se.y<v&&(v=se.y),se.x>T&&(T=se.x),se.y>W&&(W=se.y);const _=Be*4,ee=T-r+_*2,G=W-v+_*2,Z=d.clientWidth/Math.max(ee,1),le=d.clientHeight/Math.max(G,1);l.scale=Math.min(Z,le,2),l.x=(r+T)/2-d.clientWidth/(2*l.scale),l.y=(v+W)/2-d.clientHeight/(2*l.scale),g()}function i(){if(!s||M<Ht){J&&te.length>0&&!N&&(N=requestAnimationFrame(B));return}M=zt(s,M),g(),j=requestAnimationFrame(i)}let I=!1,P=!1,ne=0,e=0;d.addEventListener("mousedown",r=>{I=!0,P=!1,ne=r.clientX,e=r.clientY}),d.addEventListener("mousemove",r=>{if(!I)return;const v=r.clientX-ne,T=r.clientY-e;(Math.abs(v)>5||Math.abs(T)>5)&&(P=!0),l.x-=v/l.scale,l.y-=T/l.scale,ne=r.clientX,e=r.clientY,g()}),d.addEventListener("mouseup",r=>{if(I=!1,P)return;const v=d.getBoundingClientRect(),T=r.clientX-v.left,W=r.clientY-v.top,_=C(T,W),ee=r.ctrlKey||r.metaKey;if(J&&h&&_&&s){const G=te.length>0?te[te.length-1]:h[0],Z=new Set([G]),le=[{id:G,path:[G]}];let se=null;for(;le.length>0;){const{id:ke,path:Ae}=le.shift();if(ke===_.id){se=Ae;break}for(const be of s.edges){let Me=null;be.sourceId===ke?Me=be.targetId:be.targetId===ke&&(Me=be.sourceId),Me&&!Z.has(Me)&&(Z.add(Me),le.push({id:Me,path:[...Ae,Me]}))}}if(!se)return;for(const ke of se.slice(1))te.includes(ke)||te.push(ke);h=[_.id];const Ce=Math.max(1,H);H=Ce;const xe=ct(z,[_.id],Ce);cancelAnimationFrame(j),s=Je(xe),M=1,S=new Set([_.id]),$=null,l={x:0,y:0,scale:1},i(),setTimeout(()=>{s&&k()},300),a==null||a({seedNodeIds:[_.id],hops:Ce,totalNodes:xe.nodes.length}),t==null||t([_.id]);return}if(_){ee?S.has(_.id)?S.delete(_.id):S.add(_.id):S.size===1&&S.has(_.id)?S.clear():(S.clear(),S.add(_.id));const G=[...S];t==null||t(G.length>0?G:null)}else S.clear(),t==null||t(null);g()}),d.addEventListener("mouseleave",()=>{I=!1}),d.addEventListener("wheel",r=>{r.preventDefault();const v=d.getBoundingClientRect(),T=r.clientX-v.left,W=r.clientY-v.top,[_,ee]=u(T,W),G=r.ctrlKey?1-r.deltaY*.01:r.deltaY>0?.9:1.1;l.scale=Math.max(b.zoomMin,Math.min(b.zoomMax,l.scale*G)),l.x=_-T/l.scale,l.y=ee-W/l.scale,g()},{passive:!1});let c=[],w=0,R=1,oe=0,ae=0,ie=!1;d.addEventListener("touchstart",r=>{r.preventDefault(),c=Array.from(r.touches),c.length===2?(w=pe(c[0],c[1]),R=l.scale):c.length===1&&(ne=c[0].clientX,e=c[0].clientY,oe=c[0].clientX,ae=c[0].clientY,ie=!1)},{passive:!1}),d.addEventListener("touchmove",r=>{r.preventDefault();const v=Array.from(r.touches);if(v.length===2&&c.length===2){const W=pe(v[0],v[1])/w;l.scale=Math.max(b.zoomMin,Math.min(b.zoomMax,R*W)),g()}else if(v.length===1){const T=v[0].clientX-ne,W=v[0].clientY-e;(Math.abs(v[0].clientX-oe)>10||Math.abs(v[0].clientY-ae)>10)&&(ie=!0),l.x-=T/l.scale,l.y-=W/l.scale,ne=v[0].clientX,e=v[0].clientY,g()}c=v},{passive:!1}),d.addEventListener("touchend",r=>{if(r.preventDefault(),ie||r.changedTouches.length!==1)return;const v=r.changedTouches[0],T=d.getBoundingClientRect(),W=v.clientX-T.left,_=v.clientY-T.top,ee=C(W,_);if(ee){S.size===1&&S.has(ee.id)?S.clear():(S.clear(),S.add(ee.id));const G=[...S];t==null||t(G.length>0?G:null)}else S.clear(),t==null||t(null);g()},{passive:!1}),d.addEventListener("gesturestart",r=>r.preventDefault()),d.addEventListener("gesturechange",r=>r.preventDefault());function pe(r,v){const T=r.clientX-v.clientX,W=r.clientY-v.clientY;return Math.sqrt(T*T+W*W)}const Ee=document.createElement("div");Ee.className="zoom-controls";const ye=document.createElement("button");ye.className="zoom-btn",ye.textContent="+",ye.title="Zoom in",ye.addEventListener("click",()=>{const r=d.clientWidth/2,v=d.clientHeight/2,[T,W]=u(r,v);l.scale=Math.min(b.zoomMax,l.scale*b.zoomFactor),l.x=T-r/l.scale,l.y=W-v/l.scale,g()});const Fe=document.createElement("button");Fe.className="zoom-btn",Fe.textContent="−",Fe.title="Zoom out",Fe.addEventListener("click",()=>{const r=d.clientWidth/2,v=d.clientHeight/2,[T,W]=u(r,v);l.scale=Math.max(b.zoomMin,l.scale/b.zoomFactor),l.x=T-r/l.scale,l.y=W-v/l.scale,g()});const Ue=document.createElement("button");return Ue.className="zoom-btn",Ue.textContent="○",Ue.title="Reset zoom",Ue.addEventListener("click",()=>{if(s){if(l={x:0,y:0,scale:1},s.nodes.length>0){let r=1/0,v=1/0,T=-1/0,W=-1/0;for(const G of s.nodes)G.x<r&&(r=G.x),G.y<v&&(v=G.y),G.x>T&&(T=G.x),G.y>W&&(W=G.y);const _=(r+T)/2,ee=(v+W)/2;l.x=_-d.clientWidth/2,l.y=ee-d.clientHeight/2}g()}}),Ee.appendChild(ye),Ee.appendChild(Ue),Ee.appendChild(Fe),o.appendChild(Ee),{loadGraph(r){if(cancelAnimationFrame(j),z=r,h=null,U=null,O=null,s=Je(r),M=1,S=new Set,$=null,l={x:0,y:0,scale:1},s.nodes.length>0){let v=1/0,T=1/0,W=-1/0,_=-1/0;for(const se of s.nodes)se.x<v&&(v=se.x),se.y<T&&(T=se.y),se.x>W&&(W=se.x),se.y>_&&(_=se.y);const ee=(v+W)/2,G=(T+_)/2,Z=d.clientWidth,le=d.clientHeight;l.x=ee-Z/2,l.y=G-le/2}i()},setFilteredNodeIds(r){$=r,g()},panToNode(r){this.panToNodes([r])},panToNodes(r){if(!s||r.length===0)return;const v=r.map(_=>s.nodeMap.get(_)).filter(Boolean);if(v.length===0)return;S=new Set(r),t==null||t(r);const T=d.clientWidth,W=d.clientHeight;if(v.length===1)re={x:l.x,y:l.y,time:performance.now()},ge={x:v[0].x-T/(2*l.scale),y:v[0].y-W/(2*l.scale)};else{let _=1/0,ee=1/0,G=-1/0,Z=-1/0;for(const be of v)be.x<_&&(_=be.x),be.y<ee&&(ee=be.y),be.x>G&&(G=be.x),be.y>Z&&(Z=be.y);const le=Be*4,se=G-_+le*2,Ce=Z-ee+le*2,xe=Math.min(T/se,W/Ce,l.scale);l.scale=xe;const ke=(_+G)/2,Ae=(ee+Z)/2;re={x:l.x,y:l.y,time:performance.now()},ge={x:ke-T/(2*l.scale),y:Ae-W/(2*l.scale)}}L()},setEdges(r){D=r,g()},setEdgeLabels(r){K=r,g()},setTypeHulls(r){F=r,g()},setMinimap(r){q=r,g()},centerView(){k()},panBy(r,v){l.x+=r/l.scale,l.y+=v/l.scale,g()},zoomBy(r){const v=d.clientWidth/2,T=d.clientHeight/2,[W,_]=u(v,T);l.scale=Math.max(b.zoomMin,Math.min(b.zoomMax,l.scale*r)),l.x=W-v/l.scale,l.y=_-T/l.scale,g()},reheat(){M=.5,cancelAnimationFrame(j),i()},exportImage(r){if(!s)return"";const v=d.width,T=d.height;if(r==="png"){const G=document.createElement("canvas");G.width=v,G.height=T;const Z=G.getContext("2d");return Z.fillStyle=me("--bg")||"#141414",Z.fillRect(0,0,v,T),Z.drawImage(d,0,0),bt(Z,v,T),G.toDataURL("image/png")}const W=d.toDataURL("image/png"),_=Math.max(16,Math.round(v/80)),ee=`<svg xmlns="http://www.w3.org/2000/svg" width="${v}" height="${T}">
2
+ <image href="${W}" width="${v}" height="${T}"/>
3
+ <text x="${v-20}" y="${T-16}" text-anchor="end" font-family="system-ui, sans-serif" font-size="${_}" fill="#ffffff" opacity="0.4">backpackontology.com</text>
4
+ </svg>`;return"data:image/svg+xml;charset=utf-8,"+encodeURIComponent(ee)},enterFocus(r,v){if(!z||!s)return;h||(U=s,O={...l}),h=r,H=v;const T=ct(z,r,v);cancelAnimationFrame(j),s=Je(T),M=1,S=new Set(r),$=null,l={x:0,y:0,scale:1},i(),setTimeout(()=>{!s||!h||k()},300),a==null||a({seedNodeIds:r,hops:v,totalNodes:T.nodes.length})},exitFocus(){!h||!U||(cancelAnimationFrame(j),s=U,l=O??{x:0,y:0,scale:1},h=null,U=null,O=null,S=new Set,$=null,g(),a==null||a(null))},isFocused(){return h!==null},getFocusInfo(){return!h||!s?null:{seedNodeIds:h,hops:H,totalNodes:s.nodes.length}},findPath(r,v){if(!s)return null;const T=new Set([r]),W=[{nodeId:r,path:[r],edges:[]}];for(;W.length>0;){const{nodeId:_,path:ee,edges:G}=W.shift();if(_===v)return{nodeIds:ee,edgeIds:G};for(const Z of s.edges){let le=null;if(Z.sourceId===_?le=Z.targetId:Z.targetId===_&&(le=Z.sourceId),le&&!T.has(le)){T.add(le);const se=z==null?void 0:z.edges.find(Ce=>Ce.sourceId===Z.sourceId&&Ce.targetId===Z.targetId||Ce.targetId===Z.sourceId&&Ce.sourceId===Z.targetId);W.push({nodeId:le,path:[...ee,le],edges:[...G,(se==null?void 0:se.id)??""]})}}}return null},setHighlightedPath(r,v){r&&v?Y={nodeIds:new Set(r),edgeIds:new Set(v)}:Y=null,g()},clearHighlightedPath(){Y=null,g()},setWalkMode(r){J=r,r?(te=h?[...h]:[...S],N||(N=requestAnimationFrame(B))):(te=[],N&&(cancelAnimationFrame(N),N=0)),g()},getWalkMode(){return J},getWalkTrail(){return[...te]},getFilteredNodeIds(){return $},removeFromWalkTrail(r){te=te.filter(v=>v!==r),g()},nodeAtScreen(r,v){return C(r,v)},getNodeIds(){if(!s)return[];if(h){const r=new Set(h),v=s.nodes.filter(W=>r.has(W.id)).map(W=>W.id),T=s.nodes.filter(W=>!r.has(W.id)).map(W=>W.id);return[...v,...T]}return s.nodes.map(r=>r.id)},destroy(){cancelAnimationFrame(j),Q.disconnect()}};function bt(r,v,T){const W=Math.max(16,Math.round(v/80));r.save(),r.font=`${W}px system-ui, sans-serif`,r.fillStyle="rgba(255, 255, 255, 0.4)",r.textAlign="right",r.textBaseline="bottom",r.fillText("backpackontology.com",v-20,T-16),r.restore()}}function je(o){for(const t of Object.values(o.properties))if(typeof t=="string")return t;return o.id}const Yt="✎";function Xt(o,t,a,f){const p=document.createElement("div");p.id="info-panel",p.className="info-panel hidden",o.appendChild(p);let b=!1,m=[],d=-1,n=!1,X=null,l=[],s=!1,M=[],j=-1;function S(){p.classList.add("hidden"),p.classList.remove("info-panel-maximized"),p.innerHTML="",b=!1,m=[],d=-1}function $(h){!X||!a||(d<m.length-1&&(m=m.slice(0,d+1)),m.push(h),d=m.length-1,n=!0,a(h),n=!1)}function D(){if(d<=0||!X)return;d--,n=!0;const h=m[d];a==null||a(h),q(h,X),n=!1}function K(){if(d>=m.length-1||!X)return;d++,n=!0;const h=m[d];a==null||a(h),q(h,X),n=!1}function F(){const h=document.createElement("div");h.className="info-panel-toolbar";const H=document.createElement("button");H.className="info-toolbar-btn",H.textContent="←",H.title="Back",H.disabled=d<=0,H.addEventListener("click",D),h.appendChild(H);const U=document.createElement("button");if(U.className="info-toolbar-btn",U.textContent="→",U.title="Forward",U.disabled=d>=m.length-1,U.addEventListener("click",K),h.appendChild(U),f&&l.length>0){const J=document.createElement("button");J.className="info-toolbar-btn info-focus-btn",J.textContent="◎",J.title="Focus on neighborhood (F)",J.disabled=s,s&&(J.style.opacity="0.3"),J.addEventListener("click",()=>{s||f(l)}),h.appendChild(J)}const O=document.createElement("button");O.className="info-toolbar-btn",O.textContent=b?"⎘":"⛶",O.title=b?"Restore":"Maximize",O.addEventListener("click",()=>{b=!b,p.classList.toggle("info-panel-maximized",b),O.textContent=b?"⎘":"⛶",O.title=b?"Restore":"Maximize"}),h.appendChild(O);const Y=document.createElement("button");return Y.className="info-toolbar-btn info-close-btn",Y.textContent="×",Y.title="Close",Y.addEventListener("click",S),h.appendChild(Y),h}function q(h,H){const U=H.nodes.find(y=>y.id===h);if(!U)return;const O=H.edges.filter(y=>y.sourceId===h||y.targetId===h);M=O.map(y=>y.sourceId===h?y.targetId:y.sourceId),j=-1,p.innerHTML="",p.classList.remove("hidden"),b&&p.classList.add("info-panel-maximized");const Y=document.createElement("div");Y.className="info-panel-header",Y.appendChild(F());const J=document.createElement("div");J.className="info-header";const te=document.createElement("span");if(te.className="info-type-badge",te.textContent=U.type,te.style.backgroundColor=Se(U.type),t){te.classList.add("info-editable");const y=document.createElement("button");y.className="info-inline-edit",y.textContent=Yt,y.addEventListener("click",L=>{L.stopPropagation();const N=document.createElement("input");N.type="text",N.className="info-edit-inline-input",N.value=U.type,te.textContent="",te.appendChild(N),N.focus(),N.select();const B=()=>{const k=N.value.trim();k&&k!==U.type?t.onChangeNodeType(h,k):(te.textContent=U.type,te.appendChild(y))};N.addEventListener("blur",B),N.addEventListener("keydown",k=>{k.key==="Enter"&&N.blur(),k.key==="Escape"&&(N.value=U.type,N.blur())})}),te.appendChild(y)}const Le=document.createElement("h3");Le.className="info-label",Le.textContent=je(U);const ge=document.createElement("span");ge.className="info-id",ge.textContent=U.id,J.appendChild(te),J.appendChild(Le),J.appendChild(ge),Y.appendChild(J),p.appendChild(Y);const re=document.createElement("div");re.className="info-panel-body";const ue=Object.keys(U.properties),Ne=qe("Properties");if(ue.length>0){const y=document.createElement("dl");y.className="info-props";for(const L of ue){const N=document.createElement("dt");N.textContent=L;const B=document.createElement("dd");if(t){const k=Qe(U.properties[L]),i=document.createElement("textarea");i.className="info-edit-input",i.value=k,i.rows=1,i.addEventListener("input",()=>rt(i)),i.addEventListener("keydown",P=>{P.key==="Enter"&&!P.shiftKey&&(P.preventDefault(),i.blur())}),i.addEventListener("blur",()=>{const P=i.value;P!==k&&t.onUpdateNode(h,{[L]:Gt(P)})}),B.appendChild(i),requestAnimationFrame(()=>rt(i));const I=document.createElement("button");I.className="info-delete-prop",I.textContent="×",I.title=`Remove ${L}`,I.addEventListener("click",()=>{const P={...U.properties};delete P[L],t.onUpdateNode(h,P)}),B.appendChild(I)}else B.appendChild(_t(U.properties[L]));y.appendChild(N),y.appendChild(B)}Ne.appendChild(y)}if(t){const y=document.createElement("button");y.className="info-add-btn",y.textContent="+ Add property",y.addEventListener("click",()=>{const L=document.createElement("div");L.className="info-add-row";const N=document.createElement("input");N.type="text",N.className="info-edit-input",N.placeholder="key";const B=document.createElement("input");B.type="text",B.className="info-edit-input",B.placeholder="value";const k=document.createElement("button");k.className="info-add-save",k.textContent="Add",k.addEventListener("click",()=>{N.value&&t.onAddProperty(h,N.value,B.value)}),L.appendChild(N),L.appendChild(B),L.appendChild(k),Ne.appendChild(L),N.focus()}),Ne.appendChild(y)}if(re.appendChild(Ne),O.length>0){const y=qe(`Connections (${O.length})`),L=document.createElement("ul");L.className="info-connections";for(const N of O){const B=N.sourceId===h,k=B?N.targetId:N.sourceId,i=H.nodes.find(R=>R.id===k),I=i?je(i):k,P=document.createElement("li");if(P.className="info-connection",a&&i&&(P.classList.add("info-connection-link"),P.addEventListener("click",R=>{R.target.closest(".info-delete-edge")||$(k)})),i){const R=document.createElement("span");R.className="info-target-dot",R.style.backgroundColor=Se(i.type),P.appendChild(R)}const ne=document.createElement("span");ne.className="info-arrow",ne.textContent=B?"→":"←";const e=document.createElement("span");e.className="info-edge-type",e.textContent=N.type;const c=document.createElement("span");c.className="info-target",c.textContent=I,P.appendChild(ne),P.appendChild(e),P.appendChild(c);const w=Object.keys(N.properties);if(w.length>0){const R=document.createElement("div");R.className="info-edge-props";for(const oe of w){const ae=document.createElement("span");ae.className="info-edge-prop",ae.textContent=`${oe}: ${Qe(N.properties[oe])}`,R.appendChild(ae)}P.appendChild(R)}if(t){const R=document.createElement("button");R.className="info-delete-edge",R.textContent="×",R.title="Remove connection",R.addEventListener("click",oe=>{oe.stopPropagation(),t.onDeleteEdge(N.id)}),P.appendChild(R)}L.appendChild(P)}y.appendChild(L),re.appendChild(y)}const Q=qe("Timestamps"),u=document.createElement("dl");u.className="info-props";const C=document.createElement("dt");C.textContent="created";const g=document.createElement("dd");g.textContent=pt(U.createdAt);const x=document.createElement("dt");x.textContent="updated";const A=document.createElement("dd");if(A.textContent=pt(U.updatedAt),u.appendChild(C),u.appendChild(g),u.appendChild(x),u.appendChild(A),Q.appendChild(u),re.appendChild(Q),t){const y=document.createElement("div");y.className="info-section info-danger";const L=document.createElement("button");L.className="info-delete-node",L.textContent="Delete node",L.addEventListener("click",()=>{t.onDeleteNode(h),S()}),y.appendChild(L),re.appendChild(y)}p.appendChild(re)}function z(h,H){const U=new Set(h),O=H.nodes.filter(Q=>U.has(Q.id));if(O.length===0)return;const Y=H.edges.filter(Q=>U.has(Q.sourceId)&&U.has(Q.targetId));p.innerHTML="",p.classList.remove("hidden"),b&&p.classList.add("info-panel-maximized"),p.appendChild(F());const J=document.createElement("div");J.className="info-header";const te=document.createElement("h3");te.className="info-label",te.textContent=`${O.length} nodes selected`,J.appendChild(te);const Le=document.createElement("div");Le.style.cssText="display:flex;flex-wrap:wrap;gap:4px;margin-top:6px";const ge=new Map;for(const Q of O)ge.set(Q.type,(ge.get(Q.type)??0)+1);for(const[Q,u]of ge){const C=document.createElement("span");C.className="info-type-badge",C.style.backgroundColor=Se(Q),C.textContent=u>1?`${Q} (${u})`:Q,Le.appendChild(C)}J.appendChild(Le),p.appendChild(J);const re=qe("Selected Nodes"),ue=document.createElement("ul");ue.className="info-connections";for(const Q of O){const u=document.createElement("li");u.className="info-connection",a&&(u.classList.add("info-connection-link"),u.addEventListener("click",()=>{$(Q.id)}));const C=document.createElement("span");C.className="info-target-dot",C.style.backgroundColor=Se(Q.type);const g=document.createElement("span");g.className="info-target",g.textContent=je(Q);const x=document.createElement("span");x.className="info-edge-type",x.textContent=Q.type,u.appendChild(C),u.appendChild(g),u.appendChild(x),ue.appendChild(u)}re.appendChild(ue),p.appendChild(re);const Ne=qe(Y.length>0?`Connections Between Selected (${Y.length})`:"Connections Between Selected");if(Y.length===0){const Q=document.createElement("p");Q.style.cssText="font-size:12px;color:var(--text-dim)",Q.textContent="No direct connections between selected nodes",Ne.appendChild(Q)}else{const Q=document.createElement("ul");Q.className="info-connections";for(const u of Y){const C=H.nodes.find(P=>P.id===u.sourceId),g=H.nodes.find(P=>P.id===u.targetId),x=C?je(C):u.sourceId,A=g?je(g):u.targetId,y=document.createElement("li");if(y.className="info-connection",C){const P=document.createElement("span");P.className="info-target-dot",P.style.backgroundColor=Se(C.type),y.appendChild(P)}const L=document.createElement("span");L.className="info-target",L.textContent=x;const N=document.createElement("span");N.className="info-arrow",N.textContent="→";const B=document.createElement("span");B.className="info-edge-type",B.textContent=u.type;const k=document.createElement("span");if(k.className="info-arrow",k.textContent="→",y.appendChild(L),y.appendChild(N),y.appendChild(B),y.appendChild(k),g){const P=document.createElement("span");P.className="info-target-dot",P.style.backgroundColor=Se(g.type),y.appendChild(P)}const i=document.createElement("span");i.className="info-target",i.textContent=A,y.appendChild(i);const I=Object.keys(u.properties);if(I.length>0){const P=document.createElement("div");P.className="info-edge-props";for(const ne of I){const e=document.createElement("span");e.className="info-edge-prop",e.textContent=`${ne}: ${Qe(u.properties[ne])}`,P.appendChild(e)}y.appendChild(P)}Q.appendChild(y)}Ne.appendChild(Q)}p.appendChild(Ne)}return{show(h,H){if(X=H,l=h,h.length===1&&!n){const U=h[0];m[d]!==U&&(d<m.length-1&&(m=m.slice(0,d+1)),m.push(U),d=m.length-1)}h.length===1?q(h[0],H):h.length>1&&z(h,H)},hide:S,goBack:D,goForward:K,cycleConnection(h){if(M.length===0)return null;j===-1?j=h===1?0:M.length-1:(j+=h,j>=M.length&&(j=0),j<0&&(j=M.length-1));const H=p.querySelectorAll(".info-connection");return H.forEach((U,O)=>{U.classList.toggle("info-connection-active",O===j)}),j>=0&&H[j]&&H[j].scrollIntoView({block:"nearest"}),M[j]??null},setFocusDisabled(h){s=h;const H=p.querySelector(".info-focus-btn");H&&(H.disabled=h,H.style.opacity=h?"0.3":"")},get visible(){return!p.classList.contains("hidden")}}}function qe(o){const t=document.createElement("div");t.className="info-section";const a=document.createElement("h4");return a.className="info-section-title",a.textContent=o,t.appendChild(a),t}function _t(o){if(Array.isArray(o)){const a=document.createElement("div");a.className="info-array";for(const f of o){const p=document.createElement("span");p.className="info-tag",p.textContent=String(f),a.appendChild(p)}return a}if(o!==null&&typeof o=="object"){const a=document.createElement("pre");return a.className="info-json",a.textContent=JSON.stringify(o,null,2),a}const t=document.createElement("span");return t.className="info-value",t.textContent=String(o??""),t}function Qe(o){return Array.isArray(o)?o.map(String).join(", "):o!==null&&typeof o=="object"?JSON.stringify(o):String(o??"")}function Gt(o){const t=o.trim();if(t==="true")return!0;if(t==="false")return!1;if(t!==""&&!isNaN(Number(t)))return Number(t);if(t.startsWith("[")&&t.endsWith("]")||t.startsWith("{")&&t.endsWith("}"))try{return JSON.parse(t)}catch{return o}return o}function rt(o){o.style.height="auto",o.style.height=o.scrollHeight+"px"}function pt(o){try{return new Date(o).toLocaleString()}catch{return o}}function xt(o){for(const t of Object.values(o.properties))if(typeof t=="string")return t;return o.id}function ut(o,t){const a=t.toLowerCase();if(xt(o).toLowerCase().includes(a)||o.type.toLowerCase().includes(a))return!0;for(const f of Object.values(o.properties))if(typeof f=="string"&&f.toLowerCase().includes(a))return!0;return!1}function Vt(o,t){const a=(t==null?void 0:t.maxResults)??8,f=(t==null?void 0:t.debounceMs)??150;let p=null,b=null,m=null,d=null;const n=document.createElement("div");n.className="search-overlay hidden";const X=document.createElement("div");X.className="search-input-wrap";const l=document.createElement("input");l.className="search-input",l.type="text",l.placeholder="Search nodes...",l.setAttribute("autocomplete","off"),l.setAttribute("spellcheck","false");const s=document.createElement("kbd");s.className="search-kbd",s.textContent="/",X.appendChild(l),X.appendChild(s);const M=document.createElement("ul");M.className="search-results hidden",n.appendChild(X),n.appendChild(M),o.appendChild(n);function j(){if(!p)return null;const F=l.value.trim();if(F.length===0)return null;const q=new Set;for(const z of p.nodes)ut(z,F)&&q.add(z.id);return q}function S(){const F=j();b==null||b(F),$()}function $(){M.innerHTML="",D=-1;const F=l.value.trim();if(!p||F.length===0){M.classList.add("hidden");return}const q=[];for(const z of p.nodes)if(ut(z,F)&&(q.push(z),q.length>=a))break;if(q.length===0){M.classList.add("hidden");return}for(const z of q){const h=document.createElement("li");h.className="search-result-item";const H=document.createElement("span");H.className="search-result-dot",H.style.backgroundColor=Se(z.type);const U=document.createElement("span");U.className="search-result-label";const O=xt(z);U.textContent=O.length>36?O.slice(0,34)+"...":O;const Y=document.createElement("span");Y.className="search-result-type",Y.textContent=z.type,h.appendChild(H),h.appendChild(U),h.appendChild(Y),h.addEventListener("click",()=>{m==null||m(z.id),l.value="",M.classList.add("hidden"),S()}),M.appendChild(h)}M.classList.remove("hidden")}l.addEventListener("input",()=>{d&&clearTimeout(d),d=setTimeout(S,f)});let D=-1;function K(){const F=M.querySelectorAll(".search-result-item");F.forEach((q,z)=>{q.classList.toggle("search-result-active",z===D)}),D>=0&&F[D]&&F[D].scrollIntoView({block:"nearest"})}return l.addEventListener("keydown",F=>{const q=M.querySelectorAll(".search-result-item");F.key==="ArrowDown"?(F.preventDefault(),q.length>0&&(D=Math.min(D+1,q.length-1),K())):F.key==="ArrowUp"?(F.preventDefault(),q.length>0&&(D=Math.max(D-1,0),K())):F.key==="Enter"?(F.preventDefault(),D>=0&&q[D]?q[D].click():q.length>0&&q[0].click(),l.blur()):F.key==="Escape"&&(l.value="",l.blur(),M.classList.add("hidden"),D=-1,S())}),document.addEventListener("click",F=>{n.contains(F.target)||M.classList.add("hidden")}),l.addEventListener("focus",()=>s.classList.add("hidden")),l.addEventListener("blur",()=>{l.value.length===0&&s.classList.remove("hidden")}),{setLearningGraphData(F){p=F,l.value="",M.classList.add("hidden"),p&&p.nodes.length>0?n.classList.remove("hidden"):n.classList.add("hidden")},onFilterChange(F){b=F},onNodeSelect(F){m=F},clear(){l.value="",M.classList.add("hidden"),b==null||b(null)},focus(){l.focus()}}}function Kt(o,t){let a=null,f=null,p=!0,b=null,m=!0,d=!0,n=!0,X="types",l="",s="",M=[],j=[];const S={types:new Set,nodeIds:new Set};function $(){if(!a)return[];const u=new Set;for(const C of a.nodes)S.types.has(C.type)&&u.add(C.id);for(const C of S.nodeIds)u.add(C);return[...u]}function D(u){if(S.nodeIds.has(u))return!0;const C=a==null?void 0:a.nodes.find(g=>g.id===u);return C?S.types.has(C.type):!1}function K(){return S.types.size===0&&S.nodeIds.size===0}function F(){const u=$();t.onFocusChange(u.length>0?u:null)}const q=document.createElement("button");q.className="tools-pane-toggle hidden",q.title="Graph Inspector",q.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 z=document.createElement("div");z.className="tools-pane-content hidden",o.appendChild(q),o.appendChild(z),q.addEventListener("click",()=>{var u;p=!p,z.classList.toggle("hidden",p),q.classList.toggle("active",!p),p||(u=t.onOpen)==null||u.call(t)});function h(){if(z.innerHTML="",!f)return;const u=document.createElement("div");u.className="tools-pane-summary",u.innerHTML=`<span>${f.nodeCount} nodes</span><span class="tools-pane-sep">&middot;</span><span>${f.edgeCount} edges</span><span class="tools-pane-sep">&middot;</span><span>${f.types.length} types</span>`,z.appendChild(u);const C=document.createElement("div");C.className="tools-pane-tabs";const g=[{id:"types",label:"Types"},{id:"insights",label:"Insights"},{id:"controls",label:"Controls"}];for(const A of g){const y=document.createElement("button");y.className="tools-pane-tab",X===A.id&&y.classList.add("tools-pane-tab-active"),y.textContent=A.label,y.addEventListener("click",()=>{X=A.id,h()}),C.appendChild(y)}z.appendChild(C),K()||O(),j.length>0&&U(),X==="types"&&f.types.length>5?z.appendChild(J("Filter types...",l,A=>{l=A,H()})):X==="insights"&&f.orphans.length+f.singletons.length+f.emptyNodes.length>5&&z.appendChild(J("Filter issues...",s,y=>{s=y,H()}));const x=document.createElement("div");x.className="tools-pane-tab-content",z.appendChild(x),H()}function H(){const u=z.querySelector(".tools-pane-tab-content");u&&(u.innerHTML="",X==="types"?te(u):X==="insights"?Le(u):X==="controls"&&ge(u))}function U(){z.appendChild(ue(`Walk Trail (${j.length})`,u=>{for(let g=0;g<j.length;g++){const x=j[g],A=g===j.length-1;if(x.edgeType){const I=document.createElement("div");I.className="walk-trail-edge",I.textContent=`↓ ${x.edgeType}`,u.appendChild(I)}const y=document.createElement("div");y.className="tools-pane-row tools-pane-clickable",A&&(y.style.fontWeight="600");const L=document.createElement("span");L.className="tools-pane-count",L.style.minWidth="18px",L.textContent=`${g+1}`;const N=document.createElement("span");N.className="tools-pane-dot",N.style.backgroundColor=Se(x.type);const B=document.createElement("span");B.className="tools-pane-name",B.textContent=x.label;const k=document.createElement("span");k.className="tools-pane-count",k.textContent=x.type;const i=document.createElement("button");i.className="tools-pane-edit",i.style.opacity="1",i.textContent="×",i.title="Remove from trail",i.addEventListener("click",I=>{var P;I.stopPropagation(),(P=t.onWalkTrailRemove)==null||P.call(t,x.id)}),y.appendChild(L),y.appendChild(N),y.appendChild(B),y.appendChild(k),y.appendChild(i),y.addEventListener("click",()=>{t.onNavigateToNode(x.id)}),u.appendChild(y)}const C=document.createElement("div");if(C.className="tools-pane-export-row",t.onWalkIsolate){const g=document.createElement("button");g.className="tools-pane-export-btn",g.textContent="Isolate (I)",g.addEventListener("click",()=>t.onWalkIsolate()),C.appendChild(g)}if(t.onWalkSaveSnippet&&j.length>=2){const g=document.createElement("button");g.className="tools-pane-export-btn",g.textContent="Save snippet",g.addEventListener("click",()=>{et("Save snippet","Name for this snippet").then(x=>{x&&t.onWalkSaveSnippet(x)})}),C.appendChild(g)}u.appendChild(C)}))}function O(){if(!f||!a)return;const u=$();z.appendChild(ue("Focused",C=>{for(const y of S.types){const L=f.types.find(P=>P.name===y);if(!L)continue;const N=document.createElement("div");N.className="tools-pane-row tools-pane-clickable";const B=document.createElement("span");B.className="tools-pane-dot",B.style.backgroundColor=Se(L.name);const k=document.createElement("span");k.className="tools-pane-name",k.textContent=L.name;const i=document.createElement("span");i.className="tools-pane-count",i.textContent=`${L.count} nodes`;const I=document.createElement("button");I.className="tools-pane-edit tools-pane-focus-active",I.style.opacity="1",I.textContent="×",I.title=`Remove ${L.name} from focus`,N.appendChild(B),N.appendChild(k),N.appendChild(i),N.appendChild(I),I.addEventListener("click",P=>{P.stopPropagation(),S.types.delete(L.name),F(),h()}),C.appendChild(N)}for(const y of S.nodeIds){const L=a.nodes.find(ne=>ne.id===y);if(!L)continue;const N=mt(L.properties)??L.id,B=document.createElement("div");B.className="tools-pane-row tools-pane-clickable";const k=document.createElement("span");k.className="tools-pane-dot",k.style.backgroundColor=Se(L.type);const i=document.createElement("span");i.className="tools-pane-name",i.textContent=N;const I=document.createElement("span");I.className="tools-pane-count",I.textContent=L.type;const P=document.createElement("button");P.className="tools-pane-edit tools-pane-focus-active",P.style.opacity="1",P.textContent="×",P.title=`Remove ${N} from focus`,B.appendChild(k),B.appendChild(i),B.appendChild(I),B.appendChild(P),B.addEventListener("click",ne=>{ne.target.closest(".tools-pane-edit")||t.onNavigateToNode(y)}),P.addEventListener("click",ne=>{ne.stopPropagation(),S.nodeIds.delete(y),F(),h()}),C.appendChild(B)}const g=document.createElement("div");g.className="tools-pane-row tools-pane-clickable tools-pane-focus-clear";const x=document.createElement("span");x.className="tools-pane-name",x.style.color="var(--accent)",x.textContent=`${u.length} total`;const A=document.createElement("span");A.className="tools-pane-badge",A.textContent="clear all",g.appendChild(x),g.appendChild(A),g.addEventListener("click",()=>{S.types.clear(),S.nodeIds.clear(),F(),h()}),C.appendChild(g)}))}function Y(u){const C=document.createElement("div");C.className="tools-pane-row tools-pane-clickable",b===u.name&&C.classList.add("active");const g=document.createElement("span");g.className="tools-pane-dot",g.style.backgroundColor=Se(u.name);const x=document.createElement("span");x.className="tools-pane-name",x.textContent=u.name;const A=document.createElement("span");A.className="tools-pane-count",A.textContent=String(u.count);const y=document.createElement("button");y.className="tools-pane-edit tools-pane-focus-toggle",S.types.has(u.name)&&y.classList.add("tools-pane-focus-active"),y.textContent="◎",y.title=S.types.has(u.name)?`Remove ${u.name} from focus`:`Add ${u.name} to focus`;const L=document.createElement("button");return L.className="tools-pane-edit",L.textContent="✎",L.title=`Rename all ${u.name} nodes`,C.appendChild(g),C.appendChild(x),C.appendChild(A),C.appendChild(y),C.appendChild(L),C.addEventListener("click",N=>{N.target.closest(".tools-pane-edit")||(b===u.name?(b=null,t.onFilterByType(null)):(b=u.name,t.onFilterByType(u.name)),h())}),y.addEventListener("click",N=>{N.stopPropagation(),S.types.has(u.name)?S.types.delete(u.name):S.types.add(u.name),F(),h()}),L.addEventListener("click",N=>{N.stopPropagation(),Ne(C,u.name,B=>{B&&B!==u.name&&t.onRenameNodeType(u.name,B)})}),C}function J(u,C,g){const x=document.createElement("input");return x.type="text",x.className="tools-pane-search",x.placeholder=u,x.value=C,x.addEventListener("input",()=>g(x.value)),x}function te(u){if(!f)return;const C=l.toLowerCase();if(f.types.length){const x=f.types.filter(A=>!S.types.has(A.name)).filter(A=>!C||A.name.toLowerCase().includes(C));x.length>0&&u.appendChild(ue("Node Types",A=>{for(const y of x)A.appendChild(Y(y))}))}const g=f.edgeTypes.filter(x=>!C||x.name.toLowerCase().includes(C));g.length&&u.appendChild(ue("Edge Types",x=>{for(const A of g){const y=document.createElement("div");y.className="tools-pane-row tools-pane-clickable";const L=document.createElement("span");L.className="tools-pane-name",L.textContent=A.name;const N=document.createElement("span");N.className="tools-pane-count",N.textContent=String(A.count);const B=document.createElement("button");B.className="tools-pane-edit",B.textContent="✎",B.title=`Rename all ${A.name} edges`,y.appendChild(L),y.appendChild(N),y.appendChild(B),B.addEventListener("click",k=>{k.stopPropagation(),Ne(y,A.name,i=>{i&&i!==A.name&&t.onRenameEdgeType(A.name,i)})}),x.appendChild(y)}}))}function Le(u){if(!f)return;const C=s.toLowerCase(),g=f.mostConnected.filter(k=>!C||k.label.toLowerCase().includes(C)||k.type.toLowerCase().includes(C));g.length&&u.appendChild(ue("Most Connected",k=>{for(const i of g){const I=document.createElement("div");I.className="tools-pane-row tools-pane-clickable";const P=document.createElement("span");P.className="tools-pane-dot",P.style.backgroundColor=Se(i.type);const ne=document.createElement("span");ne.className="tools-pane-name",ne.textContent=i.label;const e=document.createElement("span");e.className="tools-pane-count",e.textContent=`${i.connections}`;const c=document.createElement("button");c.className="tools-pane-edit tools-pane-focus-toggle",D(i.id)&&c.classList.add("tools-pane-focus-active"),c.textContent="◎",c.title=D(i.id)?`Remove ${i.label} from focus`:`Add ${i.label} to focus`,I.appendChild(P),I.appendChild(ne),I.appendChild(e),I.appendChild(c),I.addEventListener("click",w=>{w.target.closest(".tools-pane-edit")||t.onNavigateToNode(i.id)}),c.addEventListener("click",w=>{w.stopPropagation(),S.nodeIds.has(i.id)?S.nodeIds.delete(i.id):S.nodeIds.add(i.id),F(),h()}),k.appendChild(I)}}));const x=f.orphans.filter(k=>!C||k.label.toLowerCase().includes(C)||k.type.toLowerCase().includes(C)),A=f.singletons.filter(k=>!C||k.name.toLowerCase().includes(C)),y=f.emptyNodes.filter(k=>!C||k.label.toLowerCase().includes(C)||k.type.toLowerCase().includes(C)),L=x.length>0,N=A.length>0,B=y.length>0;if(!L&&!N&&!B){const k=document.createElement("div");k.className="tools-pane-empty-msg",k.textContent="No issues found",u.appendChild(k);return}L&&u.appendChild(ue("Orphans",k=>{for(const i of x.slice(0,5)){const I=document.createElement("div");I.className="tools-pane-row tools-pane-clickable tools-pane-issue";const P=document.createElement("span");P.className="tools-pane-dot",P.style.backgroundColor=Se(i.type);const ne=document.createElement("span");ne.className="tools-pane-name",ne.textContent=i.label;const e=document.createElement("span");e.className="tools-pane-badge",e.textContent="orphan";const c=document.createElement("button");c.className="tools-pane-edit tools-pane-focus-toggle",D(i.id)&&c.classList.add("tools-pane-focus-active"),c.textContent="◎",c.title=D(i.id)?`Remove ${i.label} from focus`:`Add ${i.label} to focus`,I.appendChild(P),I.appendChild(ne),I.appendChild(e),I.appendChild(c),I.addEventListener("click",w=>{w.target.closest(".tools-pane-edit")||t.onNavigateToNode(i.id)}),c.addEventListener("click",w=>{w.stopPropagation(),S.nodeIds.has(i.id)?S.nodeIds.delete(i.id):S.nodeIds.add(i.id),F(),h()}),k.appendChild(I)}if(x.length>5){const i=document.createElement("div");i.className="tools-pane-more",i.textContent=`+ ${x.length-5} more orphans`,k.appendChild(i)}})),N&&u.appendChild(ue("Singletons",k=>{for(const i of A.slice(0,5)){const I=document.createElement("div");I.className="tools-pane-row tools-pane-issue";const P=document.createElement("span");P.className="tools-pane-dot",P.style.backgroundColor=Se(i.name);const ne=document.createElement("span");ne.className="tools-pane-name",ne.textContent=i.name;const e=document.createElement("span");e.className="tools-pane-badge",e.textContent="1 node",I.appendChild(P),I.appendChild(ne),I.appendChild(e),k.appendChild(I)}})),B&&u.appendChild(ue("Empty Nodes",k=>{for(const i of y.slice(0,5)){const I=document.createElement("div");I.className="tools-pane-row tools-pane-issue";const P=document.createElement("span");P.className="tools-pane-dot",P.style.backgroundColor=Se(i.type);const ne=document.createElement("span");ne.className="tools-pane-name",ne.textContent=i.label;const e=document.createElement("span");e.className="tools-pane-badge",e.textContent="empty",I.appendChild(P),I.appendChild(ne),I.appendChild(e),k.appendChild(I)}if(f.emptyNodes.length>5){const i=document.createElement("div");i.className="tools-pane-more",i.textContent=`+ ${f.emptyNodes.length-5} more empty nodes`,k.appendChild(i)}}))}function ge(u){u.appendChild(ue("Display",C=>{const g=document.createElement("div");g.className="tools-pane-row tools-pane-clickable";const x=document.createElement("input");x.type="checkbox",x.checked=m,x.className="tools-pane-checkbox";const A=document.createElement("span");A.className="tools-pane-name",A.textContent="Edge labels",g.appendChild(x),g.appendChild(A),g.addEventListener("click",I=>{I.target!==x&&(x.checked=!x.checked),m=x.checked,t.onToggleEdgeLabels(m)}),C.appendChild(g);const y=document.createElement("div");y.className="tools-pane-row tools-pane-clickable";const L=document.createElement("input");L.type="checkbox",L.checked=d,L.className="tools-pane-checkbox";const N=document.createElement("span");N.className="tools-pane-name",N.textContent="Type regions",y.appendChild(L),y.appendChild(N),y.addEventListener("click",I=>{I.target!==L&&(L.checked=!L.checked),d=L.checked,t.onToggleTypeHulls(d)}),C.appendChild(y);const B=document.createElement("div");B.className="tools-pane-row tools-pane-clickable";const k=document.createElement("input");k.type="checkbox",k.checked=n,k.className="tools-pane-checkbox";const i=document.createElement("span");i.className="tools-pane-name",i.textContent="Minimap",B.appendChild(k),B.appendChild(i),B.addEventListener("click",I=>{I.target!==k&&(k.checked=!k.checked),n=k.checked,t.onToggleMinimap(n)}),C.appendChild(B)})),u.appendChild(ue("Layout",C=>{C.appendChild(re("Clustering",0,1,.02,.08,g=>{t.onLayoutChange("clusterStrength",g)})),C.appendChild(re("Spacing",.5,20,.5,1.5,g=>{t.onLayoutChange("spacing",g)})),C.appendChild(re("Pan speed",20,200,10,60,g=>{t.onPanSpeedChange(g)}))})),u.appendChild(ue("Export",C=>{const g=document.createElement("div");g.className="tools-pane-export-row";const x=document.createElement("button");x.className="tools-pane-export-btn",x.textContent="Export PNG",x.addEventListener("click",()=>t.onExport("png"));const A=document.createElement("button");A.className="tools-pane-export-btn",A.textContent="Export SVG",A.addEventListener("click",()=>t.onExport("svg")),g.appendChild(x),g.appendChild(A),C.appendChild(g)})),(t.onSnapshot||t.onRollback)&&u.appendChild(ue("Versions",C=>{const g=document.createElement("div");g.className="tools-pane-export-row";const x=document.createElement("button");if(x.className="tools-pane-export-btn",x.textContent="Save snapshot",x.addEventListener("click",()=>{et("Save snapshot","Label (optional)").then(A=>{var y;(y=t.onSnapshot)==null||y.call(t,A||void 0)})}),g.appendChild(x),C.appendChild(g),M.length>0)for(const A of M){const y=document.createElement("div");y.className="tools-pane-row";const L=document.createElement("span");L.className="tools-pane-name";const N=Jt(A.timestamp);L.textContent=A.label?`#${A.version} ${A.label}`:`#${A.version}`,L.title=`${N} — ${A.nodeCount} nodes, ${A.edgeCount} edges`;const B=document.createElement("span");B.className="tools-pane-count",B.textContent=N;const k=document.createElement("button");k.className="tools-pane-edit",k.style.opacity="1",k.textContent="↩",k.title="Restore this snapshot",k.addEventListener("click",()=>{yt("Restore snapshot",`Restore snapshot #${A.version}? Current state will be lost unless you save a snapshot first.`).then(i=>{var I;i&&((I=t.onRollback)==null||I.call(t,A.version))})}),y.appendChild(L),y.appendChild(B),y.appendChild(k),C.appendChild(y)}else{const A=document.createElement("div");A.className="tools-pane-empty-msg",A.textContent="No snapshots yet",C.appendChild(A)}}))}function re(u,C,g,x,A,y){const L=document.createElement("div");L.className="tools-pane-slider-row";const N=document.createElement("span");N.className="tools-pane-slider-label",N.textContent=u;const B=document.createElement("input");B.type="range",B.className="tools-pane-slider",B.min=String(C),B.max=String(g),B.step=String(x),B.value=String(A);const k=document.createElement("span");return k.className="tools-pane-slider-value",k.textContent=String(A),B.addEventListener("input",()=>{const i=parseFloat(B.value);k.textContent=i%1===0?String(i):i.toFixed(2),y(i)}),L.appendChild(N),L.appendChild(B),L.appendChild(k),L}function ue(u,C){const g=document.createElement("div");g.className="tools-pane-section";const x=document.createElement("div");return x.className="tools-pane-heading",x.textContent=u,g.appendChild(x),C(g),g}function Ne(u,C,g){const x=document.createElement("input");x.className="tools-pane-inline-input",x.value=C,x.type="text";const A=u.innerHTML;u.innerHTML="",u.classList.add("tools-pane-editing"),u.appendChild(x),x.focus(),x.select();function y(){const L=x.value.trim();u.classList.remove("tools-pane-editing"),L&&L!==C?g(L):u.innerHTML=A}x.addEventListener("keydown",L=>{L.key==="Enter"&&(L.preventDefault(),y()),L.key==="Escape"&&(u.innerHTML=A,u.classList.remove("tools-pane-editing"))}),x.addEventListener("blur",y)}function Q(u){const C=new Map,g=new Map,x=new Map,A=new Set;for(const i of u.nodes)C.set(i.type,(C.get(i.type)??0)+1);for(const i of u.edges)g.set(i.type,(g.get(i.type)??0)+1),x.set(i.sourceId,(x.get(i.sourceId)??0)+1),x.set(i.targetId,(x.get(i.targetId)??0)+1),A.add(i.sourceId),A.add(i.targetId);const y=i=>mt(i.properties)??i.id,L=u.nodes.filter(i=>!A.has(i.id)).map(i=>({id:i.id,label:y(i),type:i.type})),N=[...C.entries()].filter(([,i])=>i===1).map(([i])=>({name:i})),B=u.nodes.filter(i=>Object.keys(i.properties).length===0).map(i=>({id:i.id,label:i.id,type:i.type})),k=u.nodes.map(i=>({id:i.id,label:y(i),type:i.type,connections:x.get(i.id)??0})).filter(i=>i.connections>0).sort((i,I)=>I.connections-i.connections).slice(0,5);return{nodeCount:u.nodes.length,edgeCount:u.edges.length,types:[...C.entries()].sort((i,I)=>I[1]-i[1]).map(([i,I])=>({name:i,count:I})),edgeTypes:[...g.entries()].sort((i,I)=>I[1]-i[1]).map(([i,I])=>({name:i,count:I})),orphans:L,singletons:N,emptyNodes:B,mostConnected:k}}return{collapse(){p=!0,z.classList.add("hidden"),q.classList.remove("active")},addToFocusSet(u){for(const C of u)S.nodeIds.add(C);F(),h()},clearFocusSet(){S.types.clear(),S.nodeIds.clear(),F(),h()},setData(u){a=u,b=null,S.types.clear(),S.nodeIds.clear(),a&&a.nodes.length>0?(f=Q(a),q.classList.remove("hidden"),h()):(f=null,q.classList.add("hidden"),z.classList.add("hidden"))},setSnapshots(u){M=u,X==="controls"&&H()},setWalkTrail(u){j=u,h()}}}function Jt(o){const t=Date.now()-new Date(o).getTime(),a=Math.floor(t/6e4);if(a<1)return"just now";if(a<60)return`${a}m ago`;const f=Math.floor(a/60);return f<24?`${f}h ago`:`${Math.floor(f/24)}d ago`}function mt(o){for(const t of Object.values(o))if(typeof t=="string")return t;return null}function Zt(o,t){const a=t.split("+"),f=a.pop(),p=a.map(n=>n.toLowerCase()),b=p.includes("ctrl")||p.includes("cmd")||p.includes("meta"),m=p.includes("shift"),d=p.includes("alt");return b!==(o.ctrlKey||o.metaKey)||!b&&(o.ctrlKey||o.metaKey)||m&&!o.shiftKey||d!==o.altKey?!1:f.toLowerCase()==="escape"?o.key==="Escape":f.toLowerCase()==="tab"?o.key==="Tab":p.length>0?o.key.toLowerCase()===f.toLowerCase():o.key===f}function Qt(){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 en=[{key:"Click",description:"Select node"},{key:"Ctrl+Click",description:"Multi-select nodes"},{key:"Drag",description:"Pan canvas"},{key:"Scroll",description:"Zoom in/out"}],tn=["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 nn(o){return o.split("+").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join("+")}function on(o,t){const a=Qt(),f=document.createElement("div");f.className="shortcuts-overlay hidden";const p=document.createElement("div");p.className="shortcuts-modal";const b=document.createElement("h3");b.className="shortcuts-title",b.textContent="Keyboard Shortcuts";const m=document.createElement("div");m.className="shortcuts-list";for(const s of tn){const M=t[s];if(!M)continue;const j=document.createElement("div");j.className="shortcuts-row";const S=document.createElement("div");S.className="shortcuts-keys";const $=document.createElement("kbd");$.textContent=nn(M),S.appendChild($);const D=document.createElement("span");D.className="shortcuts-desc",D.textContent=a[s],j.appendChild(S),j.appendChild(D),m.appendChild(j)}for(const s of en){const M=document.createElement("div");M.className="shortcuts-row";const j=document.createElement("div");j.className="shortcuts-keys";const S=document.createElement("kbd");S.textContent=s.key,j.appendChild(S);const $=document.createElement("span");$.className="shortcuts-desc",$.textContent=s.description,M.appendChild(j),M.appendChild($),m.appendChild(M)}const d=document.createElement("button");d.className="shortcuts-close",d.textContent="×",p.appendChild(d),p.appendChild(b),p.appendChild(m),f.appendChild(p),o.appendChild(f);function n(){f.classList.remove("hidden")}function X(){f.classList.add("hidden")}function l(){f.classList.toggle("hidden")}return d.addEventListener("click",X),f.addEventListener("click",s=>{s.target===f&&X()}),{show:n,hide:X,toggle:l}}function sn(o){const t=document.createElement("div");return t.className="empty-state",t.innerHTML=`
5
+ <div class="empty-state-content">
6
+ <div class="empty-state-icon">
7
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
8
+ <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"/>
9
+ <polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
10
+ <line x1="12" y1="22.08" x2="12" y2="12"/>
11
+ </svg>
12
+ </div>
13
+ <h2 class="empty-state-title">No learning graphs yet</h2>
14
+ <p class="empty-state-desc">Connect Backpack to Claude, then start a conversation. Claude will build your first learning graph automatically.</p>
15
+ <div class="empty-state-setup">
16
+ <div class="empty-state-label">Add Backpack to Claude Code:</div>
17
+ <code class="empty-state-code">claude mcp add backpack-local -s user -- npx backpack-ontology@latest</code>
18
+ </div>
19
+ <p class="empty-state-hint">Press <kbd>?</kbd> for keyboard shortcuts</p>
20
+ </div>
21
+ `,o.appendChild(t),{show(){t.classList.remove("hidden")},hide(){t.classList.add("hidden")}}}const an=30;function cn(){let o=[],t=[];return{push(a){o.push(JSON.stringify(a)),o.length>an&&o.shift(),t=[]},undo(a){return o.length===0?null:(t.push(JSON.stringify(a)),JSON.parse(o.pop()))},redo(a){return t.length===0?null:(o.push(JSON.stringify(a)),JSON.parse(t.pop()))},canUndo(){return o.length>0},canRedo(){return t.length>0},clear(){o=[],t=[]}}}function ln(o,t){let a=null;function f(m,d,n,X,l){p(),a=document.createElement("div"),a.className="context-menu",a.style.left=`${X}px`,a.style.top=`${l}px`;const s=[{label:n?"★ Unstar":"☆ Star",action:()=>t.onStar(m),premium:!1},{label:"◎ Focus on node",action:()=>t.onFocusNode(m),premium:!1},{label:"⑂ Explore in branch",action:()=>t.onExploreInBranch(m),premium:!1},{label:"⎘ Copy ID",action:()=>t.onCopyId(m),premium:!1}];t.onExpand&&s.push({label:"⊕ Expand node",action:()=>t.onExpand(m),premium:!0}),t.onExplainPath&&s.push({label:"↔ Explain path to…",action:()=>t.onExplainPath(m),premium:!0}),t.onEnrich&&s.push({label:"≡ Enrich from web",action:()=>t.onEnrich(m),premium:!0});let M=!1;for(const S of s){if(!M&&S.premium){const D=document.createElement("div");D.className="context-menu-separator",a.appendChild(D),M=!0}const $=document.createElement("div");$.className="context-menu-item",$.textContent=S.label,$.addEventListener("click",()=>{S.action(),p()}),a.appendChild($)}o.appendChild(a);const j=a.getBoundingClientRect();j.right>window.innerWidth&&(a.style.left=`${X-j.width}px`),j.bottom>window.innerHeight&&(a.style.top=`${l-j.height}px`),setTimeout(()=>document.addEventListener("click",p),0),document.addEventListener("keydown",b)}function p(){a&&(a.remove(),a=null),document.removeEventListener("click",p),document.removeEventListener("keydown",b)}function b(m){m.key==="Escape"&&p()}return{show:f,hide:p}}const dn={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"},rn={edges:!0,edgeLabels:!0,typeHulls:!0,minimap:!0,theme:"system"},pn={spacing:1.5,clustering:.08},un={panSpeed:60,panFastMultiplier:3,zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300},mn={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15},hn={pulseSpeed:.02},fn={maxSearchResults:8,maxQualityItems:5,maxMostConnected:5,searchDebounceMs:150},gn={keybindings:dn,display:rn,layout:pn,navigation:un,lod:mn,walk:hn,limits:fn};let de="",E=null;async function yn(){const o=document.getElementById("canvas-container"),t={...gn};try{const e=await fetch("/api/config");if(e.ok){const c=await e.json();Object.assign(t.keybindings,c.keybindings??{}),Object.assign(t.display,c.display??{}),Object.assign(t.layout,c.layout??{}),Object.assign(t.navigation,c.navigation??{}),Object.assign(t.lod,c.lod??{}),Object.assign(t.limits,c.limits??{})}}catch{}const a=t.keybindings,f=window.matchMedia("(prefers-color-scheme: dark)"),p=t.display.theme==="system"?f.matches?"dark":"light":t.display.theme,m=localStorage.getItem("backpack-theme")??p;document.documentElement.setAttribute("data-theme",m);const d=document.createElement("button");d.className="theme-toggle",d.textContent=m==="light"?"☾":"☼",d.title="Toggle light/dark mode",d.addEventListener("click",()=>{const c=document.documentElement.getAttribute("data-theme")==="light"?"dark":"light";document.documentElement.setAttribute("data-theme",c),localStorage.setItem("backpack-theme",c),d.textContent=c==="light"?"☾":"☼"}),o.appendChild(d);const n=cn();async function X(){if(!de||!E)return;E.metadata.updatedAt=new Date().toISOString(),await Ve(de,E),s.loadGraph(E),O.setLearningGraphData(E),Y.setData(E);const e=await Ye();Q.setSummaries(e)}async function l(e){E=e,await Ve(de,E),s.loadGraph(E),O.setLearningGraphData(E),Y.setData(E);const c=await Ye();Q.setSummaries(c)}let s;const M=Xt(o,{onUpdateNode(e,c){if(!E)return;n.push(E);const w=E.nodes.find(R=>R.id===e);w&&(w.properties={...w.properties,...c},w.updatedAt=new Date().toISOString(),X().then(()=>M.show([e],E)))},onChangeNodeType(e,c){if(!E)return;n.push(E);const w=E.nodes.find(R=>R.id===e);w&&(w.type=c,w.updatedAt=new Date().toISOString(),X().then(()=>M.show([e],E)))},onDeleteNode(e){E&&(n.push(E),E.nodes=E.nodes.filter(c=>c.id!==e),E.edges=E.edges.filter(c=>c.sourceId!==e&&c.targetId!==e),X())},onDeleteEdge(e){var w;if(!E)return;n.push(E);const c=(w=E.edges.find(R=>R.id===e))==null?void 0:w.sourceId;E.edges=E.edges.filter(R=>R.id!==e),X().then(()=>{c&&E&&M.show([c],E)})},onAddProperty(e,c,w){if(!E)return;n.push(E);const R=E.nodes.find(oe=>oe.id===e);R&&(R.properties[c]=w,R.updatedAt=new Date().toISOString(),X().then(()=>M.show([e],E)))}},e=>{s.panToNode(e)},e=>{Y.addToFocusSet(e)}),j=window.matchMedia("(max-width: 768px)");let S=[],$=t.display.edges,D=t.navigation.panSpeed,K=-1,F=null;function q(e){F&&F.remove(),F=document.createElement("div"),F.className="focus-indicator";const c=document.createElement("span");c.className="focus-indicator-label",c.textContent=`Focused: ${e.totalNodes} nodes`;const w=document.createElement("span");w.className="focus-indicator-hops",w.textContent=`${e.hops}`;const R=document.createElement("button");R.className="focus-indicator-btn",R.textContent="−",R.title="Fewer hops",R.disabled=e.hops===0,R.addEventListener("click",()=>{s.enterFocus(e.seedNodeIds,Math.max(0,e.hops-1))});const oe=document.createElement("button");oe.className="focus-indicator-btn",oe.textContent="+",oe.title="More hops",oe.disabled=!1,oe.addEventListener("click",()=>{s.enterFocus(e.seedNodeIds,e.hops+1)});const ae=document.createElement("button");ae.className="focus-indicator-btn focus-indicator-exit",ae.textContent="×",ae.title="Exit focus (Esc)",ae.addEventListener("click",()=>Y.clearFocusSet());const ie=document.createElement("button");ie.className="walk-indicator",s.getWalkMode()&&ie.classList.add("active"),ie.textContent="Walk",ie.title="Toggle walk mode (W) — click nodes to traverse",ie.addEventListener("click",()=>{s.setWalkMode(!s.getWalkMode()),ie.classList.toggle("active",s.getWalkMode())}),F.appendChild(c),F.appendChild(R),F.appendChild(w),F.appendChild(oe),F.appendChild(ie),F.appendChild(ae)}function z(){F&&(F.remove(),F=null)}const h=document.createElement("div");h.className="path-bar hidden",o.appendChild(h);function H(e){if(h.innerHTML="",!E)return;for(let w=0;w<e.nodeIds.length;w++){const R=e.nodeIds[w],oe=E.nodes.find(pe=>pe.id===R);if(!oe)continue;const ae=Object.values(oe.properties).find(pe=>typeof pe=="string")??oe.id;if(w>0){const pe=e.edgeIds[w-1],Ee=E.edges.find(Fe=>Fe.id===pe),ye=document.createElement("span");ye.className="path-bar-edge",ye.textContent=Ee?`→ ${Ee.type} →`:"→",h.appendChild(ye)}const ie=document.createElement("span");ie.className="path-bar-node",ie.textContent=ae,ie.addEventListener("click",()=>s.panToNode(R)),h.appendChild(ie)}const c=document.createElement("button");c.className="path-bar-close",c.textContent="×",c.addEventListener("click",U),h.appendChild(c),h.classList.remove("hidden")}function U(){h.classList.add("hidden"),h.innerHTML="",s.clearHighlightedPath()}s=qt(o,e=>{if(S=e??[],!s.getWalkMode())if(e&&e.length===2){const c=s.findPath(e[0],e[1]);c&&c.nodeIds.length>0?(s.setHighlightedPath(c.nodeIds,c.edgeIds),H(c)):U()}else U();e&&e.length>0&&E?(M.show(e,E),j.matches&&Y.collapse(),N(de,e)):(M.hide(),de&&N(de))},e=>{if(e){q(e);const c=o.querySelector(".canvas-top-left");c&&F&&c.appendChild(F),N(de,e.seedNodeIds),M.setFocusDisabled(e.hops===0),u()}else z(),M.setFocusDisabled(!1),de&&N(de),u()},{lod:t.lod,navigation:t.navigation,walk:t.walk});const O=Vt(o,{maxResults:t.limits.maxSearchResults,debounceMs:t.limits.searchDebounceMs}),Y=Kt(o,{onFilterByType(e){if(E)if(e===null)s.setFilteredNodeIds(null);else{const c=new Set(((E==null?void 0:E.nodes)??[]).filter(w=>w.type===e).map(w=>w.id));s.setFilteredNodeIds(c)}},onNavigateToNode(e){s.panToNode(e),E&&M.show([e],E)},onWalkTrailRemove(e){s.removeFromWalkTrail(e),u()},onWalkIsolate(){if(!E)return;const e=s.getWalkTrail();e.length!==0&&s.enterFocus(e,0)},async onWalkSaveSnippet(e){if(!de||!E)return;const c=s.getWalkTrail();if(c.length<2)return;const w=new Set(c),R=E.edges.filter(oe=>w.has(oe.sourceId)&&w.has(oe.targetId)).map(oe=>oe.id);await At(de,e,c,R),await x(de)},onFocusChange(e){e&&e.length>0?s.enterFocus(e,0):s.isFocused()&&s.exitFocus()},onRenameNodeType(e,c){if(E){n.push(E);for(const w of E.nodes)w.type===e&&(w.type=c,w.updatedAt=new Date().toISOString());X()}},onRenameEdgeType(e,c){if(E){n.push(E);for(const w of E.edges)w.type===e&&(w.type=c);X()}},onToggleEdgeLabels(e){s.setEdgeLabels(e)},onToggleTypeHulls(e){s.setTypeHulls(e)},onToggleMinimap(e){s.setMinimap(e)},onLayoutChange(e,c){He({[e]:c}),s.reheat()},onPanSpeedChange(e){D=e},onExport(e){const c=s.exportImage(e);if(!c)return;const w=document.createElement("a");w.download=`${de||"graph"}.${e}`,w.href=c,w.click()},onSnapshot:async e=>{de&&(await It(de,e),await g(de))},onRollback:async e=>{de&&(await Mt(de,e),E=await Xe(de),s.loadGraph(E),O.setLearningGraphData(E),Y.setData(E),await g(de))},onOpen(){j.matches&&M.hide()}}),J=document.createElement("div");J.className="canvas-top-bar";const te=document.createElement("div");te.className="canvas-top-left";const Le=document.createElement("div");Le.className="canvas-top-center";const ge=document.createElement("div");ge.className="canvas-top-right";const re=o.querySelector(".tools-pane-toggle");re&&te.appendChild(re);const ue=o.querySelector(".search-overlay");ue&&Le.appendChild(ue);const Ne=o.querySelector(".zoom-controls");Ne&&ge.appendChild(Ne),ge.appendChild(d),J.appendChild(te),J.appendChild(Le),J.appendChild(ge),o.appendChild(J),O.onFilterChange(e=>{s.setFilteredNodeIds(e)}),O.onNodeSelect(e=>{s.isFocused()&&Y.clearFocusSet(),s.panToNode(e),E&&M.show([e],E)});const Q=Pt(document.getElementById("sidebar"),{onSelect:e=>k(e),onRename:async(e,c)=>{await St(e,c),de===e&&(de=c);const w=await Ye();Q.setSummaries(w),Q.setActive(de),de===c&&(E=await Xe(c),s.loadGraph(E),O.setLearningGraphData(E),Y.setData(E))},onBranchSwitch:async(e,c)=>{await nt(e,c),await C(e),E=await Xe(e),s.loadGraph(E),O.setLearningGraphData(E),Y.setData(E),await g(e)},onBranchCreate:async(e,c)=>{await tt(e,c),await C(e)},onBranchDelete:async(e,c)=>{await Nt(e,c),await C(e)},onSnippetLoad:async(e,c)=>{var R;const w=await Bt(e,c);((R=w==null?void 0:w.nodeIds)==null?void 0:R.length)>0&&s.enterFocus(w.nodeIds,0)},onSnippetDelete:async(e,c)=>{await Ft(e,c),await x(e)}});function u(){const e=s.getWalkTrail();if(!E||e.length===0){Y.setWalkTrail([]),U();return}const c=[],w=e.map((R,oe)=>{const ae=E.nodes.find(pe=>pe.id===R);let ie;if(oe>0){const pe=e[oe-1],Ee=E.edges.find(ye=>ye.sourceId===pe&&ye.targetId===R||ye.targetId===pe&&ye.sourceId===R);ie=Ee==null?void 0:Ee.type,Ee&&c.push(Ee.id)}return{id:R,label:ae?Object.values(ae.properties).find(pe=>typeof pe=="string")??ae.id:R,type:(ae==null?void 0:ae.type)??"?",edgeType:ie}});Y.setWalkTrail(w),e.length>=2?(s.setHighlightedPath(e,c),H({nodeIds:e,edgeIds:c})):U()}async function C(e){const c=await Lt(e),w=c.find(R=>R.active);w&&Q.setActiveBranch(e,w.name,c)}async function g(e){const c=await kt(e);Y.setSnapshots(c)}async function x(e){const c=await Tt(e);Q.setSnippets(e,c)}te.insertBefore(Q.expandBtn,te.firstChild);const A=on(o,a),y=sn(o),L=ln(o,{onStar(e){if(!E)return;const c=E.nodes.find(R=>R.id===e);if(!c)return;const w=c.properties._starred===!0;c.properties._starred=!w,Ve(de,E),s.loadGraph(E)},onFocusNode(e){Y.addToFocusSet([e])},onExploreInBranch(e){if(de){const c=`explore-${e.slice(0,8)}`;tt(de,c).then(()=>{nt(de,c).then(()=>{s.enterFocus([e],1)})})}},onCopyId(e){navigator.clipboard.writeText(e)}});o.addEventListener("contextmenu",e=>{e.preventDefault();const c=o.querySelector("canvas");if(!c||!E)return;const w=c.getBoundingClientRect(),R=e.clientX-w.left,oe=e.clientY-w.top,ae=s.nodeAtScreen(R,oe);if(!ae)return;const ie=E.nodes.find(ye=>ye.id===ae.id);if(!ie)return;const pe=Object.values(ie.properties).find(ye=>typeof ye=="string")??ie.id,Ee=ie.properties._starred===!0;L.show(ie.id,pe,Ee,e.clientX-w.left,e.clientY-w.top)}),t.display.edges||s.setEdges(!1),t.display.edgeLabels||s.setEdgeLabels(!1),t.display.typeHulls||s.setTypeHulls(!1),t.display.minimap||s.setMinimap(!1);function N(e,c){const w=[];c!=null&&c.length&&w.push("node="+c.map(encodeURIComponent).join(","));const R=s.getFocusInfo();R&&(w.push("focus="+R.seedNodeIds.map(encodeURIComponent).join(",")),w.push("hops="+R.hops));const oe="#"+encodeURIComponent(e)+(w.length?"?"+w.join("&"):"");history.replaceState(null,"",oe)}function B(){const e=window.location.hash.slice(1);if(!e)return{graph:null,nodes:[],focus:[],hops:1};const[c,w]=e.split("?"),R=c?decodeURIComponent(c):null;let oe=[],ae=[],ie=1;if(w){const pe=new URLSearchParams(w),Ee=pe.get("node");Ee&&(oe=Ee.split(",").map(decodeURIComponent));const ye=pe.get("focus");ye&&(ae=ye.split(",").map(decodeURIComponent));const Fe=pe.get("hops");Fe&&(ie=Math.max(0,parseInt(Fe,10)||1))}return{graph:R,nodes:oe,focus:ae,hops:ie}}async function k(e,c,w,R){de=e,Q.setActive(e),M.hide(),z(),O.clear(),n.clear(),E=await Xe(e);const oe=Ot(E.nodes.length);if(He({spacing:Math.max(t.layout.spacing,oe.spacing),clusterStrength:Math.max(t.layout.clustering,oe.clusterStrength)}),s.loadGraph(E),O.setLearningGraphData(E),Y.setData(E),y.hide(),N(e),await C(e),await g(e),await x(e),w!=null&&w.length&&E){const ae=w.filter(ie=>E.nodes.some(pe=>pe.id===ie));if(ae.length){setTimeout(()=>{s.enterFocus(ae,R??1)},500);return}}if(c!=null&&c.length&&E){const ae=c.filter(ie=>E.nodes.some(pe=>pe.id===ie));ae.length&&setTimeout(()=>{s.panToNodes(ae),E&&M.show(ae,E),N(e,ae)},500)}}const i=await Ye();Q.setSummaries(i);const I=B(),P=I.graph&&i.some(e=>e.name===I.graph)?I.graph:i.length>0?i[0].name:null;P?await k(P,I.nodes.length?I.nodes:void 0,I.focus.length?I.focus:void 0,I.hops):y.show();const ne={search(){O.focus()},searchAlt(){O.focus()},undo(){if(E){const e=n.undo(E);e&&l(e)}},redo(){if(E){const e=n.redo(E);e&&l(e)}},focus(){s.isFocused()?Y.clearFocusSet():S.length>0&&Y.addToFocusSet(S)},hopsDecrease(){const e=s.getFocusInfo();e&&e.hops>0&&s.enterFocus(e.seedNodeIds,e.hops-1)},hopsIncrease(){const e=s.getFocusInfo();e&&s.enterFocus(e.seedNodeIds,e.hops+1)},nextNode(){const e=s.getNodeIds();e.length>0&&(K=(K+1)%e.length,s.panToNode(e[K]),E&&M.show([e[K]],E))},prevNode(){const e=s.getNodeIds();e.length>0&&(K=K<=0?e.length-1:K-1,s.panToNode(e[K]),E&&M.show([e[K]],E))},nextConnection(){const e=M.cycleConnection(1);e&&s.panToNode(e)},prevConnection(){const e=M.cycleConnection(-1);e&&s.panToNode(e)},historyBack(){M.goBack()},historyForward(){M.goForward()},center(){s.centerView()},toggleEdges(){$=!$,s.setEdges($)},panLeft(){s.panBy(-D,0)},panDown(){s.panBy(0,D)},panUp(){s.panBy(0,-D)},panRight(){s.panBy(D,0)},panFastLeft(){s.panBy(-D*t.navigation.panFastMultiplier,0)},zoomOut(){s.zoomBy(1/t.navigation.zoomFactor)},zoomIn(){s.zoomBy(t.navigation.zoomFactor)},panFastRight(){s.panBy(D*t.navigation.panFastMultiplier,0)},spacingDecrease(){const e=_e();He({spacing:Math.max(.5,e.spacing-.5)}),s.reheat()},spacingIncrease(){const e=_e();He({spacing:Math.min(20,e.spacing+.5)}),s.reheat()},clusteringDecrease(){const e=_e();He({clusterStrength:Math.max(0,e.clusterStrength-.03)}),s.reheat()},clusteringIncrease(){const e=_e();He({clusterStrength:Math.min(1,e.clusterStrength+.03)}),s.reheat()},help(){A.toggle()},toggleSidebar(){Q.toggle()},walkIsolate(){if(!E)return;const e=s.getWalkTrail();e.length!==0&&s.enterFocus(e,0)},walkMode(){!s.isFocused()&&S.length>0&&Y.addToFocusSet(S),s.setWalkMode(!s.getWalkMode());const e=o.querySelector(".walk-indicator");e&&e.classList.toggle("active",s.getWalkMode()),u()},escape(){s.isFocused()?Y.clearFocusSet():A.hide()}};document.addEventListener("keydown",e=>{var c;if(!(e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement)){for(const[w,R]of Object.entries(a))if(Zt(e,R)){(w==="search"||w==="searchAlt"||w==="undo"||w==="redo"||w==="toggleSidebar")&&e.preventDefault(),(c=ne[w])==null||c.call(ne);return}}}),window.addEventListener("hashchange",()=>{const e=B();if(e.graph&&e.graph!==de)k(e.graph,e.nodes.length?e.nodes:void 0,e.focus.length?e.focus:void 0,e.hops);else if(e.graph&&e.focus.length&&E)s.enterFocus(e.focus,e.hops);else if(e.graph&&e.nodes.length&&E){s.isFocused()&&s.exitFocus();const c=e.nodes.filter(w=>E.nodes.some(R=>R.id===w));c.length&&(s.panToNodes(c),M.show(c,E))}})}yn();
@@ -0,0 +1 @@
1
+ *{margin:0;padding:0;box-sizing:border-box}:root{--bg: #141414;--bg-surface: #1a1a1a;--bg-hover: #222222;--bg-active: #2a2a2a;--bg-elevated: #1e1e1e;--bg-inset: #111111;--border: #2a2a2a;--text: #d4d4d4;--text-strong: #e5e5e5;--text-muted: #737373;--text-dim: #525252;--accent: #d4a27f;--accent-hover: #e8b898;--badge-text: #141414;--glass-bg: rgba(20, 20, 20, .85);--glass-border: rgba(255, 255, 255, .08);--chip-bg: rgba(42, 42, 42, .7);--chip-bg-active: rgba(42, 42, 42, .9);--chip-bg-hover: rgba(50, 50, 50, .9);--chip-border-active: rgba(255, 255, 255, .06);--shadow: rgba(0, 0, 0, .6);--shadow-strong: rgba(0, 0, 0, .5);--canvas-edge: rgba(255, 255, 255, .08);--canvas-edge-highlight: rgba(212, 162, 127, .5);--canvas-edge-dim: rgba(255, 255, 255, .03);--canvas-edge-label: rgba(255, 255, 255, .2);--canvas-edge-label-highlight: rgba(212, 162, 127, .7);--canvas-edge-label-dim: rgba(255, 255, 255, .05);--canvas-arrow: rgba(255, 255, 255, .12);--canvas-arrow-highlight: rgba(212, 162, 127, .5);--canvas-node-label: #a3a3a3;--canvas-node-label-dim: rgba(212, 212, 212, .2);--canvas-type-badge: rgba(115, 115, 115, .5);--canvas-type-badge-dim: rgba(115, 115, 115, .15);--canvas-selection-border: #d4d4d4;--canvas-node-border: rgba(255, 255, 255, .15);--canvas-walk-edge: #e8d5c4}[data-theme=light]{--bg: #f5f5f4;--bg-surface: #fafaf9;--bg-hover: #f0efee;--bg-active: #e7e5e4;--bg-elevated: #f0efee;--bg-inset: #e7e5e4;--border: #d6d3d1;--text: #292524;--text-strong: #1c1917;--text-muted: #78716c;--text-dim: #a8a29e;--accent: #c17856;--accent-hover: #b07a5e;--badge-text: #fafaf9;--glass-bg: rgba(250, 250, 249, .85);--glass-border: rgba(0, 0, 0, .08);--chip-bg: rgba(214, 211, 209, .5);--chip-bg-active: rgba(214, 211, 209, .8);--chip-bg-hover: rgba(200, 197, 195, .8);--chip-border-active: rgba(0, 0, 0, .08);--shadow: rgba(0, 0, 0, .1);--shadow-strong: rgba(0, 0, 0, .15);--canvas-edge: rgba(0, 0, 0, .1);--canvas-edge-highlight: rgba(193, 120, 86, .6);--canvas-edge-dim: rgba(0, 0, 0, .03);--canvas-edge-label: rgba(0, 0, 0, .25);--canvas-edge-label-highlight: rgba(193, 120, 86, .8);--canvas-edge-label-dim: rgba(0, 0, 0, .06);--canvas-arrow: rgba(0, 0, 0, .15);--canvas-arrow-highlight: rgba(193, 120, 86, .6);--canvas-node-label: #57534e;--canvas-node-label-dim: rgba(87, 83, 78, .2);--canvas-type-badge: rgba(87, 83, 78, .5);--canvas-type-badge-dim: rgba(87, 83, 78, .15);--canvas-selection-border: #292524;--canvas-node-border: rgba(0, 0, 0, .1);--canvas-walk-edge: #1a1a1a}body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);overflow:hidden}#app{display:flex;height:100vh;width:100vw}#sidebar{width:280px;min-width:280px;background:var(--bg-surface);border-right:1px solid var(--border);display:flex;flex-direction:column;padding:16px;overflow-y:auto}#sidebar.sidebar-collapsed{width:0;min-width:0;padding:0;overflow:hidden;border-right:none}.sidebar-heading-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.sidebar-heading-row h2{margin-bottom:0}.sidebar-collapse-btn{background:none;border:1px solid var(--border);border-radius:6px;color:var(--text-dim);cursor:pointer;padding:4px 6px;line-height:1;transition:color .15s,border-color .15s}.sidebar-collapse-btn:hover{color:var(--text);border-color:var(--text-muted)}#sidebar h2{font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:14px}#sidebar input{width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font-size:13px;outline:none;margin-bottom:12px}#sidebar input:focus{border-color:var(--accent)}#sidebar input::placeholder{color:var(--text-dim)}#ontology-list{list-style:none;display:flex;flex-direction:column;gap:2px}.ontology-item{padding:10px 12px;border-radius:6px;cursor:pointer;transition:background .15s}.ontology-item:hover{background:var(--bg-hover)}.ontology-item.active{background:var(--bg-active)}.ontology-item .name{display:block;font-size:13px;font-weight:500;color:var(--text)}.ontology-item .stats{display:block;font-size:11px;color:var(--text-dim);margin-top:2px}.sidebar-edit-btn{position:absolute;right:8px;top:10px;background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;opacity:0;transition:opacity .1s}.ontology-item{position:relative}.ontology-item:hover .sidebar-edit-btn{opacity:.7}.sidebar-edit-btn:hover{opacity:1!important;color:var(--text)}.sidebar-rename-input{background:transparent;border:none;border-bottom:1px solid var(--accent);color:var(--text);font:inherit;font-size:13px;font-weight:500;outline:none;width:100%;padding:0}.sidebar-branch{font-size:10px;color:var(--accent);opacity:.7;display:block;margin-top:2px}.sidebar-branch:hover{opacity:1}.branch-picker{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:4px;margin-top:4px;box-shadow:0 4px 16px var(--shadow);z-index:50}.branch-picker-item{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;font-size:12px;color:var(--text);border-radius:4px;cursor:pointer}.branch-picker-item:hover{background:var(--bg-hover)}.branch-picker-active{color:var(--accent);font-weight:600;cursor:default}.branch-picker-active:hover{background:none}.branch-picker-delete{background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:14px;padding:0 4px}.sidebar-snippets{margin-top:4px;padding-left:8px}.sidebar-snippet{display:flex;align-items:center;justify-content:space-between;padding:2px 4px;border-radius:4px;cursor:pointer}.sidebar-snippet:hover{background:var(--bg-hover)}.sidebar-snippet-label{font-size:10px;color:var(--text-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-snippet-delete{background:none;border:none;color:var(--text-dim);font-size:12px;cursor:pointer;padding:0 2px;opacity:0}.sidebar-snippet:hover .sidebar-snippet-delete{opacity:1}.branch-picker-delete:hover{color:var(--danger, #e55)}.branch-picker-create{color:var(--accent);font-size:11px;border-top:1px solid var(--border);margin-top:4px;padding-top:8px}.sidebar-footer{margin-top:auto;padding-top:16px;border-top:1px solid var(--border);text-align:center}.sidebar-footer a{display:block;font-size:12px;font-weight:500;color:var(--accent);text-decoration:none;margin-bottom:4px}.sidebar-footer a:hover{color:var(--accent-hover)}.sidebar-footer span{display:block;font-size:10px;color:var(--text-dim)}.sidebar-version{font-size:9px;color:var(--text-dim);opacity:.5;margin-top:4px}.canvas-top-bar{position:absolute;top:16px;left:16px;right:16px;z-index:30;display:flex;justify-content:space-between;align-items:flex-start;pointer-events:none}.canvas-top-left,.canvas-top-center,.canvas-top-right{pointer-events:auto;display:flex;align-items:center;gap:4px}.canvas-top-left{flex-shrink:0;min-width:var(--tools-width, 264px)}.canvas-top-center{flex:1;justify-content:center}.focus-indicator{display:flex;align-items:center;gap:2px;background:var(--bg-surface);border:1px solid rgba(212,162,127,.4);border-radius:8px;padding:4px 6px 4px 10px;box-shadow:0 2px 8px var(--shadow)}.focus-indicator-label{font-size:11px;color:var(--accent);font-weight:500;white-space:nowrap;margin-right:4px}.focus-indicator-hops{font-size:11px;color:var(--text-muted);font-family:monospace;min-width:12px;text-align:center}.focus-indicator-btn{background:none;border:none;color:var(--text-muted);font-size:14px;cursor:pointer;padding:2px 4px;line-height:1;border-radius:4px;transition:color .15s,background .15s}.focus-indicator-btn:hover:not(:disabled){color:var(--text);background:var(--bg-hover)}.focus-indicator-btn:disabled{color:var(--text-dim);cursor:default;opacity:.3}.focus-indicator-exit{font-size:16px;margin-left:2px}.focus-indicator-exit:hover{color:#ef4444!important}.info-focus-btn{font-size:14px}.theme-toggle{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;color:var(--text-muted);font-size:18px;cursor:pointer;padding:6px 10px;line-height:1;transition:color .15s,border-color .15s,background .15s;box-shadow:0 2px 8px var(--shadow)}.theme-toggle:hover{color:var(--text);border-color:var(--text-muted);background:var(--bg-hover)}.zoom-controls{display:flex;gap:4px}.zoom-btn{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;color:var(--text-muted);font-size:18px;cursor:pointer;padding:6px 10px;line-height:1;transition:color .15s,border-color .15s,background .15s;box-shadow:0 2px 8px var(--shadow)}.zoom-btn:hover{color:var(--text);border-color:var(--text-muted);background:var(--bg-hover)}#canvas-container{flex:1;position:relative;overflow:hidden;touch-action:none}#graph-canvas{position:absolute;top:0;left:0;touch-action:none;width:100%;height:100%;cursor:grab}#graph-canvas:active{cursor:grabbing}.search-overlay{position:relative;display:flex;flex-direction:column;align-items:center;gap:8px;max-height:calc(100vh - 48px);pointer-events:none}.search-overlay>*{pointer-events:auto}.search-overlay.hidden{display:none}.search-input-wrap{position:relative;display:flex;align-items:center;gap:6px;width:380px;max-width:calc(100vw - 340px)}.search-input{flex:1;min-width:0;padding:10px 36px 10px 16px;border:1px solid var(--glass-border);border-radius:10px;background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);color:var(--text);font-size:14px;outline:none;transition:border-color .15s,box-shadow .15s}.search-input:focus{border-color:#d4a27f66;box-shadow:0 0 0 3px #d4a27f1a}.search-input::placeholder{color:var(--text-dim)}.search-kbd{position:absolute;right:10px;top:50%;transform:translateY(-50%);padding:2px 7px;border:1px solid var(--border);border-radius:4px;background:var(--bg-surface);color:var(--text-dim);font-size:11px;font-family:monospace;pointer-events:none}.search-kbd.hidden{display:none}.search-results{list-style:none;width:380px;max-width:calc(100vw - 340px);background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border);border-radius:10px;overflow:hidden;box-shadow:0 8px 32px var(--shadow-strong)}.search-results.hidden{display:none}.search-result-item{display:flex;align-items:center;gap:8px;padding:8px 14px;cursor:pointer;transition:background .1s}.search-result-item:hover,.search-result-active{background:var(--bg-hover)}.search-result-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.search-result-label{font-size:13px;color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.search-result-type{font-size:11px;color:var(--text-dim);flex-shrink:0}.chip-toggle{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:1px solid var(--glass-border);border-radius:10px;background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);color:var(--text-dim);cursor:pointer;transition:border-color .15s,color .15s;pointer-events:auto}.chip-toggle:hover{border-color:#d4a27f4d;color:var(--text)}.chip-toggle.active{border-color:#d4a27f66;color:var(--accent)}.type-chips{display:flex;flex-wrap:wrap;gap:4px;justify-content:center;max-width:500px;max-height:200px;overflow-y:auto;padding:4px;border-radius:10px}.type-chips.hidden{display:none}.type-chip{display:flex;align-items:center;gap:4px;padding:3px 10px;border:1px solid transparent;border-radius:12px;background:var(--chip-bg);color:var(--text-dim);font-size:11px;cursor:pointer;transition:all .15s;white-space:nowrap}.type-chip.active{background:var(--chip-bg-active);color:var(--text-muted);border-color:var(--chip-border-active)}.type-chip:hover{background:var(--chip-bg-hover)}.type-chip-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.type-chip:not(.active) .type-chip-dot{opacity:.3}.info-panel{position:absolute;top:56px;right:16px;bottom:16px;width:360px;background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;overflow:hidden;display:flex;flex-direction:column;padding:0;z-index:10;box-shadow:0 8px 32px var(--shadow);transition:top .25s ease,right .25s ease,bottom .25s ease,left .25s ease,width .25s ease,max-height .25s ease,border-radius .25s ease}.info-panel-header{flex-shrink:0;padding:20px 20px 12px;position:relative}.info-panel-body{flex:1;overflow-y:auto;min-height:0;padding:0 20px 20px}.info-panel.hidden{display:none}.info-panel.info-panel-maximized{top:0;right:0;bottom:0;left:0;width:auto;max-height:none;border-radius:0;z-index:40}.info-panel-toolbar{position:absolute;top:12px;right:14px;display:flex;align-items:center;gap:2px;z-index:1}.info-toolbar-btn{background:none;border:none;color:var(--text-muted);font-size:16px;cursor:pointer;padding:4px 6px;line-height:1;border-radius:4px;transition:color .15s,background .15s}.info-toolbar-btn:hover:not(:disabled){color:var(--text);background:var(--bg-hover)}.info-toolbar-btn:disabled{color:var(--text-dim);cursor:default;opacity:.3}.info-close-btn{font-size:20px}.info-connection-link{cursor:pointer;transition:background .15s}.info-connection-link:hover{background:var(--bg-active)}.info-connection-link .info-target{color:var(--accent);text-decoration:underline;text-decoration-color:transparent;transition:text-decoration-color .15s}.info-connection-link:hover .info-target{text-decoration-color:var(--accent)}.info-header{margin-bottom:16px}.info-type-badge{display:inline-block;padding:3px 10px;border-radius:12px;font-size:11px;font-weight:600;color:var(--badge-text);margin-bottom:8px}.info-label{font-size:18px;font-weight:600;color:var(--text-strong);margin-bottom:4px;word-break:break-word}.info-id{display:block;font-size:11px;color:var(--text-dim);font-family:monospace}.info-section{margin-bottom:16px}.info-section-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border)}.info-props{display:grid;grid-template-columns:auto 1fr;gap:4px 12px}.info-props dt{font-size:12px;color:var(--text-muted);padding-top:2px}.info-props dd{font-size:12px;color:var(--text);word-break:break-word;display:flex;align-items:center;gap:4px}.info-value{white-space:pre-wrap}.info-array{display:flex;flex-wrap:wrap;gap:4px}.info-tag{display:inline-block;padding:2px 8px;background:var(--bg-hover);border-radius:4px;font-size:11px;color:var(--text-muted)}.info-json{font-size:11px;font-family:monospace;color:var(--text-muted);background:var(--bg-inset);padding:6px 8px;border-radius:4px;overflow-x:auto;white-space:pre}.info-connections{list-style:none;display:flex;flex-direction:column;gap:6px}.info-connection{display:flex;align-items:center;gap:6px;padding:6px 8px;background:var(--bg-elevated);border-radius:6px;font-size:12px;flex-wrap:wrap}.info-connection-active{outline:1.5px solid var(--accent);background:var(--bg-hover)}.info-target-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.info-arrow{color:var(--text-dim);font-size:14px;flex-shrink:0}.info-edge-type{color:var(--text-muted);font-size:11px;font-weight:500}.info-target{color:var(--text);font-weight:500}.info-edge-props{width:100%;padding-top:4px;padding-left:20px}.info-edge-prop{display:block;font-size:11px;color:var(--text-dim)}.info-editable{cursor:default;position:relative}.info-inline-edit{background:none;border:none;color:var(--badge-text);opacity:0;font-size:10px;cursor:pointer;margin-left:4px;transition:opacity .15s}.info-editable:hover .info-inline-edit{opacity:.8}.info-inline-edit:hover{opacity:1!important}.info-edit-inline-input{background:transparent;border:none;border-bottom:1px solid var(--accent);color:var(--badge-text);font:inherit;font-size:inherit;outline:none;width:100%;padding:0}.info-edit-input{background:var(--bg-inset);border:1px solid var(--border);border-radius:4px;padding:3px 6px;font-size:12px;font-family:inherit;color:var(--text);flex:1;min-width:0;resize:vertical;overflow:hidden;line-height:1.4;max-height:300px}.info-edit-input:focus{outline:none;border-color:var(--accent)}.info-delete-prop{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;padding:0 2px;flex-shrink:0;opacity:0;transition:opacity .1s,color .1s}.info-props dd:hover .info-delete-prop{opacity:1}.info-delete-prop:hover{color:#ef4444}.info-add-btn{background:none;border:1px dashed var(--border);border-radius:4px;padding:6px 10px;font-size:12px;color:var(--text-dim);cursor:pointer;width:100%;margin-top:8px;transition:border-color .15s,color .15s}.info-add-btn:hover{border-color:var(--accent);color:var(--text)}.info-add-row{display:flex;gap:4px;margin-top:6px}.info-add-save{background:var(--accent);border:none;border-radius:4px;padding:3px 10px;font-size:12px;color:var(--badge-text);cursor:pointer;flex-shrink:0}.info-add-save:hover{background:var(--accent-hover)}.info-delete-edge{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;margin-left:auto;padding:0 2px;opacity:0;transition:opacity .1s,color .1s}.info-connection:hover .info-delete-edge{opacity:1}.info-delete-edge:hover{color:#ef4444}.info-danger{margin-top:8px;padding-top:12px;border-top:1px solid var(--border)}.info-delete-node{background:none;border:1px solid rgba(239,68,68,.3);border-radius:6px;padding:6px 12px;font-size:12px;color:#ef4444;cursor:pointer;width:100%;transition:background .15s}.info-delete-node:hover{background:#ef44441a}.tools-pane-toggle{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;color:var(--text-muted);font-size:18px;cursor:pointer;padding:6px 10px;line-height:1;transition:color .15s,border-color .15s,background .15s;box-shadow:0 2px 8px var(--shadow)}.tools-pane-toggle.hidden{display:none}.tools-pane-toggle:hover{color:var(--text);border-color:var(--text-muted);background:var(--bg-hover)}.tools-pane-toggle.active{color:var(--accent);border-color:#d4a27f66}.tools-pane-content{position:absolute;top:56px;left:16px;bottom:16px;z-index:20;width:var(--tools-width, 264px);box-sizing:border-box;overflow:hidden;display:flex;flex-direction:column;background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;padding:12px;box-shadow:0 8px 32px var(--shadow)}.tools-pane-content.hidden{display:none}.tools-pane-section{margin-bottom:12px}.tools-pane-section:last-child{margin-bottom:0}.tools-pane-heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);margin-bottom:6px}.tools-pane-row{display:flex;align-items:center;gap:6px;padding:3px 0;font-size:12px}.tools-pane-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.tools-pane-name{flex:1;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tools-pane-count{color:var(--text-dim);font-size:11px;flex-shrink:0}.tools-pane-summary{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--text-muted);padding-bottom:10px;margin-bottom:10px;border-bottom:1px solid var(--border)}.tools-pane-sep{color:var(--text-dim)}.tools-pane-clickable{cursor:pointer;border-radius:4px;padding:3px 4px;margin:0 -4px;transition:background .1s}.tools-pane-clickable:hover{background:var(--bg-hover)}.tools-pane-clickable.active{background:var(--bg-hover);outline:1px solid var(--border)}.tools-pane-badge{font-size:9px;color:var(--accent);flex-shrink:0;opacity:.8}.tools-pane-issue .tools-pane-name{color:var(--text-muted)}.tools-pane-more{font-size:10px;color:var(--text-dim);padding:4px 0 0}.tools-pane-edit{background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;padding:0 2px;opacity:0;transition:opacity .1s,color .1s;flex-shrink:0}.tools-pane-row:hover .tools-pane-edit{opacity:1}.tools-pane-edit:hover{color:var(--accent)}.tools-pane-focus-toggle{opacity:.4;font-size:11px}.tools-pane-focus-active{opacity:1!important;color:var(--accent)!important}.tools-pane-focus-clear{margin-top:4px;border-top:1px solid var(--border);padding-top:6px}.tools-pane-editing{background:none!important}.tools-pane-inline-input{width:100%;background:var(--bg);border:1px solid var(--accent);border-radius:4px;color:var(--text);font-size:12px;padding:2px 6px;outline:none}.tools-pane-slider-row{display:flex;align-items:center;gap:6px;padding:4px 0}.tools-pane-slider-label{font-size:11px;color:var(--text-muted);white-space:nowrap;min-width:56px}.tools-pane-slider{flex:1;min-width:0;height:4px;accent-color:var(--accent);cursor:pointer}.tools-pane-slider-value{font-size:10px;color:var(--text-dim);min-width:28px;text-align:right;font-family:monospace}.tools-pane-checkbox{width:14px;height:14px;accent-color:var(--accent);cursor:pointer;flex-shrink:0}.tools-pane-export-row{display:flex;gap:4px;margin-top:6px}.tools-pane-export-btn{flex:1;padding:4px 8px;font-size:11px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text-muted);cursor:pointer;transition:color .15s,border-color .15s}.tools-pane-export-btn:hover{color:var(--text);border-color:var(--text-muted)}.tools-pane-empty{font-size:11px;color:var(--text-dim);text-align:center;padding:8px 0}.tools-pane-tabs{display:flex;gap:2px;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}.tools-pane-tab{flex:1;padding:4px 0;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.03em;background:none;border:1px solid transparent;border-radius:5px;color:var(--text-dim);cursor:pointer;transition:color .15s,background .15s,border-color .15s}.tools-pane-tab:hover{color:var(--text-muted);background:var(--bg-hover)}.tools-pane-tab-active{color:var(--text);background:var(--bg-hover);border-color:var(--border)}.tools-pane-tab-content{flex:1;overflow-y:auto;overflow-x:hidden;min-height:0}.tools-pane-search{width:100%;padding:4px 8px;font-size:11px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);outline:none;margin-bottom:8px;box-sizing:border-box}.tools-pane-search:focus{border-color:var(--accent)}.tools-pane-search::placeholder{color:var(--text-dim)}.tools-pane-empty-msg{font-size:11px;color:var(--text-dim);text-align:center;padding:16px 0}.empty-state{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;z-index:5;pointer-events:none}.empty-state.hidden{display:none}.empty-state-content{text-align:center;max-width:420px;padding:40px 24px}.empty-state-icon{color:var(--text-dim);margin-bottom:16px}.empty-state-title{font-size:18px;font-weight:600;color:var(--text);margin-bottom:8px}.empty-state-desc{font-size:13px;color:var(--text-muted);line-height:1.5;margin-bottom:20px}.empty-state-setup{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:16px}.empty-state-label{font-size:11px;color:var(--text-dim);margin-bottom:8px}.empty-state-code{display:block;font-size:12px;color:var(--accent);font-family:monospace;word-break:break-all}.empty-state-hint{font-size:11px;color:var(--text-dim)}.empty-state-hint kbd{padding:1px 5px;border:1px solid var(--border);border-radius:3px;background:var(--bg-surface);font-family:monospace;font-size:11px}.shortcuts-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:100}.shortcuts-overlay.hidden{display:none}.shortcuts-modal{background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;padding:24px;min-width:300px;max-width:400px;box-shadow:0 16px 48px var(--shadow);position:relative}.shortcuts-close{position:absolute;top:12px;right:14px;background:none;border:none;color:var(--text-dim);font-size:20px;cursor:pointer;padding:0 4px;line-height:1}.shortcuts-close:hover{color:var(--text)}.shortcuts-title{font-size:15px;font-weight:600;color:var(--text);margin-bottom:16px}.shortcuts-list{display:flex;flex-direction:column;gap:8px}.shortcuts-row{display:flex;align-items:center;justify-content:space-between;gap:12px}.shortcuts-keys{display:flex;align-items:center;gap:4px}.shortcuts-keys kbd{padding:2px 7px;border:1px solid var(--border);border-radius:4px;background:var(--bg);color:var(--text);font-size:11px;font-family:monospace}.shortcuts-or{font-size:10px;color:var(--text-dim)}.shortcuts-desc{font-size:12px;color:var(--text-muted)}@media(max-width:768px){#app{flex-direction:column}#sidebar{width:100%;min-width:0;max-height:35vh;border-right:none;border-bottom:1px solid var(--border)}.info-panel{top:auto;bottom:72px;right:8px;left:8px;width:auto;max-height:calc(100% - 200px);overflow-y:auto}.info-panel.info-panel-maximized{bottom:0;left:0;right:0}.canvas-top-bar{top:8px;left:8px;right:8px}.tools-pane-content{top:48px;left:8px;bottom:80px;width:160px;max-width:calc(100vw - 24px)}.tools-pane-edit{opacity:.6}}.bp-dialog-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.bp-dialog{background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;padding:20px;min-width:280px;max-width:400px;box-shadow:0 16px 48px #0000004d}.bp-dialog-title{font-size:14px;font-weight:600;color:var(--text);margin-bottom:12px}.bp-dialog-message{font-size:13px;color:var(--text-muted);margin-bottom:16px;line-height:1.5}.bp-dialog-input{width:100%;padding:8px 12px;font-size:13px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);outline:none;margin-bottom:16px}.bp-dialog-input:focus{border-color:var(--accent)}.bp-dialog-buttons{display:flex;justify-content:flex-end;gap:8px}.bp-dialog-btn{padding:6px 16px;font-size:12px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text-muted);cursor:pointer;transition:all .15s}.bp-dialog-btn:hover{background:var(--bg-hover);color:var(--text)}.bp-dialog-btn-accent{background:var(--accent);color:#fff;border-color:var(--accent)}.bp-dialog-btn-accent:hover{opacity:.9;color:#fff;background:var(--accent)}.bp-dialog-btn-danger{background:#e55;color:#fff;border-color:#e55}.bp-dialog-btn-danger:hover{opacity:.9;color:#fff;background:#e55}.bp-toast{position:fixed;bottom:24px;left:50%;transform:translate(-50%) translateY(20px);background:var(--bg-surface);border:1px solid var(--border);color:var(--text);padding:8px 20px;border-radius:8px;font-size:12px;z-index:1001;opacity:0;transition:opacity .3s,transform .3s;box-shadow:0 4px 16px var(--shadow)}.bp-toast-visible{opacity:1;transform:translate(-50%) translateY(0)}.context-menu{position:absolute;z-index:100;background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:4px;min-width:180px;box-shadow:0 8px 24px var(--shadow)}.context-menu-item{padding:6px 12px;font-size:12px;color:var(--text);border-radius:4px;cursor:pointer;white-space:nowrap}.context-menu-item:hover{background:var(--bg-hover)}.context-menu-separator{height:1px;background:var(--border);margin:4px 8px}.path-bar{position:absolute;bottom:16px;left:50%;transform:translate(-50%);z-index:25;display:flex;align-items:center;gap:4px;background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:6px 12px;box-shadow:0 4px 16px var(--shadow);max-width:80%;overflow-x:auto}.path-bar.hidden{display:none}.path-bar-node{font-size:11px;color:var(--text);padding:2px 8px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0}.path-bar-node:hover{background:var(--bg-hover)}.path-bar-edge{font-size:9px;color:var(--text-dim);white-space:nowrap;flex-shrink:0}.path-bar-close{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;padding:0 4px;margin-left:8px;flex-shrink:0}.path-bar-close:hover{color:var(--text)}.walk-trail-edge{font-size:9px;color:var(--text-dim);padding:1px 0 1px 24px;opacity:.7}.walk-indicator{font-size:10px;color:var(--accent);padding:2px 8px;border:1px solid rgba(212,162,127,.4);border-radius:4px;cursor:pointer;opacity:.4}.walk-indicator.active{opacity:1;background:#d4a27f26;animation:walk-strobe 2s ease-in-out infinite}@keyframes walk-strobe{0%,to{opacity:.6;border-color:#d4a27f4d}50%{opacity:1;border-color:#d4a27fcc}}
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Backpack Viewer</title>
7
- <script type="module" crossorigin src="/assets/index-BAsAhA_i.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-CvETIueX.css">
7
+ <script type="module" crossorigin src="/assets/index-BBfZ1JvO.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-DNiYjxNx.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="app">
package/dist/canvas.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { LearningGraphData } from "backpack-ontology";
2
+ import { type LayoutNode } from "./layout";
2
3
  export interface CanvasConfig {
3
4
  lod?: {
4
5
  hideBadges?: number;
@@ -13,6 +14,9 @@ export interface CanvasConfig {
13
14
  zoomMax?: number;
14
15
  panAnimationMs?: number;
15
16
  };
17
+ walk?: {
18
+ pulseSpeed?: number;
19
+ };
16
20
  }
17
21
  export interface FocusInfo {
18
22
  seedNodeIds: string[];
@@ -37,6 +41,19 @@ export declare function initCanvas(container: HTMLElement, onNodeClick?: (nodeId
37
41
  exitFocus(): void;
38
42
  isFocused(): boolean;
39
43
  getFocusInfo(): FocusInfo | null;
44
+ findPath(sourceId: string, targetId: string): {
45
+ nodeIds: string[];
46
+ edgeIds: string[];
47
+ } | null;
48
+ setHighlightedPath(nodeIds: string[] | null, edgeIds: string[] | null): void;
49
+ clearHighlightedPath(): void;
50
+ setWalkMode(enabled: boolean): void;
51
+ getWalkMode(): boolean;
52
+ getWalkTrail(): string[];
53
+ getFilteredNodeIds(): Set<string> | null;
54
+ removeFromWalkTrail(nodeId: string): void;
55
+ /** Hit-test a screen coordinate against nodes. Returns the node or null. */
56
+ nodeAtScreen(sx: number, sy: number): LayoutNode | null;
40
57
  /** Get all node IDs in the current layout (subgraph if focused, full graph otherwise). Seed nodes first. */
41
58
  getNodeIds(): string[];
42
59
  destroy(): void;