backpack-viewer 0.6.0 → 0.7.1
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 +159 -396
- package/dist/app/assets/index-D-H7agBH.js +12 -0
- package/dist/app/assets/index-DE73ngo-.css +1 -0
- package/dist/app/assets/index-Lvl7EMM_.js +6 -0
- package/dist/app/index.html +2 -2
- package/dist/bridge.d.ts +22 -0
- package/dist/bridge.js +41 -0
- package/dist/config.js +10 -0
- package/dist/copy-prompt.d.ts +17 -0
- package/dist/copy-prompt.js +81 -0
- package/dist/default-config.json +4 -0
- package/dist/dom-utils.d.ts +46 -0
- package/dist/dom-utils.js +57 -0
- package/dist/empty-state.js +63 -31
- package/dist/extensions/api.d.ts +15 -0
- package/dist/extensions/api.js +185 -0
- package/dist/extensions/chat/backpack-extension.json +23 -0
- package/dist/extensions/chat/src/index.js +32 -0
- package/dist/extensions/chat/src/panel.js +306 -0
- package/dist/extensions/chat/src/providers/anthropic.js +158 -0
- package/dist/extensions/chat/src/providers/types.js +15 -0
- package/dist/extensions/chat/src/tools.js +281 -0
- package/dist/extensions/chat/style.css +147 -0
- package/dist/extensions/event-bus.d.ts +12 -0
- package/dist/extensions/event-bus.js +30 -0
- package/dist/extensions/loader.d.ts +32 -0
- package/dist/extensions/loader.js +71 -0
- package/dist/extensions/manifest.d.ts +54 -0
- package/dist/extensions/manifest.js +116 -0
- package/dist/extensions/panel-mount.d.ts +26 -0
- package/dist/extensions/panel-mount.js +377 -0
- package/dist/extensions/share/backpack-extension.json +20 -0
- package/dist/extensions/share/src/index.js +357 -0
- package/dist/extensions/share/style.css +151 -0
- package/dist/extensions/taskbar.d.ts +29 -0
- package/dist/extensions/taskbar.js +64 -0
- package/dist/extensions/types.d.ts +182 -0
- package/dist/extensions/types.js +8 -0
- package/dist/info-panel.d.ts +2 -1
- package/dist/info-panel.js +78 -87
- package/dist/main.js +189 -29
- package/dist/search.js +1 -1
- package/dist/server-api-routes.d.ts +56 -0
- package/dist/server-api-routes.js +460 -0
- package/dist/server-extensions.d.ts +126 -0
- package/dist/server-extensions.js +272 -0
- package/dist/server-viewer-state.d.ts +18 -0
- package/dist/server-viewer-state.js +33 -0
- package/dist/sidebar.js +19 -7
- package/dist/style.css +356 -74
- package/dist/tools-pane.js +31 -14
- package/package.json +4 -3
- package/dist/app/assets/index-B3z5bBGl.css +0 -1
- package/dist/app/assets/index-BROJmzot.js +0 -35
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
var Jn=Object.defineProperty;var Kn=(t,e,s)=>e in t?Jn(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s;var It=(t,e,s)=>Kn(t,typeof e!="symbol"?e+"":e,s);(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const u of document.querySelectorAll('link[rel="modulepreload"]'))l(u);new MutationObserver(u=>{for(const r of u)if(r.type==="childList")for(const d of r.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&l(d)}).observe(document,{childList:!0,subtree:!0});function s(u){const r={};return u.integrity&&(r.integrity=u.integrity),u.referrerPolicy&&(r.referrerPolicy=u.referrerPolicy),u.crossOrigin==="use-credentials"?r.credentials="include":u.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function l(u){if(u.ep)return;u.ep=!0;const r=s(u);fetch(u.href,r)}})();const Zn="modulepreload",Qn=function(t){return"/"+t},hn={},eo=function(e,s,l){let u=Promise.resolve();if(s&&s.length>0){let d=function(g){return Promise.all(g.map(i=>Promise.resolve(i).then(a=>({status:"fulfilled",value:a}),a=>({status:"rejected",reason:a}))))};document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),n=(o==null?void 0:o.nonce)||(o==null?void 0:o.getAttribute("nonce"));u=d(s.map(g=>{if(g=Qn(g),g in hn)return;hn[g]=!0;const i=g.endsWith(".css"),a=i?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${g}"]${a}`))return;const M=document.createElement("link");if(M.rel=i?"stylesheet":Zn,i||(M.as="script"),M.crossOrigin="",M.href=g,n&&M.setAttribute("nonce",n),document.head.appendChild(M),i)return new Promise((T,x)=>{M.addEventListener("load",T),M.addEventListener("error",()=>x(new Error(`Unable to preload CSS for ${g}`)))})}))}function r(d){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=d,window.dispatchEvent(o),!o.defaultPrevented)throw d}return u.then(d=>{for(const o of d||[])o.status==="rejected"&&r(o.reason);return e().catch(r)})};async function vt(){const t=await fetch("/api/ontologies");return t.ok?t.json():[]}async function Mt(t){const e=await fetch(`/api/ontologies/${encodeURIComponent(t)}`);if(!e.ok)throw new Error(`Failed to load ontology: ${t}`);return e.json()}async function to(){const t=await fetch("/api/remotes");return t.ok?t.json():[]}async function no(t){const e=await fetch(`/api/remotes/${encodeURIComponent(t)}`);if(!e.ok)throw new Error(`Failed to load remote graph: ${t}`);return e.json()}async function Ot(t,e){if(!(await fetch(`/api/ontologies/${encodeURIComponent(t)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).ok)throw new Error(`Failed to save ontology: ${t}`)}async function oo(t,e){if(!(await fetch(`/api/ontologies/${encodeURIComponent(t)}/rename`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e})})).ok)throw new Error(`Failed to rename ontology: ${t}`)}async function so(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches`);return e.ok?e.json():[]}async function fn(t,e,s){const l=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,from:s})});if(!l.ok){const u=await l.json().catch(()=>({}));throw new Error(u.error||"Failed to create branch")}}async function gn(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches/switch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e})});if(!s.ok){const l=await s.json().catch(()=>({}));throw new Error(l.error||"Failed to switch branch")}}async function ao(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches/${encodeURIComponent(e)}`,{method:"DELETE"});if(!s.ok){const l=await s.json().catch(()=>({}));throw new Error(l.error||"Failed to delete branch")}}async function co(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/snapshots`);return e.ok?e.json():[]}async function io(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/snapshots`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:e})});if(!s.ok){const l=await s.json().catch(()=>({}));throw new Error(l.error||"Failed to create snapshot")}}async function lo(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/rollback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({version:e})});if(!s.ok){const l=await s.json().catch(()=>({}));throw new Error(l.error||"Failed to rollback")}}async function ro(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets`);return e.ok?e.json():[]}async function yn(t,e,s,l,u){const r=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:e,description:u,nodeIds:s,edgeIds:l})});if(!r.ok)throw new Error("Failed to save snippet");return(await r.json()).id}async function po(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets/${encodeURIComponent(e)}`);if(!s.ok)throw new Error("Snippet not found");return s.json()}async function uo(t,e){if(!(await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets/${encodeURIComponent(e)}`,{method:"DELETE"})).ok)throw new Error("Failed to delete snippet")}const Cn="bp-dialog-overlay";function Zt(){var e;(e=document.querySelector(`.${Cn}`))==null||e.remove();const t=document.createElement("div");return t.className=Cn,document.body.appendChild(t),t}function Qt(t,e){const s=document.createElement("div");s.className="bp-dialog";const l=document.createElement("h4");return l.className="bp-dialog-title",l.textContent=e,s.appendChild(l),t.appendChild(s),t.addEventListener("click",u=>{u.target===t&&t.remove()}),s}function en(t,e){const s=document.createElement("div");s.className="bp-dialog-buttons";for(const l of e){const u=document.createElement("button");u.className="bp-dialog-btn",l.accent&&u.classList.add("bp-dialog-btn-accent"),l.danger&&u.classList.add("bp-dialog-btn-danger"),u.textContent=l.label,u.addEventListener("click",l.onClick),s.appendChild(u)}t.appendChild(s)}function jn(t,e){return new Promise(s=>{var d;const l=Zt(),u=Qt(l,t),r=document.createElement("p");r.className="bp-dialog-message",r.textContent=e,u.appendChild(r),en(u,[{label:"Cancel",onClick:()=>{l.remove(),s(!1)}},{label:"Confirm",accent:!0,onClick:()=>{l.remove(),s(!0)}}]),(d=u.querySelector(".bp-dialog-btn-accent"))==null||d.focus()})}function Bt(t,e,s){return new Promise(l=>{const u=Zt(),r=Qt(u,t),d=document.createElement("input");d.type="text",d.className="bp-dialog-input",d.placeholder=e??"",d.value="",r.appendChild(d);const o=()=>{const n=d.value.trim();u.remove(),l(n||null)};d.addEventListener("keydown",n=>{n.key==="Enter"&&o(),n.key==="Escape"&&(u.remove(),l(null))}),en(r,[{label:"Cancel",onClick:()=>{u.remove(),l(null)}},{label:"OK",accent:!0,onClick:o}]),d.focus(),d.select()})}function mo(){return new Promise(t=>{const e=Zt(),s=Qt(e,"Add Backpack"),l=document.createElement("p");l.className="bp-dialog-message",l.textContent="Enter the absolute path to a directory that should become a backpack. It will be shown in the sidebar using the last segment of the path as its display name.",s.appendChild(l);const u=document.createElement("label");u.className="bp-dialog-label",u.textContent="Path",s.appendChild(u);const r=document.createElement("div");r.className="bp-dialog-path-row",s.appendChild(r);const d=document.createElement("input");d.type="text",d.className="bp-dialog-input bp-dialog-path-input",d.placeholder="/Users/you/OneDrive/work",r.appendChild(d);const o=document.createElement("button");o.type="button",o.className="bp-dialog-btn bp-dialog-browse-btn",o.textContent="Browse...",r.appendChild(o),typeof window.showDirectoryPicker=="function"||(o.disabled=!0,o.title="Browser doesn't support native folder picker — paste the path manually"),o.addEventListener("click",async x=>{x.preventDefault();try{const F=await window.showDirectoryPicker({mode:"read"});g.textContent=`Picked "${F.name}" — paste the absolute path to it below.`,d.focus()}catch{}});const g=document.createElement("div");g.className="bp-dialog-picker-hint",s.appendChild(g);const i=document.createElement("div");i.className="bp-dialog-activate-row";const a=document.createElement("input");a.type="checkbox",a.id="bp-dialog-activate",a.checked=!0;const M=document.createElement("label");M.htmlFor="bp-dialog-activate",M.textContent="Switch to this backpack after registering",i.appendChild(a),i.appendChild(M),s.appendChild(i),d.addEventListener("dragover",x=>{x.preventDefault(),d.classList.add("bp-dialog-drag-over")}),d.addEventListener("dragleave",()=>{d.classList.remove("bp-dialog-drag-over")}),d.addEventListener("drop",x=>{var V,z,X;x.preventDefault(),d.classList.remove("bp-dialog-drag-over");const F=(V=x.dataTransfer)==null?void 0:V.items;if(!F||F.length===0)return;const W=(X=(z=F[0]).webkitGetAsEntry)==null?void 0:X.call(z);W!=null&&W.isDirectory&&(g.textContent=`Dropped "${W.name}" — paste the absolute path to it below.`)});const T=()=>{const x=d.value.trim();x&&(e.remove(),t({path:x,activate:a.checked}))};d.addEventListener("keydown",x=>{x.key==="Enter"&&T(),x.key==="Escape"&&(e.remove(),t(null))}),en(s,[{label:"Cancel",onClick:()=>{e.remove(),t(null)}},{label:"Register",accent:!0,onClick:T}]),d.focus()})}function wt(t,e=3e3){const s=document.querySelector(".bp-toast");s&&s.remove();const l=document.createElement("div");l.className="bp-toast",l.textContent=t,document.body.appendChild(l),setTimeout(()=>l.classList.add("bp-toast-visible"),10),setTimeout(()=>{l.classList.remove("bp-toast-visible"),setTimeout(()=>l.remove(),300)},e)}const vn="http://www.w3.org/2000/svg";function ct(t,e){const s=document.createElementNS(vn,"svg"),l=t.size??16;s.setAttribute("width",String(l)),s.setAttribute("height",String(l)),s.setAttribute("viewBox",t.viewBox??"0 0 24 24"),s.setAttribute("fill","none"),s.setAttribute("stroke","currentColor"),s.setAttribute("stroke-width",String(t.strokeWidth??2)),t.strokeLinecap&&s.setAttribute("stroke-linecap",t.strokeLinecap),t.strokeLinejoin&&s.setAttribute("stroke-linejoin",t.strokeLinejoin),t.className&&s.setAttribute("class",t.className);for(const u of e){const r=document.createElementNS(vn,u.tag);for(const[d,o]of Object.entries(u.attrs))r.setAttribute(d,String(o));s.appendChild(r)}return s}function ho(t){return Array.from(t.childNodes).map(e=>e.cloneNode(!0))}function fo(t,e){t.replaceChildren(...e)}function bn(t){return t>=1e3?`${(t/1e3).toFixed(1)}k tokens`:`${t} tokens`}function xn(t,e){return t*50+e*25+50}function go(t,e){const s=typeof e=="function"?{onSelect:e}:e,l=document.createElement("h2");l.textContent="Backpack Viewer";const u=document.createElement("input");u.type="text",u.placeholder="Filter...",u.id="filter";const r=document.createElement("ul");r.id="ontology-list";const d=document.createElement("h3");d.className="sidebar-section-heading",d.textContent="REMOTE GRAPHS",d.hidden=!0;const o=document.createElement("ul");o.id="remote-list",o.className="remote-list",o.hidden=!0;const n=document.createElement("div");n.className="sidebar-footer";const g=document.createElement("a");g.href="mailto:support@backpackontology.com",g.textContent="support@backpackontology.com";const i=document.createElement("span");i.textContent="Feedback & support";const a=document.createElement("span");a.className="sidebar-version",a.textContent="v0.7.1",n.append(g,i,a);const M=document.createElement("button");M.className="sidebar-collapse-btn",M.title="Toggle sidebar (Tab)",M.appendChild(ct({size:14},[{tag:"polyline",attrs:{points:"11 17 6 12 11 7"}},{tag:"polyline",attrs:{points:"18 17 13 12 18 7"}}]));let T=!1;function x(){T=!T,t.classList.toggle("sidebar-collapsed",T),ye.classList.toggle("hidden",!T)}M.addEventListener("click",x);const F=document.createElement("div");F.className="sidebar-heading-row",F.appendChild(l),F.appendChild(M),t.appendChild(F);const W=document.createElement("div");W.className="sidebar-stale-banner",W.hidden=!0,t.appendChild(W);const V=document.createElement("button");V.className="backpack-picker-pill",V.type="button",V.setAttribute("aria-haspopup","listbox"),V.setAttribute("aria-expanded","false");const z=document.createElement("span");z.className="backpack-picker-dot";const X=document.createElement("span");X.className="backpack-picker-name",X.textContent="...";const H=document.createElement("span");H.className="backpack-picker-caret",H.textContent="▾",V.appendChild(z),V.appendChild(X),V.appendChild(H);const K=document.createElement("div");K.className="backpack-picker-dropdown",K.hidden=!0,K.setAttribute("role","listbox");const q=document.createElement("div");q.className="backpack-picker-container",q.appendChild(V),q.appendChild(K),t.appendChild(q);let ue=!1;function U(){ue=!1,K.hidden=!0,V.setAttribute("aria-expanded","false")}function de(){ue=!0,K.hidden=!1,V.setAttribute("aria-expanded","true")}V.addEventListener("click",p=>{p.stopPropagation(),ue?U():de()}),document.addEventListener("click",p=>{q.contains(p.target)||U()});let xe=[],ee=null;function G(){K.replaceChildren();for(const v of xe){const m=document.createElement("button");m.className="backpack-picker-item",m.type="button",m.setAttribute("role","option"),v.active&&m.classList.add("active");const b=document.createElement("span");b.className="backpack-picker-item-dot",b.style.setProperty("--backpack-color",v.color);const L=document.createElement("span");L.className="backpack-picker-item-name",L.textContent=v.name;const h=document.createElement("span");h.className="backpack-picker-item-path",h.textContent=v.path,m.appendChild(b),m.appendChild(L),m.appendChild(h),m.addEventListener("click",S=>{S.stopPropagation(),U(),!v.active&&s.onBackpackSwitch&&s.onBackpackSwitch(v.name)}),K.appendChild(m)}const p=document.createElement("div");p.className="backpack-picker-divider",K.appendChild(p);const y=document.createElement("button");y.className="backpack-picker-item backpack-picker-add",y.type="button",y.textContent="+ Add new backpack…",y.addEventListener("click",async v=>{if(v.stopPropagation(),U(),!s.onBackpackRegister)return;const m=await mo();m&&s.onBackpackRegister(m.path,m.activate)}),K.appendChild(y)}const ye=document.createElement("button");ye.className="tools-pane-toggle hidden",ye.title="Show sidebar (Tab)",ye.appendChild(ct({size:14},[{tag:"polyline",attrs:{points:"13 7 18 12 13 17"}},{tag:"polyline",attrs:{points:"6 7 11 12 6 17"}}])),ye.addEventListener("click",x),t.appendChild(u),t.appendChild(r),t.appendChild(d),t.appendChild(o),t.appendChild(n);let Ce=[],se=[],ge="";return u.addEventListener("input",()=>{const p=u.value.toLowerCase();for(const y of Ce){const v=y.dataset.name??"";y.style.display=v.includes(p)?"":"none"}for(const y of se){const v=y.dataset.name??"";y.style.display=v.includes(p)?"":"none"}}),{setStaleVersionBanner(p,y){W.replaceChildren();const v=document.createElement("div");v.className="sidebar-stale-banner-title",v.textContent=`Viewer ${p} is out of date`;const m=document.createElement("div");m.className="sidebar-stale-banner-subtitle",m.textContent=`Latest is ${y}. Your version is stuck because of an npx cache.`;const b=document.createElement("pre");b.className="sidebar-stale-banner-hint",b.textContent=`npm cache clean --force
|
|
2
|
+
npx backpack-viewer@latest`,W.appendChild(v),W.appendChild(m),W.appendChild(b),W.hidden=!1},setBackpacks(p){xe=p.slice();const y=p.find(v=>v.active)??null;ee=y,y&&(X.textContent=y.name,z.style.setProperty("--backpack-color",y.color),t.style.setProperty("--backpack-color",y.color)),G()},setActiveBackpack(p){ee=p,xe=xe.map(y=>({...y,active:y.name===p.name})),xe.some(y=>y.name===p.name)||xe.push({...p,active:!0}),X.textContent=p.name,z.style.setProperty("--backpack-color",p.color),t.style.setProperty("--backpack-color",p.color),G()},getActiveBackpack(){return ee},setSummaries(p){r.replaceChildren();const y=fetch("/api/locks").then(v=>v.json()).catch(()=>({}));Ce=p.map(v=>{const m=document.createElement("li");m.className="ontology-item",m.dataset.name=v.name;const b=document.createElement("span");b.className="name",b.textContent=v.name;const L=document.createElement("span");L.className="stats";const h=xn(v.nodeCount,v.edgeCount);L.textContent=`${v.nodeCount} nodes, ${v.edgeCount} edges · ~${bn(h)}`;const S=document.createElement("span");S.className="sidebar-branch",S.dataset.graph=v.name;const w=document.createElement("span");if(w.className="sidebar-lock-badge",w.dataset.graph=v.name,y.then(N=>{if(!w.isConnected)return;const I=N[v.name];I&&typeof I=="object"&&I.author&&(w.textContent=`editing: ${I.author}`,w.title=`Last activity: ${I.lastActivity??""}`,w.classList.add("active"))}),m.appendChild(b),m.appendChild(L),m.appendChild(w),m.appendChild(S),s.onRename){const N=document.createElement("button");N.className="sidebar-edit-btn",N.textContent="✎",N.title="Rename";const I=s.onRename;N.addEventListener("click",C=>{C.stopPropagation();const B=document.createElement("input");B.type="text",B.className="sidebar-rename-input",B.value=v.name,b.textContent="",b.appendChild(B),N.style.display="none",B.focus(),B.select();const $=()=>{const te=B.value.trim();te&&te!==v.name?I(v.name,te):(b.textContent=v.name,N.style.display="")};B.addEventListener("blur",$),B.addEventListener("keydown",te=>{te.key==="Enter"&&B.blur(),te.key==="Escape"&&(B.value=v.name,B.blur())})}),m.appendChild(N)}return m.addEventListener("click",()=>s.onSelect(v.name)),r.appendChild(m),m}),ge&&this.setActive(ge)},setActive(p){ge=p;for(const y of Ce)y.classList.toggle("active",y.dataset.name===p);for(const y of se)y.classList.toggle("active",y.dataset.name===p)},setRemotes(p){o.replaceChildren(),se=p.map(v=>{const m=document.createElement("li");m.className="ontology-item ontology-item-remote",m.dataset.name=v.name;const b=document.createElement("div");b.className="remote-name-row";const L=document.createElement("span");L.className="name",L.textContent=v.name;const h=document.createElement("span");h.className="remote-badge",h.textContent=v.pinned?"remote · pinned":"remote",h.title=`Source: ${v.source??v.url}`,b.appendChild(L),b.appendChild(h);const S=document.createElement("span");S.className="stats";const w=xn(v.nodeCount,v.edgeCount);S.textContent=`${v.nodeCount} nodes, ${v.edgeCount} edges · ~${bn(w)}`;const N=document.createElement("span");return N.className="remote-source",N.textContent=v.source??new URL(v.url).hostname,N.title=v.url,m.appendChild(b),m.appendChild(S),m.appendChild(N),m.addEventListener("click",()=>s.onSelect(v.name)),o.appendChild(m),m});const y=p.length>0;d.hidden=!y,o.hidden=!y,ge&&this.setActive(ge)},setActiveBranch(p,y,v){const m=r.querySelectorAll(`.sidebar-branch[data-graph="${p}"]`);for(const b of m){b.textContent=`/ ${y}`,b.title="Click to switch branch",b.style.cursor="pointer";const L=b.cloneNode(!0);b.replaceWith(L),L.addEventListener("click",h=>{h.stopPropagation(),be(p,L,v??[])})}},setSnippets(p,y){var b;const v=Ce.find(L=>L.dataset.name===p);if(!v||((b=v.querySelector(".sidebar-snippets"))==null||b.remove(),y.length===0))return;const m=document.createElement("div");m.className="sidebar-snippets";for(const L of y){const h=document.createElement("div");h.className="sidebar-snippet";const S=document.createElement("span");S.className="sidebar-snippet-label",S.textContent=`◆ ${L.label}`,S.title=`${L.nodeCount} nodes — click to load`;const w=document.createElement("button");w.className="sidebar-snippet-delete",w.textContent="×",w.title="Delete snippet",w.addEventListener("click",N=>{var I;N.stopPropagation(),(I=s.onSnippetDelete)==null||I.call(s,p,L.id)}),h.appendChild(S),h.appendChild(w),h.addEventListener("click",N=>{var I;N.stopPropagation(),(I=s.onSnippetLoad)==null||I.call(s,p,L.id)}),m.appendChild(h)}v.appendChild(m)},toggle:x,expandBtn:ye};function be(p,y,v){const m=t.querySelector(".branch-picker");m&&m.remove();const b=document.createElement("div");b.className="branch-picker";for(const h of v){const S=document.createElement("div");S.className="branch-picker-item",h.active&&S.classList.add("branch-picker-active");const w=document.createElement("span");if(w.textContent=h.name,S.appendChild(w),!h.active&&s.onBranchDelete){const N=document.createElement("button");N.className="branch-picker-delete",N.textContent="×",N.title=`Delete ${h.name}`,N.addEventListener("click",I=>{I.stopPropagation(),jn("Delete branch",`Delete branch "${h.name}"?`).then(C=>{C&&(s.onBranchDelete(p,h.name),b.remove())})}),S.appendChild(N)}h.active||S.addEventListener("click",()=>{var N;(N=s.onBranchSwitch)==null||N.call(s,p,h.name),b.remove()}),b.appendChild(S)}if(s.onBranchCreate){const h=document.createElement("div");h.className="branch-picker-item branch-picker-create",h.textContent="+ New branch",h.addEventListener("click",()=>{Bt("New branch","Branch name").then(S=>{S&&(s.onBranchCreate(p,S),b.remove())})}),b.appendChild(h)}y.after(b);const L=h=>{b.contains(h.target)||(b.remove(),document.removeEventListener("click",L))};setTimeout(()=>document.addEventListener("click",L),0)}}function Gt(t,e,s,l){return{x0:t,y0:e,x1:s,y1:l,cx:0,cy:0,mass:0,children:[null,null,null,null],body:null}}function En(t,e,s){const l=(t.x0+t.x1)/2,u=(t.y0+t.y1)/2;return(e<l?0:1)+(s<u?0:2)}function wn(t,e){const s=(t.x0+t.x1)/2,l=(t.y0+t.y1)/2;switch(e){case 0:return[t.x0,t.y0,s,l];case 1:return[s,t.y0,t.x1,l];case 2:return[t.x0,l,s,t.y1];default:return[s,l,t.x1,t.y1]}}function Vt(t,e){if(t.mass===0&&t.body===null){t.body=e,t.cx=e.x,t.cy=e.y,t.mass=1;return}if(t.body!==null){const u=t.body;t.body=null,u.x===e.x&&u.y===e.y&&(e.x+=(Math.random()-.5)*.1,e.y+=(Math.random()-.5)*.1);const r=En(t,u.x,u.y);if(t.children[r]===null){const[d,o,n,g]=wn(t,r);t.children[r]=Gt(d,o,n,g)}Vt(t.children[r],u)}const s=En(t,e.x,e.y);if(t.children[s]===null){const[u,r,d,o]=wn(t,s);t.children[s]=Gt(u,r,d,o)}Vt(t.children[s],e);const l=t.mass+1;t.cx=(t.cx*t.mass+e.x)/l,t.cy=(t.cy*t.mass+e.y)/l,t.mass=l}function yo(t){if(t.length===0)return null;let e=1/0,s=1/0,l=-1/0,u=-1/0;for(const i of t)i.x<e&&(e=i.x),i.y<s&&(s=i.y),i.x>l&&(l=i.x),i.y>u&&(u=i.y);const r=Math.max(l-e,u-s)*.1+50,d=(e+l)/2,o=(s+u)/2,n=Math.max(l-e,u-s)/2+r,g=Gt(d-n,o-n,d+n,o+n);for(const i of t)Vt(g,i);return g}function Co(t,e,s,l,u,r){zn(t,e,s,l,u,r)}function zn(t,e,s,l,u,r){if(t.mass===0)return;const d=t.cx-e.x,o=t.cy-e.y,n=d*d+o*o;if(t.body!==null){if(t.body!==e){let i=Math.sqrt(n);i<r&&(i=r);const a=l*u/(i*i),M=d/i*a,T=o/i*a;e.vx-=M,e.vy-=T}return}const g=t.x1-t.x0;if(g*g/n<s*s){let i=Math.sqrt(n);i<r&&(i=r);const a=l*t.mass*u/(i*i),M=d/i*a,T=o/i*a;e.vx-=M,e.vy-=T;return}for(let i=0;i<4;i++)t.children[i]!==null&&zn(t.children[i],e,s,l,u,r)}const _n={clusterStrength:.08,spacing:1.5},kn=6e3,vo=12e3,bo=.004,Hn=140,Yn=350,Sn=.9,Nn=.01,bt=30,Wt=50;let et={..._n};function lt(t){t.clusterStrength!==void 0&&(et.clusterStrength=t.clusterStrength),t.spacing!==void 0&&(et.spacing=t.spacing)}function Et(){return{...et}}function xo(t){if(t<=30)return{..._n};const e=Math.log2(t/30);return{clusterStrength:Math.min(.5,.08+.06*e),spacing:Math.min(15,1.5+1.2*e)}}function Eo(t,e){for(const s of Object.values(t))if(typeof s=="string")return s;return e}function Ln(t,e,s){const l=new Set(e);let u=new Set(e);for(let r=0;r<s;r++){const d=new Set;for(const o of t.edges)u.has(o.sourceId)&&!l.has(o.targetId)&&d.add(o.targetId),u.has(o.targetId)&&!l.has(o.sourceId)&&d.add(o.sourceId);for(const o of d)l.add(o);if(u=d,d.size===0)break}return{nodes:t.nodes.filter(r=>l.has(r.id)),edges:t.edges.filter(r=>l.has(r.sourceId)&&l.has(r.targetId)),metadata:t.metadata}}function Ut(t){const e=new Map,s=[...new Set(t.nodes.map(n=>n.type))],l=Math.sqrt(s.length)*Yn*.6*Math.max(1,et.spacing),u=new Map,r=new Map;for(const n of t.nodes)r.set(n.type,(r.get(n.type)??0)+1);const d=t.nodes.map(n=>{const g=s.indexOf(n.type),i=2*Math.PI*g/Math.max(s.length,1),a=Math.cos(i)*l,M=Math.sin(i)*l,T=u.get(n.type)??0;u.set(n.type,T+1);const x=r.get(n.type)??1,F=2*Math.PI*T/x,W=Hn*.6,V={id:n.id,x:a+Math.cos(F)*W,y:M+Math.sin(F)*W,vx:0,vy:0,label:Eo(n.properties,n.id),type:n.type};return e.set(n.id,V),V}),o=t.edges.map(n=>({sourceId:n.sourceId,targetId:n.targetId,type:n.type}));return{nodes:d,edges:o,nodeMap:e}}const wo=.7,ko=80;function So(t,e){const{nodes:s,edges:l,nodeMap:u}=t,r=vo*et.spacing;if(s.length>=ko){const o=yo(s);if(o)for(const g of s)Co(o,g,wo,r,e,bt);const n=r-kn;if(n>0){const g=new Map;for(const i of s){let a=g.get(i.type);a||(a=[],g.set(i.type,a)),a.push(i)}for(const i of g.values())for(let a=0;a<i.length;a++)for(let M=a+1;M<i.length;M++){const T=i[a],x=i[M];let F=x.x-T.x,W=x.y-T.y,V=Math.sqrt(F*F+W*W);V<bt&&(V=bt);const z=n*e/(V*V),X=F/V*z,H=W/V*z;T.vx+=X,T.vy+=H,x.vx-=X,x.vy-=H}}}else for(let o=0;o<s.length;o++)for(let n=o+1;n<s.length;n++){const g=s[o],i=s[n];let a=i.x-g.x,M=i.y-g.y,T=Math.sqrt(a*a+M*M);T<bt&&(T=bt);const F=(g.type===i.type?kn:r)*e/(T*T),W=a/T*F,V=M/T*F;g.vx-=W,g.vy-=V,i.vx+=W,i.vy+=V}for(const o of l){const n=u.get(o.sourceId),g=u.get(o.targetId);if(!n||!g)continue;const i=g.x-n.x,a=g.y-n.y,M=Math.sqrt(i*i+a*a);if(M===0)continue;const T=n.type===g.type?Hn*et.spacing:Yn*et.spacing,x=bo*(M-T)*e,F=i/M*x,W=a/M*x;n.vx+=F,n.vy+=W,g.vx-=F,g.vy-=W}for(const o of s)o.vx-=o.x*Nn*e,o.vy-=o.y*Nn*e;const d=new Map;for(const o of s){const n=d.get(o.type)??{x:0,y:0,count:0};n.x+=o.x,n.y+=o.y,n.count++,d.set(o.type,n)}for(const o of d.values())o.x/=o.count,o.y/=o.count;for(const o of s){const n=d.get(o.type);o.vx+=(n.x-o.x)*et.clusterStrength*e,o.vy+=(n.y-o.y)*et.clusterStrength*e}for(const o of s){if(o.pinned){o.vx=0,o.vy=0;continue}o.vx*=Sn,o.vy*=Sn;const n=Math.sqrt(o.vx*o.vx+o.vy*o.vy);n>Wt&&(o.vx=o.vx/n*Wt,o.vy=o.vy/n*Wt),o.x+=o.vx,o.y+=o.vy}return e*.995}const In=["#d4a27f","#c17856","#b07a5e","#d4956b","#a67c5a","#cc9e7c","#c4866a","#cb8e6c","#b8956e","#a88a70","#d9b08c","#c4a882","#e8b898","#b5927a","#a8886e","#d1a990"],Mn=new Map;function Ae(t){const e=Mn.get(t);if(e)return e;let s=0;for(let u=0;u<t.length;u++)s=(s<<5)-s+t.charCodeAt(u)|0;const l=In[Math.abs(s)%In.length];return Mn.set(t,l),l}class No{constructor(e){It(this,"cells",new Map);It(this,"cellSize");It(this,"invCell");this.cellSize=e,this.invCell=1/e}key(e,s){const l=e+32768|0,u=s+32768|0;return l*73856093^u*19349663}clear(){this.cells.clear()}insert(e){const s=Math.floor(e.x*this.invCell),l=Math.floor(e.y*this.invCell),u=this.key(s,l),r=this.cells.get(u);r?r.push(e):this.cells.set(u,[e])}rebuild(e){this.cells.clear();for(const s of e)this.insert(s)}query(e,s,l){const u=l*l,r=Math.floor((e-l)*this.invCell),d=Math.floor((e+l)*this.invCell),o=Math.floor((s-l)*this.invCell),n=Math.floor((s+l)*this.invCell);let g=null,i=u;for(let a=r;a<=d;a++)for(let M=o;M<=n;M++){const T=this.cells.get(this.key(a,M));if(T)for(const x of T){const F=x.x-e,W=x.y-s,V=F*F+W*W;V<=i&&(i=V,g=x)}}return g}}const pt=new Map,Lo=2e3;function Io(t,e,s){return`${t}|${e}|${s}`}const Mo=new OffscreenCanvas(1,1),Tn=Mo.getContext("2d");function To(t,e,s){Tn.font=e;const l=Tn.measureText(t),u=Math.ceil(l.width)+2,r=Math.ceil(l.actualBoundingBoxAscent+l.actualBoundingBoxDescent)+4,d=new OffscreenCanvas(u,r),o=d.getContext("2d");return o.font=e,o.fillStyle=s,o.textAlign="left",o.textBaseline="top",o.fillText(t,1,1),{canvas:d,width:u,height:r}}function An(t,e,s,l,u,r,d){const o=Io(e,u,r);let n=pt.get(o);if(!n){if(pt.size>=Lo){const a=pt.keys().next().value;a!==void 0&&pt.delete(a)}n=To(e,u,r),pt.set(o,n)}const g=s-n.width/2,i=d==="top"?l:l-n.height;t.drawImage(n.canvas,g,i)}function Ao(){pt.clear()}function Ee(t){return getComputedStyle(document.documentElement).getPropertyValue(t).trim()}const Ue=20,jt=.001,Po={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15,dotNodes:.1,hullsOnly:.05},Ro={zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300};function Tt(t,e,s,l,u,r=100){const d=(t-s.x)*s.scale,o=(e-s.y)*s.scale;return d>=-r&&d<=l+r&&o>=-r&&o<=u+r}function Bo(t,e,s,l){const u={...Po,...(l==null?void 0:l.lod)??{}},r={...Ro,...(l==null?void 0:l.navigation)??{}},d={pulseSpeed:.02,...(l==null?void 0:l.walk)??{}},o=t.querySelector("canvas"),n=o.getContext("2d"),g=window.devicePixelRatio||1;let i={x:0,y:0,scale:1},a=null,M=1,T=0,x=new Set,F=null,W=!0,V=!0,z=!0,X=!0,H="idle",K=[],q=0,ue=0,U=null,de=new Set;const xe=5;let ee=null,G=null,ye=1,Ce=null,se=null,ge=null,be=!1,p=[],y=0,v=0;const m=400,b=new No(Ue*2);let L=0;function h(){ie(),L||(L=requestAnimationFrame(()=>{L=0,c()}))}const S=150;let w=null,N=!1;function I(){if(!w)try{w=new Worker(new URL("/assets/layout-worker-4xak23M6.js",import.meta.url),{type:"module"}),w.onmessage=C,w.onerror=()=>{N=!1,w=null,we()}}catch{return N=!1,null}return w}function C(f){const k=f.data;if(k.type==="tick"&&a){const P=k.positions,D=a.nodes;for(let _=0;_<D.length;_++)D[_].x=P[_*4],D[_].y=P[_*4+1],D[_].vx=P[_*4+2],D[_].vy=P[_*4+3];M=k.alpha,b.rebuild(D),c()}k.type==="settled"&&(M=0,be&&p.length>0&&!ae&&(ae=requestAnimationFrame(pe)))}let B=null,$=null,te=!0;function ie(){te=!0}let le=null,me=null;const ve=r.panAnimationMs;function je(){o.width=o.clientWidth*g,o.height=o.clientHeight*g,ie(),h()}const qe=new ResizeObserver(je);qe.observe(t),je();function Xe(f,k){return[f/i.scale+i.x,k/i.scale+i.y]}function Je(f,k){if(!a)return null;const[P,D]=Xe(f,k);return b.query(P,D,Ue)}function ut(){if(!U)return;n.save();const f=Math.min(U.x1,U.x2),k=Math.max(U.x1,U.x2),P=Math.min(U.y1,U.y2),D=Math.max(U.y1,U.y2);n.strokeStyle=Ee("--accent")||"#d4a27f",n.fillStyle=Ee("--accent")||"#d4a27f",n.globalAlpha=.12,n.fillRect(f,P,k-f,D-P),n.globalAlpha=.8,n.lineWidth=1/Math.max(i.scale,.5),n.setLineDash([6/Math.max(i.scale,.5),4/Math.max(i.scale,.5)]),n.strokeRect(f,P,k-f,D-P),n.setLineDash([]),n.restore()}function mt(){if(!a)return;y+=d.pulseSpeed;const f=new Set(p);n.save(),n.setTransform(g,0,0,g,0,0),B&&(n.clearRect(0,0,o.clientWidth,o.clientHeight),n.drawImage(B,0,0,o.clientWidth,o.clientHeight)),n.save(),n.translate(-i.x*i.scale,-i.y*i.scale),n.scale(i.scale,i.scale);const k=Ee("--canvas-walk-edge")||"#1a1a1a",P=[];for(const j of a.edges){if(!f.has(j.sourceId)||!f.has(j.targetId)||j.sourceId===j.targetId)continue;const O=a.nodeMap.get(j.sourceId),J=a.nodeMap.get(j.targetId);!O||!J||P.push(O.x,O.y,J.x,J.y)}if(P.length>0){n.beginPath();for(let j=0;j<P.length;j+=4)n.moveTo(P[j],P[j+1]),n.lineTo(P[j+2],P[j+3]);n.strokeStyle=k,n.lineWidth=3,n.globalAlpha=.5+.5*Math.sin(y),n.stroke(),n.globalAlpha=1}const D=i.scale<u.smallNodes?Ue*.5:Ue,_=Ee("--accent")||"#d4a27f";for(const j of p){const O=a.nodeMap.get(j);if(!O||!Tt(O.x,O.y,i,o.clientWidth,o.clientHeight))continue;const J=j===p[p.length-1],Z=.5+.5*Math.sin(y);n.strokeStyle=_,n.lineWidth=J?3:2,n.globalAlpha=J?.5+.5*Z:.3+.4*Z,n.beginPath(),n.arc(O.x,O.y,D+(J?6:4),0,Math.PI*2),n.stroke()}n.globalAlpha=1,ut(),n.restore(),X&&a.nodes.length>1&&E(),n.restore()}function c(){var ln;if(!a){n.clearRect(0,0,o.width,o.height);return}if(!te&&B&&be&&p.length>0&&M<jt){mt();return}const f=M<jt&&be&&p.length>0;be&&p.length>0&&(y+=d.pulseSpeed);const k=be&&!f?new Set(p):null,P=Ee("--canvas-edge"),D=Ee("--canvas-edge-highlight"),_=Ee("--canvas-edge-dim"),j=Ee("--canvas-edge-label"),O=Ee("--canvas-edge-label-highlight"),J=Ee("--canvas-edge-label-dim"),Z=Ee("--canvas-arrow"),re=Ee("--canvas-arrow-highlight"),fe=Ee("--canvas-node-label"),ke=Ee("--canvas-node-label-dim"),Se=Ee("--canvas-type-badge"),Te=Ee("--canvas-type-badge-dim"),Re=Ee("--canvas-selection-border"),ze=Ee("--canvas-node-border");if(n.save(),n.setTransform(g,0,0,g,0,0),n.clearRect(0,0,o.clientWidth,o.clientHeight),n.save(),n.translate(-i.x*i.scale,-i.y*i.scale),n.scale(i.scale,i.scale),z&&i.scale>=u.smallNodes){const Q=new Map;for(const Pe of a.nodes){if(F!==null&&!F.has(Pe.id))continue;const Fe=Q.get(Pe.type)??[];Fe.push(Pe),Q.set(Pe.type,Fe)}for(const[Pe,Fe]of Q){if(Fe.length<2)continue;const at=Ae(Pe),ot=Ue*2.5;let Ye=1/0,Ve=1/0,Be=-1/0,Qe=-1/0;for(const We of Fe)We.x<Ye&&(Ye=We.x),We.y<Ve&&(Ve=We.y),We.x>Be&&(Be=We.x),We.y>Qe&&(Qe=We.y);n.beginPath();const oe=(Be-Ye)/2+ot,Me=(Qe-Ve)/2+ot,Ne=(Ye+Be)/2,_e=(Ve+Qe)/2;n.ellipse(Ne,_e,oe,Me,0,0,Math.PI*2),n.fillStyle=at,n.globalAlpha=.05,n.fill(),n.strokeStyle=at,n.globalAlpha=.12,n.lineWidth=1,n.setLineDash([4,4]),n.stroke(),n.setLineDash([]),n.globalAlpha=1}}let Ge=null;if(x.size>0){Ge=new Set;for(const Q of a.edges)x.has(Q.sourceId)&&Ge.add(Q.targetId),x.has(Q.targetId)&&Ge.add(Q.sourceId)}const nt=Ee("--accent")||"#d4a27f",He=Ee("--canvas-walk-edge")||"#1a1a1a",it=i.scale>=u.hideArrows,Nt=V&&i.scale>=u.hideEdgeLabels;if(W){const Q=[],Pe=[],Fe=[],at=[],ot=[],Ye=[];for(const oe of a.edges){const Me=a.nodeMap.get(oe.sourceId),Ne=a.nodeMap.get(oe.targetId);if(!Me||!Ne||!Tt(Me.x,Me.y,i,o.clientWidth,o.clientHeight,200)&&!Tt(Ne.x,Ne.y,i,o.clientWidth,o.clientHeight,200))continue;const _e=F===null||F.has(oe.sourceId),We=F===null||F.has(oe.targetId),rn=_e&&We;if(F!==null&&!_e&&!We)continue;const Dt=x.size>0&&(x.has(oe.sourceId)||x.has(oe.targetId))||F!==null&&rn,dn=F!==null&&!rn,pn=k!==null&&k.has(oe.sourceId)&&k.has(oe.targetId),un=ge?ee==null?void 0:ee.edges.find(Lt=>Lt.sourceId===oe.sourceId&&Lt.targetId===oe.targetId||Lt.targetId===oe.sourceId&&Lt.sourceId===oe.targetId):null,mn=!!(ge&&un&&ge.edgeIds.has(un.id));if(oe.sourceId===oe.targetId){Y(Me,oe.type,Dt,P,D,j,O);continue}(mn?ot:pn?at:Dt?Pe:dn?Fe:Q).push(Me.x,Me.y,Ne.x,Ne.y),(it||Nt)&&Ye.push({sx:Me.x,sy:Me.y,tx:Ne.x,ty:Ne.y,type:oe.type,highlighted:Dt,edgeDimmed:dn,isPathEdge:mn,isWalkEdge:pn})}const Ve=it?1.5:1,Qe=[{lines:Q,color:P,width:Ve,alpha:1},{lines:Fe,color:_,width:Ve,alpha:1},{lines:Pe,color:D,width:it?2.5:1,alpha:1},{lines:ot,color:nt,width:3,alpha:1},{lines:at,color:He,width:3,alpha:.5+.5*Math.sin(y)}];for(const oe of Qe)if(oe.lines.length!==0){n.beginPath();for(let Me=0;Me<oe.lines.length;Me+=4)n.moveTo(oe.lines[Me],oe.lines[Me+1]),n.lineTo(oe.lines[Me+2],oe.lines[Me+3]);n.strokeStyle=oe.color,n.lineWidth=oe.width,n.globalAlpha=oe.alpha,n.stroke()}n.globalAlpha=1;for(const oe of Ye)if(it&&R(oe.sx,oe.sy,oe.tx,oe.ty,oe.highlighted||oe.isPathEdge,Z,re),Nt){const Me=(oe.sx+oe.tx)/2,Ne=(oe.sy+oe.ty)/2;n.fillStyle=oe.highlighted?O:oe.edgeDimmed?J:j,n.font="9px system-ui, sans-serif",n.textAlign="center",n.textBaseline="bottom",n.fillText(oe.type,Me,Ne-4)}}const Ft=performance.now()-v,Ie=Math.min(1,Ft/m),Ke=1-(1-Ie)*(1-Ie),Ze=Ie<1,cn=i.scale<u.hullsOnly,Vn=!cn&&i.scale<u.dotNodes;if(!cn)for(const Q of a.nodes){if(!Tt(Q.x,Q.y,i,o.clientWidth,o.clientHeight))continue;const Pe=Ae(Q.type);if(Vn){const Ne=F!==null&&!F.has(Q.id);n.fillStyle=Pe;const _e=Ne?.1:.8;n.globalAlpha=Ze?_e*Ke:_e,n.fillRect(Q.x-2,Q.y-2,4,4);continue}const Fe=x.has(Q.id),at=Ge!==null&&Ge.has(Q.id),ot=F!==null&&!F.has(Q.id),Ye=ot||x.size>0&&!Fe&&!at,Ve=i.scale<u.smallNodes?Ue*.5:Ue,Be=Ze?Ve*Ke:Ve;if(k!=null&&k.has(Q.id)){const Ne=p[p.length-1]===Q.id,_e=.5+.5*Math.sin(y),We=Ee("--accent")||"#d4a27f";n.save(),n.strokeStyle=We,n.lineWidth=Ne?3:2,n.globalAlpha=Ne?.5+.5*_e:.3+.4*_e,n.beginPath(),n.arc(Q.x,Q.y,Be+(Ne?6:4),0,Math.PI*2),n.stroke(),n.restore()}Fe&&(n.save(),n.shadowColor=Pe,n.shadowBlur=20,n.beginPath(),n.arc(Q.x,Q.y,Be+3,0,Math.PI*2),n.fillStyle=Pe,n.globalAlpha=.3,n.fill(),n.restore()),n.beginPath(),n.arc(Q.x,Q.y,Be,0,Math.PI*2),n.fillStyle=Pe;const Qe=ot?.1:Ye?.3:1;n.globalAlpha=Ze?Qe*Ke:Qe,n.fill(),n.strokeStyle=Fe?Re:ze,n.lineWidth=Fe?3:1.5,n.stroke(),n.globalAlpha=1,Q.pinned&&(n.save(),n.strokeStyle=ze,n.globalAlpha=.55,n.lineWidth=1,n.setLineDash([3,3]),n.beginPath(),n.arc(Q.x,Q.y,Be+4,0,Math.PI*2),n.stroke(),n.setLineDash([]),n.restore()),ge&&ge.nodeIds.has(Q.id)&&!Fe&&(n.save(),n.shadowColor=Ee("--accent")||"#d4a27f",n.shadowBlur=15,n.beginPath(),n.arc(Q.x,Q.y,Be+2,0,Math.PI*2),n.strokeStyle=Ee("--accent")||"#d4a27f",n.globalAlpha=.5,n.lineWidth=2,n.stroke(),n.restore());const oe=ee==null?void 0:ee.nodes.find(Ne=>Ne.id===Q.id);if(((ln=oe==null?void 0:oe.properties)==null?void 0:ln._starred)===!0&&(n.fillStyle="#ffd700",n.font="10px system-ui, sans-serif",n.textAlign="left",n.textBaseline="bottom",n.fillText("★",Q.x+Be-2,Q.y-Be+2)),i.scale>=u.hideLabels){const Ne=Q.label.length>24?Q.label.slice(0,22)+"...":Q.label,_e=Ye?ke:fe;An(n,Ne,Q.x,Q.y+Be+4,"11px system-ui, sans-serif",_e,"top")}if(i.scale>=u.hideBadges){const Ne=Ye?Te:Se;An(n,Q.type,Q.x,Q.y-Be-3,"9px system-ui, sans-serif",Ne,"bottom")}n.globalAlpha=1}if(n.restore(),n.restore(),f){const Q=o.width,Pe=o.height;(!B||B.width!==Q||B.height!==Pe)&&(B=new OffscreenCanvas(Q,Pe),$=B.getContext("2d")),$&&($.clearRect(0,0,Q,Pe),$.drawImage(o,0,0),te=!1),mt();return}X&&a.nodes.length>1&&E(),U&&(n.save(),n.setTransform(g,0,0,g,0,0),n.translate(-i.x*i.scale,-i.y*i.scale),n.scale(i.scale,i.scale),ut(),n.restore())}function E(){if(!a)return;const f=140,k=100,P=8,D=o.clientWidth-f-16,_=o.clientHeight-k-16;let j=1/0,O=1/0,J=-1/0,Z=-1/0;for(const Ie of a.nodes)Ie.x<j&&(j=Ie.x),Ie.y<O&&(O=Ie.y),Ie.x>J&&(J=Ie.x),Ie.y>Z&&(Z=Ie.y);const re=J-j||1,fe=Z-O||1,ke=Math.min((f-P*2)/re,(k-P*2)/fe),Se=D+P+(f-P*2-re*ke)/2,Te=_+P+(k-P*2-fe*ke)/2;n.save(),n.setTransform(g,0,0,g,0,0),n.fillStyle=Ee("--bg-surface")||"#1a1a1a",n.globalAlpha=.85,n.beginPath(),n.roundRect(D,_,f,k,8),n.fill(),n.strokeStyle=Ee("--border")||"#2a2a2a",n.globalAlpha=1,n.lineWidth=1,n.stroke(),n.globalAlpha=.15,n.strokeStyle=Ee("--canvas-edge")||"#555",n.lineWidth=.5;for(const Ie of a.edges){const Ke=a.nodeMap.get(Ie.sourceId),Ze=a.nodeMap.get(Ie.targetId);!Ke||!Ze||Ie.sourceId===Ie.targetId||(n.beginPath(),n.moveTo(Se+(Ke.x-j)*ke,Te+(Ke.y-O)*ke),n.lineTo(Se+(Ze.x-j)*ke,Te+(Ze.y-O)*ke),n.stroke())}n.globalAlpha=.8;for(const Ie of a.nodes){const Ke=Se+(Ie.x-j)*ke,Ze=Te+(Ie.y-O)*ke;n.beginPath(),n.arc(Ke,Ze,2,0,Math.PI*2),n.fillStyle=Ae(Ie.type),n.fill()}const Re=i.x,ze=i.y,Ge=i.x+o.clientWidth/i.scale,nt=i.y+o.clientHeight/i.scale,He=Se+(Re-j)*ke,it=Te+(ze-O)*ke,Nt=(Ge-Re)*ke,Ft=(nt-ze)*ke;n.globalAlpha=.3,n.strokeStyle=Ee("--accent")||"#d4a27f",n.lineWidth=1.5,n.strokeRect(Math.max(D,Math.min(He,D+f)),Math.max(_,Math.min(it,_+k)),Math.min(Nt,f),Math.min(Ft,k)),n.globalAlpha=1,n.restore()}function R(f,k,P,D,_,j,O){const J=Math.atan2(D-k,P-f),Z=P-Math.cos(J)*Ue,re=D-Math.sin(J)*Ue,fe=8;n.beginPath(),n.moveTo(Z,re),n.lineTo(Z-fe*Math.cos(J-.4),re-fe*Math.sin(J-.4)),n.lineTo(Z-fe*Math.cos(J+.4),re-fe*Math.sin(J+.4)),n.closePath(),n.fillStyle=_?O:j,n.fill()}function Y(f,k,P,D,_,j,O){const J=f.x+Ue+15,Z=f.y-Ue-15;n.beginPath(),n.arc(J,Z,15,0,Math.PI*2),n.strokeStyle=P?_:D,n.lineWidth=P?2.5:1.5,n.stroke(),V&&(n.fillStyle=P?O:j,n.font="9px system-ui, sans-serif",n.textAlign="center",n.fillText(k,J,Z-18))}function ne(){if(!le||!me)return;const f=performance.now()-me.time,k=Math.min(f/ve,1),P=1-Math.pow(1-k,3);i.x=me.x+(le.x-me.x)*P,i.y=me.y+(le.y-me.y)*P,ie(),c(),k<1?requestAnimationFrame(ne):(le=null,me=null)}let ae=0;function pe(){if(!be||p.length===0){ae=0;return}c(),ae=requestAnimationFrame(pe)}function he(){if(!a||a.nodes.length===0)return;let f=1/0,k=1/0,P=-1/0,D=-1/0;for(const re of a.nodes)re.x<f&&(f=re.x),re.y<k&&(k=re.y),re.x>P&&(P=re.x),re.y>D&&(D=re.y);const _=Ue*4,j=P-f+_*2,O=D-k+_*2,J=o.clientWidth/Math.max(j,1),Z=o.clientHeight/Math.max(O,1);i.scale=Math.min(J,Z,2),i.x=(f+P)/2-o.clientWidth/(2*i.scale),i.y=(k+D)/2-o.clientHeight/(2*i.scale),h()}function we(){if(!a||M<jt){T=0,be&&p.length>0&&!ae&&(ae=requestAnimationFrame(pe));return}M=So(a,M),b.rebuild(a.nodes),c(),T=requestAnimationFrame(we)}let Le=!1,De=0,tt=0,ht=0,kt=0;o.addEventListener("mousedown",f=>{H="pending",Le=!1,De=f.clientX,tt=f.clientY,ht=f.clientX,kt=f.clientY;const k=o.getBoundingClientRect(),P=f.clientX-k.left,D=f.clientY-k.top,_=Je(P,D);if(_&&!be){if(K=[],x.has(_.id)&&x.size>1)for(const J of x){const Z=a==null?void 0:a.nodeMap.get(J);Z&&K.push({node:Z,startX:Z.x,startY:Z.y})}else K.push({node:_,startX:_.x,startY:_.y});const[j,O]=Xe(P,D);q=j,ue=O}else if(!_&&f.shiftKey){const[j,O]=Xe(P,D);U={x1:j,y1:O,x2:j,y2:O}}}),o.addEventListener("mousemove",f=>{if(H==="idle")return;const k=f.clientX-De,P=f.clientY-tt,D=Math.abs(f.clientX-ht),_=Math.abs(f.clientY-kt);if(H==="pending"&&(D>xe||_>xe)&&(Le=!0,K.length>0?(H="nodeDrag",N&&w&&w.postMessage({type:"stop"})):U?H="rubberBand":H="pan"),H==="nodeDrag"){const j=o.getBoundingClientRect(),O=f.clientX-j.left,J=f.clientY-j.top,[Z,re]=Xe(O,J),fe=Z-q,ke=re-ue;for(const Se of K)Se.node.x=Se.startX+fe,Se.node.y=Se.startY+ke,Se.node.vx=0,Se.node.vy=0,Se.node.pinned=!0,de.add(Se.node.id);b.rebuild((a==null?void 0:a.nodes)??[]),h()}else if(H==="rubberBand"&&U){const j=o.getBoundingClientRect(),O=f.clientX-j.left,J=f.clientY-j.top,[Z,re]=Xe(O,J);U.x2=Z,U.y2=re,h()}else H==="pan"&&(i.x-=k/i.scale,i.y-=P/i.scale,h());De=f.clientX,tt=f.clientY}),o.addEventListener("mouseup",f=>{const k=H==="nodeDrag",P=H==="rubberBand";if(K.map(Z=>Z.node.id),k){if(N&&w&&a){const Z=K.map(re=>({id:re.node.id,x:re.node.x,y:re.node.y}));w.postMessage({type:"pin",updates:Z}),w.postMessage({type:"resume",alpha:.5})}else M=Math.max(M,.5),T||we();H="idle",K=[],h();return}if(P&&U&&a){const Z=Math.min(U.x1,U.x2),re=Math.max(U.x1,U.x2),fe=Math.min(U.y1,U.y2),ke=Math.max(U.y1,U.y2);f.shiftKey||x.clear();for(const Te of a.nodes)Te.x>=Z&&Te.x<=re&&Te.y>=fe&&Te.y<=ke&&x.add(Te.id);const Se=[...x];e==null||e(Se.length>0?Se:null),U=null,H="idle",h();return}if(H==="pan"){H="idle";return}if(H="idle",U=null,Le)return;const D=o.getBoundingClientRect(),_=f.clientX-D.left,j=f.clientY-D.top,O=Je(_,j),J=f.ctrlKey||f.metaKey||f.shiftKey;if(be&&G&&O&&a){const Z=p.length>0?p[p.length-1]:G[0],re=new Set([Z]),fe=[{id:Z,path:[Z]}];let ke=null;for(;fe.length>0;){const{id:ze,path:Ge}=fe.shift();if(ze===O.id){ke=Ge;break}for(const nt of a.edges){let He=null;nt.sourceId===ze?He=nt.targetId:nt.targetId===ze&&(He=nt.sourceId),He&&!re.has(He)&&(re.add(He),fe.push({id:He,path:[...Ge,He]}))}}if(!ke)return;for(const ze of ke.slice(1))p.includes(ze)||p.push(ze);G=[O.id];const Se=Math.max(1,ye);ye=Se;const Te=Ln(ee,[O.id],Se);cancelAnimationFrame(T),w&&w.postMessage({type:"stop"}),a=Ut(Te),b.rebuild(a.nodes),M=1,x=new Set([O.id]),F=null,i={x:0,y:0,scale:1},N=Te.nodes.length>=S;const Re=N?I():null;Re?Re.postMessage({type:"start",data:Te}):(N=!1,we()),setTimeout(()=>{a&&he()},300),s==null||s({seedNodeIds:[O.id],hops:Se,totalNodes:Te.nodes.length}),e==null||e([O.id]);return}if(O){J?x.has(O.id)?x.delete(O.id):x.add(O.id):x.size===1&&x.has(O.id)?x.clear():(x.clear(),x.add(O.id));const Z=[...x];e==null||e(Z.length>0?Z:null)}else x.clear(),e==null||e(null);h()}),o.addEventListener("mouseleave",()=>{if(H==="nodeDrag")if(N&&w&&K.length>0){const f=K.map(k=>({id:k.node.id,x:k.node.x,y:k.node.y}));w.postMessage({type:"pin",updates:f}),w.postMessage({type:"resume",alpha:.5})}else M=Math.max(M,.5),T||we();H==="rubberBand"&&(U=null,h()),H="idle",K=[]}),o.addEventListener("wheel",f=>{f.preventDefault();const k=o.getBoundingClientRect(),P=f.clientX-k.left,D=f.clientY-k.top,[_,j]=Xe(P,D),O=f.ctrlKey?1-f.deltaY*.01:f.deltaY>0?.9:1.1;i.scale=Math.max(r.zoomMin,Math.min(r.zoomMax,i.scale*O)),i.x=_-P/i.scale,i.y=j-D/i.scale,h()},{passive:!1});let Oe=[],tn=0,nn=1,on=0,sn=0,$t=!1;o.addEventListener("touchstart",f=>{f.preventDefault(),Oe=Array.from(f.touches),Oe.length===2?(tn=an(Oe[0],Oe[1]),nn=i.scale):Oe.length===1&&(De=Oe[0].clientX,tt=Oe[0].clientY,on=Oe[0].clientX,sn=Oe[0].clientY,$t=!1)},{passive:!1}),o.addEventListener("touchmove",f=>{f.preventDefault();const k=Array.from(f.touches);if(k.length===2&&Oe.length===2){const D=an(k[0],k[1])/tn;i.scale=Math.max(r.zoomMin,Math.min(r.zoomMax,nn*D)),h()}else if(k.length===1){const P=k[0].clientX-De,D=k[0].clientY-tt;(Math.abs(k[0].clientX-on)>10||Math.abs(k[0].clientY-sn)>10)&&($t=!0),i.x-=P/i.scale,i.y-=D/i.scale,De=k[0].clientX,tt=k[0].clientY,h()}Oe=k},{passive:!1}),o.addEventListener("touchend",f=>{if(f.preventDefault(),$t||f.changedTouches.length!==1)return;const k=f.changedTouches[0],P=o.getBoundingClientRect(),D=k.clientX-P.left,_=k.clientY-P.top,j=Je(D,_);if(j){x.size===1&&x.has(j.id)?x.clear():(x.clear(),x.add(j.id));const O=[...x];e==null||e(O.length>0?O:null)}else x.clear(),e==null||e(null);h()},{passive:!1}),o.addEventListener("gesturestart",f=>f.preventDefault()),o.addEventListener("gesturechange",f=>f.preventDefault());function an(f,k){const P=f.clientX-k.clientX,D=f.clientY-k.clientY;return Math.sqrt(P*P+D*D)}const ft=document.createElement("div");ft.className="zoom-controls";const gt=document.createElement("button");gt.className="zoom-btn",gt.textContent="+",gt.title="Zoom in",gt.addEventListener("click",()=>{const f=o.clientWidth/2,k=o.clientHeight/2,[P,D]=Xe(f,k);i.scale=Math.min(r.zoomMax,i.scale*r.zoomFactor),i.x=P-f/i.scale,i.y=D-k/i.scale,h()});const yt=document.createElement("button");yt.className="zoom-btn",yt.textContent="−",yt.title="Zoom out",yt.addEventListener("click",()=>{const f=o.clientWidth/2,k=o.clientHeight/2,[P,D]=Xe(f,k);i.scale=Math.max(r.zoomMin,i.scale/r.zoomFactor),i.x=P-f/i.scale,i.y=D-k/i.scale,h()});const Ct=document.createElement("button");Ct.className="zoom-btn",Ct.textContent="○",Ct.title="Reset zoom",Ct.addEventListener("click",()=>{if(a){if(i={x:0,y:0,scale:1},a.nodes.length>0){let f=1/0,k=1/0,P=-1/0,D=-1/0;for(const O of a.nodes)O.x<f&&(f=O.x),O.y<k&&(k=O.y),O.x>P&&(P=O.x),O.y>D&&(D=O.y);const _=(f+P)/2,j=(k+D)/2;i.x=_-o.clientWidth/2,i.y=j-o.clientHeight/2}h()}}),ft.appendChild(gt),ft.appendChild(Ct),ft.appendChild(yt),t.appendChild(ft);const $e=document.createElement("div");$e.className="node-tooltip",$e.style.display="none",t.appendChild($e);let St=null,st=null;return o.addEventListener("mousemove",f=>{if(H!=="idle"&&H!=="pending"){$e.style.display!=="none"&&($e.style.display="none",St=null);return}const k=o.getBoundingClientRect(),P=f.clientX-k.left,D=f.clientY-k.top,_=Je(P,D),j=(_==null?void 0:_.id)??null;j!==St?(St=j,$e.style.display="none",st&&clearTimeout(st),st=null,j&&_&&(st=setTimeout(()=>{if(!a||!ee)return;const O=a.edges.filter(J=>J.sourceId===j||J.targetId===j).length;$e.textContent=`${_.label} · ${_.type} · ${O} edge${O!==1?"s":""}`,$e.style.left=`${f.clientX-k.left+12}px`,$e.style.top=`${f.clientY-k.top-8}px`,$e.style.display="block"},200))):j&&$e.style.display==="block"&&($e.style.left=`${f.clientX-k.left+12}px`,$e.style.top=`${f.clientY-k.top-8}px`)}),o.addEventListener("mouseleave",()=>{$e.style.display="none",St=null,st&&clearTimeout(st),st=null}),{loadGraph(f){if(cancelAnimationFrame(T),w&&w.postMessage({type:"stop"}),Ao(),ee=f,G=null,Ce=null,se=null,v=performance.now(),a=Ut(f),b.rebuild(a.nodes),M=1,x=new Set,F=null,de.clear(),H="idle",K=[],U=null,i={x:0,y:0,scale:1},a.nodes.length>0){let P=1/0,D=1/0,_=-1/0,j=-1/0;for(const fe of a.nodes)fe.x<P&&(P=fe.x),fe.y<D&&(D=fe.y),fe.x>_&&(_=fe.x),fe.y>j&&(j=fe.y);const O=(P+_)/2,J=(D+j)/2,Z=o.clientWidth,re=o.clientHeight;i.x=O-Z/2,i.y=J-re/2}N=f.nodes.length>=S;const k=N?I():null;k?k.postMessage({type:"start",data:f}):(N=!1,we())},setFilteredNodeIds(f){F=f,h()},releaseAllPins(){if(!a)return!1;let f=!1;for(const k of a.nodes)k.pinned&&(f=!0,k.pinned=!1);return de.clear(),H="idle",K=[],U=null,f&&(N&&w?w.postMessage({type:"unpin",ids:"all"}):(M=Math.max(M,.5),T||we()),h()),f},hasPinnedNodes(){if(!a)return!1;for(const f of a.nodes)if(f.pinned)return!0;return!1},clearSelection(){x.size!==0&&(x.clear(),e==null||e(null),h())},getSelectedNodeIds(){return[...x]},panToNode(f){this.panToNodes([f])},panToNodes(f){if(!a||f.length===0)return;const k=f.map(_=>a.nodeMap.get(_)).filter(Boolean);if(k.length===0)return;x=new Set(f),e==null||e(f);const P=o.clientWidth,D=o.clientHeight;if(k.length===1)me={x:i.x,y:i.y,time:performance.now()},le={x:k[0].x-P/(2*i.scale),y:k[0].y-D/(2*i.scale)};else{let _=1/0,j=1/0,O=-1/0,J=-1/0;for(const Re of k)Re.x<_&&(_=Re.x),Re.y<j&&(j=Re.y),Re.x>O&&(O=Re.x),Re.y>J&&(J=Re.y);const Z=Ue*4,re=O-_+Z*2,fe=J-j+Z*2,ke=Math.min(P/re,D/fe,i.scale);i.scale=ke;const Se=(_+O)/2,Te=(j+J)/2;me={x:i.x,y:i.y,time:performance.now()},le={x:Se-P/(2*i.scale),y:Te-D/(2*i.scale)}}ne()},setEdges(f){W=f,h()},setEdgeLabels(f){V=f,h()},setTypeHulls(f){z=f,h()},setMinimap(f){X=f,h()},centerView(){he()},panBy(f,k){i.x+=f/i.scale,i.y+=k/i.scale,h()},zoomBy(f){const k=o.clientWidth/2,P=o.clientHeight/2,[D,_]=Xe(k,P);i.scale=Math.max(r.zoomMin,Math.min(r.zoomMax,i.scale*f)),i.x=D-k/i.scale,i.y=_-P/i.scale,h()},reheat(){N&&w?w.postMessage({type:"params",params:Et()}):(M=.5,cancelAnimationFrame(T),we())},exportImage(f){if(!a)return"";const k=o.width,P=o.height;if(f==="png"){const O=document.createElement("canvas");O.width=k,O.height=P;const J=O.getContext("2d");return J.fillStyle=Ee("--bg")||"#141414",J.fillRect(0,0,k,P),J.drawImage(o,0,0),Gn(J,k,P),O.toDataURL("image/png")}const D=o.toDataURL("image/png"),_=Math.max(16,Math.round(k/80)),j=`<svg xmlns="http://www.w3.org/2000/svg" width="${k}" height="${P}">
|
|
3
|
+
<image href="${D}" width="${k}" height="${P}"/>
|
|
4
|
+
<text x="${k-20}" y="${P-16}" text-anchor="end" font-family="system-ui, sans-serif" font-size="${_}" fill="#ffffff" opacity="0.4">backpackontology.com</text>
|
|
5
|
+
</svg>`;return"data:image/svg+xml;charset=utf-8,"+encodeURIComponent(j)},enterFocus(f,k){if(!ee||!a)return;for(const _ of a.nodes)_.pinned=!1;de.clear(),H="idle",K=[],U=null,G||(Ce=a,se={...i}),G=f,ye=k;const P=Ln(ee,f,k);cancelAnimationFrame(T),w&&w.postMessage({type:"stop"}),a=Ut(P),b.rebuild(a.nodes),M=1,x=new Set(f),F=null,i={x:0,y:0,scale:1},N=P.nodes.length>=S;const D=N?I():null;D?D.postMessage({type:"start",data:P}):(N=!1,we()),setTimeout(()=>{!a||!G||he()},300),s==null||s({seedNodeIds:f,hops:k,totalNodes:P.nodes.length})},exitFocus(){if(!(!G||!Ce)){cancelAnimationFrame(T),w&&w.postMessage({type:"stop"}),a=Ce;for(const f of a.nodes)f.pinned=!1;de.clear(),H="idle",K=[],U=null,b.rebuild(a.nodes),i=se??{x:0,y:0,scale:1},G=null,Ce=null,se=null,x=new Set,F=null,h(),s==null||s(null)}},isFocused(){return G!==null},getFocusInfo(){return!G||!a?null:{seedNodeIds:G,hops:ye,totalNodes:a.nodes.length}},findPath(f,k){if(!a)return null;const P=new Set([f]),D=[{nodeId:f,path:[f],edges:[]}];for(;D.length>0;){const{nodeId:_,path:j,edges:O}=D.shift();if(_===k)return{nodeIds:j,edgeIds:O};for(const J of a.edges){let Z=null;if(J.sourceId===_?Z=J.targetId:J.targetId===_&&(Z=J.sourceId),Z&&!P.has(Z)){P.add(Z);const re=ee==null?void 0:ee.edges.find(fe=>fe.sourceId===J.sourceId&&fe.targetId===J.targetId||fe.targetId===J.sourceId&&fe.sourceId===J.targetId);D.push({nodeId:Z,path:[...j,Z],edges:[...O,(re==null?void 0:re.id)??""]})}}}return null},setHighlightedPath(f,k){f&&k?ge={nodeIds:new Set(f),edgeIds:new Set(k)}:ge=null,h()},clearHighlightedPath(){ge=null,h()},setWalkMode(f){be=f,this.releaseAllPins(),f?(p=G?[...G]:[...x],ae||(ae=requestAnimationFrame(pe))):(p=[],ae&&(cancelAnimationFrame(ae),ae=0)),h()},getWalkMode(){return be},getWalkTrail(){return[...p]},getFilteredNodeIds(){return F},removeFromWalkTrail(f){p=p.filter(k=>k!==f),h()},nodeAtScreen(f,k){return Je(f,k)},getNodeIds(){if(!a)return[];if(G){const f=new Set(G),k=a.nodes.filter(D=>f.has(D.id)).map(D=>D.id),P=a.nodes.filter(D=>!f.has(D.id)).map(D=>D.id);return[...k,...P]}return a.nodes.map(f=>f.id)},destroy(){cancelAnimationFrame(T),L&&(cancelAnimationFrame(L),L=0),ae&&(cancelAnimationFrame(ae),ae=0),w&&(w.terminate(),w=null),B=null,$=null,qe.disconnect()}};function Gn(f,k,P){const D=Math.max(16,Math.round(k/80));f.save(),f.font=`${D}px system-ui, sans-serif`,f.fillStyle="rgba(255, 255, 255, 0.4)",f.textAlign="right",f.textBaseline="bottom",f.fillText("backpackontology.com",k-20,P-16),f.restore()}}function rt(t){for(const e of Object.values(t.properties))if(typeof e=="string")return e;return t.id}const $o="✎";function Fo(t,e,s,l,u){const r=document.createElement("div");r.className="info-panel-content";let d=[],o=-1,n=!1,g=null,i=[],a=!1,M=[],T=-1;const x=e.mount("info",r,{title:"Node info",persistKey:"info-panel",hideOnClose:!0,onClose:()=>{d=[],o=-1}});x.setVisible(!1);function F(){x.setVisible(!1),d=[],o=-1}function W(q){!g||!l||(o<d.length-1&&(d=d.slice(0,o+1)),d.push(q),o=d.length-1,n=!0,l(q),n=!1)}function V(){if(o<=0||!g)return;o--,n=!0;const q=d[o];l==null||l(q),H(q,g),n=!1}function z(){if(o>=d.length-1||!g)return;o++,n=!0;const q=d[o];l==null||l(q),H(q,g),n=!1}function X(){const q=[{label:"Back",iconText:"←",onClick:V,disabled:o<=0},{label:"Forward",iconText:"→",onClick:z,disabled:o>=d.length-1}];u&&i.length>0&&q.push({label:"Focus",iconText:"◎",onClick:()=>{a||u(i)},disabled:a}),x.setHeaderButtons(q)}function H(q,ue){const U=ue.nodes.find(h=>h.id===q);if(!U)return;const de=ue.edges.filter(h=>h.sourceId===q||h.targetId===q);M=de.map(h=>h.sourceId===q?h.targetId:h.sourceId),T=-1,r.replaceChildren(),x.setTitle(rt(U)),X(),x.setVisible(!0);const xe=document.createElement("div");xe.className="info-panel-header";const ee=document.createElement("div");ee.className="info-header";const G=document.createElement("span");if(G.className="info-type-badge",G.textContent=U.type,G.style.backgroundColor=Ae(U.type),s){G.classList.add("info-editable");const h=document.createElement("button");h.className="info-inline-edit",h.textContent=$o,h.addEventListener("click",S=>{S.stopPropagation();const w=document.createElement("input");w.type="text",w.className="info-edit-inline-input",w.value=U.type,G.textContent="",G.appendChild(w),w.focus(),w.select();const N=()=>{const I=w.value.trim();I&&I!==U.type?s.onChangeNodeType(q,I):(G.textContent=U.type,G.appendChild(h))};w.addEventListener("blur",N),w.addEventListener("keydown",I=>{I.key==="Enter"&&w.blur(),I.key==="Escape"&&(w.value=U.type,w.blur())})}),G.appendChild(h)}const ye=document.createElement("h3");ye.className="info-label",ye.textContent=rt(U);const Ce=document.createElement("span");Ce.className="info-id",Ce.textContent=U.id,ee.appendChild(G),ee.appendChild(ye),ee.appendChild(Ce),xe.appendChild(ee),r.appendChild(xe);const se=document.createElement("div");se.className="info-panel-body";const ge=Object.keys(U.properties),be=xt("Properties");if(ge.length>0){const h=document.createElement("dl");h.className="info-props";for(const S of ge){const w=document.createElement("dt");w.textContent=S;const N=document.createElement("dd");if(s){const I=zt(U.properties[S]),C=document.createElement("textarea");C.className="info-edit-input",C.value=I,C.rows=1,C.addEventListener("input",()=>Pn(C)),C.addEventListener("keydown",$=>{$.key==="Enter"&&!$.shiftKey&&($.preventDefault(),C.blur())}),C.addEventListener("blur",()=>{const $=C.value;$!==I&&s.onUpdateNode(q,{[S]:Oo($)})}),N.appendChild(C),requestAnimationFrame(()=>Pn(C));const B=document.createElement("button");B.className="info-delete-prop",B.textContent="×",B.title=`Remove ${S}`,B.addEventListener("click",()=>{const $={...U.properties};delete $[S],s.onUpdateNode(q,$)}),N.appendChild(B)}else N.appendChild(Do(U.properties[S]));h.appendChild(w),h.appendChild(N)}be.appendChild(h)}if(s){const h=document.createElement("button");h.className="info-add-btn",h.textContent="+ Add property",h.addEventListener("click",()=>{const S=document.createElement("div");S.className="info-add-row";const w=document.createElement("input");w.type="text",w.className="info-edit-input",w.placeholder="key";const N=document.createElement("input");N.type="text",N.className="info-edit-input",N.placeholder="value";const I=document.createElement("button");I.className="info-add-save",I.textContent="Add",I.addEventListener("click",()=>{w.value&&s.onAddProperty(q,w.value,N.value)}),S.appendChild(w),S.appendChild(N),S.appendChild(I),be.appendChild(S),w.focus()}),be.appendChild(h)}if(se.appendChild(be),de.length>0){const h=xt(`Connections (${de.length})`),S=document.createElement("ul");S.className="info-connections";for(const w of de){const N=w.sourceId===q,I=N?w.targetId:w.sourceId,C=ue.nodes.find(ve=>ve.id===I),B=C?rt(C):I,$=document.createElement("li");if($.className="info-connection",l&&C&&($.classList.add("info-connection-link"),$.addEventListener("click",ve=>{ve.target.closest(".info-delete-edge")||W(I)})),C){const ve=document.createElement("span");ve.className="info-target-dot",ve.style.backgroundColor=Ae(C.type),$.appendChild(ve)}const te=document.createElement("span");te.className="info-arrow",te.textContent=N?"→":"←";const ie=document.createElement("span");ie.className="info-edge-type",ie.textContent=w.type;const le=document.createElement("span");le.className="info-target",le.textContent=B,$.appendChild(te),$.appendChild(ie),$.appendChild(le);const me=Object.keys(w.properties);if(me.length>0){const ve=document.createElement("div");ve.className="info-edge-props";for(const je of me){const qe=document.createElement("span");qe.className="info-edge-prop",qe.textContent=`${je}: ${zt(w.properties[je])}`,ve.appendChild(qe)}$.appendChild(ve)}if(s){const ve=document.createElement("button");ve.className="info-delete-edge",ve.textContent="×",ve.title="Remove connection",ve.addEventListener("click",je=>{je.stopPropagation(),s.onDeleteEdge(w.id)}),$.appendChild(ve)}S.appendChild($)}h.appendChild(S),se.appendChild(h)}const p=xt("Timestamps"),y=document.createElement("dl");y.className="info-props";const v=document.createElement("dt");v.textContent="created";const m=document.createElement("dd");m.textContent=Rn(U.createdAt);const b=document.createElement("dt");b.textContent="updated";const L=document.createElement("dd");if(L.textContent=Rn(U.updatedAt),y.appendChild(v),y.appendChild(m),y.appendChild(b),y.appendChild(L),p.appendChild(y),se.appendChild(p),s){const h=document.createElement("div");h.className="info-section info-danger";const S=document.createElement("button");S.className="info-delete-node",S.textContent="Delete node",S.addEventListener("click",()=>{s.onDeleteNode(q),F()}),h.appendChild(S),se.appendChild(h)}r.appendChild(se)}function K(q,ue){const U=new Set(q),de=ue.nodes.filter(p=>U.has(p.id));if(de.length===0)return;const xe=ue.edges.filter(p=>U.has(p.sourceId)&&U.has(p.targetId));r.replaceChildren(),x.setTitle(`${de.length} nodes selected`),X(),x.setVisible(!0);const ee=document.createElement("div");ee.className="info-header";const G=document.createElement("h3");G.className="info-label",G.textContent=`${de.length} nodes selected`,ee.appendChild(G);const ye=document.createElement("div");ye.className="info-badge-row";const Ce=new Map;for(const p of de)Ce.set(p.type,(Ce.get(p.type)??0)+1);for(const[p,y]of Ce){const v=document.createElement("span");v.className="info-type-badge",v.style.backgroundColor=Ae(p),v.textContent=y>1?`${p} (${y})`:p,ye.appendChild(v)}ee.appendChild(ye),r.appendChild(ee);const se=xt("Selected Nodes"),ge=document.createElement("ul");ge.className="info-connections";for(const p of de){const y=document.createElement("li");y.className="info-connection",l&&(y.classList.add("info-connection-link"),y.addEventListener("click",()=>{W(p.id)}));const v=document.createElement("span");v.className="info-target-dot",v.style.backgroundColor=Ae(p.type);const m=document.createElement("span");m.className="info-target",m.textContent=rt(p);const b=document.createElement("span");b.className="info-edge-type",b.textContent=p.type,y.appendChild(v),y.appendChild(m),y.appendChild(b),ge.appendChild(y)}se.appendChild(ge),r.appendChild(se);const be=xt(xe.length>0?`Connections Between Selected (${xe.length})`:"Connections Between Selected");if(xe.length===0){const p=document.createElement("p");p.className="info-empty-message",p.textContent="No direct connections between selected nodes",be.appendChild(p)}else{const p=document.createElement("ul");p.className="info-connections";for(const y of xe){const v=ue.nodes.find($=>$.id===y.sourceId),m=ue.nodes.find($=>$.id===y.targetId),b=v?rt(v):y.sourceId,L=m?rt(m):y.targetId,h=document.createElement("li");if(h.className="info-connection",v){const $=document.createElement("span");$.className="info-target-dot",$.style.backgroundColor=Ae(v.type),h.appendChild($)}const S=document.createElement("span");S.className="info-target",S.textContent=b;const w=document.createElement("span");w.className="info-arrow",w.textContent="→";const N=document.createElement("span");N.className="info-edge-type",N.textContent=y.type;const I=document.createElement("span");if(I.className="info-arrow",I.textContent="→",h.appendChild(S),h.appendChild(w),h.appendChild(N),h.appendChild(I),m){const $=document.createElement("span");$.className="info-target-dot",$.style.backgroundColor=Ae(m.type),h.appendChild($)}const C=document.createElement("span");C.className="info-target",C.textContent=L,h.appendChild(C);const B=Object.keys(y.properties);if(B.length>0){const $=document.createElement("div");$.className="info-edge-props";for(const te of B){const ie=document.createElement("span");ie.className="info-edge-prop",ie.textContent=`${te}: ${zt(y.properties[te])}`,$.appendChild(ie)}h.appendChild($)}p.appendChild(h)}be.appendChild(p)}r.appendChild(be)}return{show(q,ue){if(g=ue,i=q,q.length===1&&!n){const U=q[0];d[o]!==U&&(o<d.length-1&&(d=d.slice(0,o+1)),d.push(U),o=d.length-1)}q.length===1?H(q[0],ue):q.length>1&&K(q,ue)},hide:F,goBack:V,goForward:z,cycleConnection(q){if(M.length===0)return null;T===-1?T=q===1?0:M.length-1:(T+=q,T>=M.length&&(T=0),T<0&&(T=M.length-1));const ue=r.querySelectorAll(".info-connection");return ue.forEach((U,de)=>{U.classList.toggle("info-connection-active",de===T)}),T>=0&&ue[T]&&ue[T].scrollIntoView({block:"nearest"}),M[T]??null},setFocusDisabled(q){a=q,X()},get visible(){return x.isVisible()}}}function xt(t){const e=document.createElement("div");e.className="info-section";const s=document.createElement("h4");return s.className="info-section-title",s.textContent=t,e.appendChild(s),e}function Do(t){if(Array.isArray(t)){const s=document.createElement("div");s.className="info-array";for(const l of t){const u=document.createElement("span");u.className="info-tag",u.textContent=String(l),s.appendChild(u)}return s}if(t!==null&&typeof t=="object"){const s=document.createElement("pre");return s.className="info-json",s.textContent=JSON.stringify(t,null,2),s}const e=document.createElement("span");return e.className="info-value",e.textContent=String(t??""),e}function zt(t){return Array.isArray(t)?t.map(String).join(", "):t!==null&&typeof t=="object"?JSON.stringify(t):String(t??"")}function Oo(t){const e=t.trim();if(e==="true")return!0;if(e==="false")return!1;if(e!==""&&!isNaN(Number(e)))return Number(e);if(e.startsWith("[")&&e.endsWith("]")||e.startsWith("{")&&e.endsWith("}"))try{return JSON.parse(e)}catch{return t}return t}function Pn(t){t.style.height="auto",t.style.height=t.scrollHeight+"px"}function Rn(t){try{return new Date(t).toLocaleString()}catch{return t}}const Jt=30,Wo=39,Uo=45,Bn=380,jo=70,zo=16,$n=80,qn="backpack-viewer:panel:",_o="has-fullscreen-panel";let At=Jt;function dt(t){At++,At>Wo&&(document.querySelectorAll(".extension-panel").forEach(e=>{e.style.zIndex=String(Jt+1)}),At=Jt+2),t.style.zIndex=String(At)}let Pt=0;function Rt(t,e){const s=t.id==="canvas-container"?t:t.closest("#canvas-container")??t;e?Pt++:Pt=Math.max(0,Pt-1),s.classList.toggle(_o,Pt>0)}function _t(t){try{const e=localStorage.getItem(qn+t);if(!e)return null;const s=JSON.parse(e);if(s&&typeof s=="object")return s}catch{}return null}function Fn(t,e){try{localStorage.setItem(qn+t,JSON.stringify(e))}catch{}}function Dn(){return ct({size:13,strokeWidth:1.8,strokeLinecap:"round",strokeLinejoin:"round"},[{tag:"polyline",attrs:{points:"4 9 4 4 9 4"}},{tag:"polyline",attrs:{points:"20 9 20 4 15 4"}},{tag:"polyline",attrs:{points:"4 15 4 20 9 20"}},{tag:"polyline",attrs:{points:"20 15 20 20 15 20"}}])}function Ho(){return ct({size:13,strokeWidth:1.8,strokeLinecap:"round",strokeLinejoin:"round"},[{tag:"polyline",attrs:{points:"9 4 9 9 4 9"}},{tag:"polyline",attrs:{points:"15 4 15 9 20 9"}},{tag:"polyline",attrs:{points:"9 20 9 15 4 15"}},{tag:"polyline",attrs:{points:"15 20 15 15 20 15"}}])}function Yo(t){const e=document.createElement("div");e.className="extension-panel-layer",t.appendChild(e);function s(){const r=t.getBoundingClientRect();return{left:Math.max(0,r.width-Bn-zo),top:jo}}function l(r,d){const o=t.getBoundingClientRect(),n=$n-d.width,g=o.width-$n,i=0,a=o.height-40;return{left:Math.max(n,Math.min(g,r.left)),top:Math.max(i,Math.min(a,r.top))}}function u(r,d,o={}){const n=o.persistKey??r,g=_t(n),i=document.createElement("aside");i.className="extension-panel",i.dataset.panel=n;const a=document.createElement("div");a.className="extension-panel-header";const M=document.createElement("span");M.className="extension-panel-title",M.textContent=o.title??r,a.appendChild(M);const T=document.createElement("div");T.className="extension-panel-custom-btns",a.appendChild(T);function x(m){T.replaceChildren();for(const b of m){const L=document.createElement("button");L.className="extension-panel-btn",L.title=b.label,L.setAttribute("aria-label",b.label),L.textContent=b.iconText??b.label,b.disabled&&(L.disabled=!0),L.addEventListener("click",h=>{h.stopPropagation();try{b.onClick()}catch(S){console.error(`[backpack-viewer] panel header button "${b.label}" threw:`,S)}}),L.addEventListener("mousedown",h=>h.stopPropagation()),T.appendChild(L)}}x(o.headerButtons??[]);let F=!1,W=null,V=null;o.showFullscreenButton!==!1&&(W=document.createElement("button"),W.className="extension-panel-btn extension-panel-btn-fullscreen",W.title="Toggle fullscreen",W.setAttribute("aria-label","Toggle fullscreen"),V=Dn(),W.appendChild(V),W.addEventListener("click",m=>{m.stopPropagation(),p(!F)}),W.addEventListener("mousedown",m=>m.stopPropagation()),a.appendChild(W));let z=null;o.showCloseButton!==!1&&(z=document.createElement("button"),z.className="extension-panel-btn extension-panel-btn-close",z.title="Close panel",z.setAttribute("aria-label","Close panel"),z.textContent="×",z.addEventListener("click",m=>{var b;if(m.stopPropagation(),o.hideOnClose){be(!1);try{(b=o.onClose)==null||b.call(o)}catch(L){console.error("[backpack-viewer] panel onClose threw:",L)}}else ge()}),z.addEventListener("mousedown",m=>m.stopPropagation()),a.appendChild(z)),i.appendChild(a);const X=document.createElement("div");X.className="extension-panel-body",X.appendChild(d),i.appendChild(X),e.appendChild(i);const H=g&&g.left!=null&&g.top!=null?{left:g.left,top:g.top}:o.defaultPosition??s(),q=l(H,{width:Bn});i.style.left=q.left+"px",i.style.top=q.top+"px",dt(i);let ue=0,U=0,de=0,xe=0,ee=!1;function G(m){if(!ee)return;const b=m.clientX-ue,L=m.clientY-U,h=l({left:de+b,top:xe+L},i.getBoundingClientRect());i.style.left=h.left+"px",i.style.top=h.top+"px"}function ye(){if(!ee)return;ee=!1,document.removeEventListener("mousemove",G),document.removeEventListener("mouseup",ye);const m=i.getBoundingClientRect(),b=t.getBoundingClientRect();Fn(n,{..._t(n),left:m.left-b.left,top:m.top-b.top})}a.addEventListener("mousedown",m=>{if(F)return;ee=!0,ue=m.clientX,U=m.clientY;const b=i.getBoundingClientRect(),L=t.getBoundingClientRect();de=b.left-L.left,xe=b.top-L.top,dt(i),document.addEventListener("mousemove",G),document.addEventListener("mouseup",ye),m.preventDefault()}),i.addEventListener("mousedown",()=>{F||dt(i)},{capture:!0});let Ce=!1,se=!0;function ge(){var m;if(!Ce){Ce=!0,document.removeEventListener("mousemove",G),document.removeEventListener("mouseup",ye),F&&(Rt(t,!1),F=!1),i.remove();try{(m=o.onClose)==null||m.call(o)}catch(b){console.error("[backpack-viewer] panel onClose threw:",b)}}}function be(m){m!==se&&(se=m,i.classList.toggle("is-hidden",!se),se?(dt(i),F&&Rt(t,!0)):F&&Rt(t,!1))}function p(m){var b;if(m!==F){F=m,i.classList.toggle("is-fullscreen",F),W&&V&&(V.remove(),V=F?Ho():Dn(),W.appendChild(V)),F?i.style.zIndex=String(Uo):dt(i),Rt(t,F),Fn(n,{..._t(n),fullscreen:F});try{(b=o.onFullscreenChange)==null||b.call(o,F)}catch(L){console.error("[backpack-viewer] panel onFullscreenChange threw:",L)}}}g!=null&&g.fullscreen&&p(!0);function y(m){M.textContent=m}function v(m){x(m)}return{close:ge,setFullscreen:p,isFullscreen:()=>F,setTitle:y,setHeaderButtons:v,setVisible:be,isVisible:()=>se,bringToFront:()=>dt(i),element:d}}return{mount:u}}function Xn(t){for(const e of Object.values(t.properties))if(typeof e=="string")return e;return t.id}function On(t,e){const s=e.toLowerCase();if(Xn(t).toLowerCase().includes(s)||t.type.toLowerCase().includes(s))return!0;for(const l of Object.values(t.properties))if(typeof l=="string"&&l.toLowerCase().includes(s))return!0;return!1}function qo(t,e){const s=(e==null?void 0:e.maxResults)??8,l=(e==null?void 0:e.debounceMs)??150;let u=null,r=null,d=null,o=null;const n=document.createElement("div");n.className="search-overlay hidden";const g=document.createElement("div");g.className="search-input-wrap";const i=document.createElement("input");i.className="search-input",i.type="text",i.placeholder="Search nodes...",i.setAttribute("autocomplete","off"),i.setAttribute("spellcheck","false");const a=document.createElement("kbd");a.className="search-kbd",a.textContent="/",g.appendChild(i),g.appendChild(a);const M=document.createElement("ul");M.className="search-results hidden",n.appendChild(g),n.appendChild(M),t.appendChild(n);function T(){if(!u)return null;const z=i.value.trim();if(z.length===0)return null;const X=new Set;for(const H of u.nodes)On(H,z)&&X.add(H.id);return X}function x(){const z=T();r==null||r(z),F()}function F(){M.replaceChildren(),W=-1;const z=i.value.trim();if(!u||z.length===0){M.classList.add("hidden");return}const X=[];for(const H of u.nodes)if(On(H,z)&&(X.push(H),X.length>=s))break;if(X.length===0){M.classList.add("hidden");return}for(const H of X){const K=document.createElement("li");K.className="search-result-item";const q=document.createElement("span");q.className="search-result-dot",q.style.backgroundColor=Ae(H.type);const ue=document.createElement("span");ue.className="search-result-label";const U=Xn(H);ue.textContent=U.length>36?U.slice(0,34)+"...":U;const de=document.createElement("span");de.className="search-result-type",de.textContent=H.type,K.appendChild(q),K.appendChild(ue),K.appendChild(de),K.addEventListener("click",()=>{d==null||d(H.id),i.value="",M.classList.add("hidden"),x()}),M.appendChild(K)}M.classList.remove("hidden")}i.addEventListener("input",()=>{o&&clearTimeout(o),o=setTimeout(x,l)});let W=-1;function V(){const z=M.querySelectorAll(".search-result-item");z.forEach((X,H)=>{X.classList.toggle("search-result-active",H===W)}),W>=0&&z[W]&&z[W].scrollIntoView({block:"nearest"})}return i.addEventListener("keydown",z=>{const X=M.querySelectorAll(".search-result-item");z.key==="ArrowDown"?(z.preventDefault(),X.length>0&&(W=Math.min(W+1,X.length-1),V())):z.key==="ArrowUp"?(z.preventDefault(),X.length>0&&(W=Math.max(W-1,0),V())):z.key==="Enter"?(z.preventDefault(),W>=0&&X[W]?X[W].click():X.length>0&&X[0].click(),i.blur()):z.key==="Escape"&&(i.value="",i.blur(),M.classList.add("hidden"),W=-1,x())}),document.addEventListener("click",z=>{n.contains(z.target)||M.classList.add("hidden")}),i.addEventListener("focus",()=>a.classList.add("hidden")),i.addEventListener("blur",()=>{i.value.length===0&&a.classList.remove("hidden")}),{setLearningGraphData(z){u=z,i.value="",M.classList.add("hidden"),u&&u.nodes.length>0?n.classList.remove("hidden"):n.classList.add("hidden")},onFilterChange(z){r=z},onNodeSelect(z){d=z},clear(){i.value="",M.classList.add("hidden"),r==null||r(null)},focus(){i.focus()}}}function Xo(t,e){let s=null,l=null,u=!0,r=null,d=!0,o=!0,n=!0,g="types",i="",a="",M=[],T=[];const x={types:new Set,nodeIds:new Set};function F(){if(!s)return[];const p=new Set;for(const y of s.nodes)x.types.has(y.type)&&p.add(y.id);for(const y of x.nodeIds)p.add(y);return[...p]}function W(p){if(x.nodeIds.has(p))return!0;const y=s==null?void 0:s.nodes.find(v=>v.id===p);return y?x.types.has(y.type):!1}function V(){return x.types.size===0&&x.nodeIds.size===0}function z(){const p=F();e.onFocusChange(p.length>0?p:null)}const X=document.createElement("button");X.className="tools-pane-toggle hidden",X.title="Graph Inspector",X.appendChild(ct({size:16,strokeLinecap:"round",strokeLinejoin:"round"},[{tag:"path",attrs:{d:"M4 7h16"}},{tag:"path",attrs:{d:"M4 12h16"}},{tag:"path",attrs:{d:"M4 17h10"}}]));const H=document.createElement("div");H.className="tools-pane-content hidden",t.appendChild(X),t.appendChild(H),X.addEventListener("click",()=>{var p;u=!u,H.classList.toggle("hidden",u),X.classList.toggle("active",!u),u||(p=e.onOpen)==null||p.call(e)});function K(){if(H.replaceChildren(),!l)return;const p=document.createElement("div");p.className="tools-pane-summary";const y=document.createElement("span");y.textContent=`${l.nodeCount} nodes`;const v=document.createElement("span");v.className="tools-pane-sep",v.textContent="·";const m=document.createElement("span");m.textContent=`${l.edgeCount} edges`;const b=document.createElement("span");b.className="tools-pane-sep",b.textContent="·";const L=document.createElement("span");if(L.textContent=`${l.types.length} types`,p.append(y,v,m,b,L),H.appendChild(p),s&&l.nodeCount>0){const N=Math.ceil(JSON.stringify(s).length/4),I=Math.round(N/l.nodeCount),C=Math.max(10,Math.round(I*.3)*Math.min(5,l.nodeCount)),B=N>C?Math.round((1-C/N)*100):0,$=document.createElement("div");$.className="tools-pane-token-card";const te=Math.min(100,B),ie=document.createElement("div");ie.className="token-card-label",ie.textContent="Token Efficiency",$.appendChild(ie);const le=document.createElement("div");le.className="token-card-stat",le.textContent=`~${N.toLocaleString()} tokens stored`,$.appendChild(le);const me=document.createElement("div");me.className="token-card-bar";const ve=document.createElement("div");ve.className="token-card-bar-fill",ve.style.width=`${te}%`,me.appendChild(ve),$.appendChild(me);const je=document.createElement("div");je.className="token-card-stat",je.textContent=`A search returns ~${C} tokens instead of ~${N.toLocaleString()} (${B}% reduction)`,$.appendChild(je),H.appendChild($)}const h=document.createElement("div");h.className="tools-pane-tabs";const S=[{id:"types",label:"Types"},{id:"insights",label:"Insights"},{id:"controls",label:"Controls"}];for(const N of S){const I=document.createElement("button");I.className="tools-pane-tab",g===N.id&&I.classList.add("tools-pane-tab-active"),I.textContent=N.label,I.addEventListener("click",()=>{g=N.id,K()}),h.appendChild(I)}H.appendChild(h),V()||U(),T.length>0&&ue(),g==="types"&&l.types.length>5?H.appendChild(xe("Filter types...",i,N=>{i=N,q()})):g==="insights"&&l.orphans.length+l.singletons.length+l.emptyNodes.length>5&&H.appendChild(xe("Filter issues...",a,I=>{a=I,q()}));const w=document.createElement("div");w.className="tools-pane-tab-content",H.appendChild(w),q()}function q(){const p=H.querySelector(".tools-pane-tab-content");p&&(p.replaceChildren(),g==="types"?ee(p):g==="insights"?G(p):g==="controls"&&ye(p))}function ue(){H.appendChild(se(`Walk Trail (${T.length})`,p=>{for(let v=0;v<T.length;v++){const m=T[v],b=v===T.length-1;if(m.edgeType){const C=document.createElement("div");C.className="walk-trail-edge",C.textContent=`↓ ${m.edgeType}`,p.appendChild(C)}const L=document.createElement("div");L.className="tools-pane-row tools-pane-clickable",b&&(L.style.fontWeight="600");const h=document.createElement("span");h.className="tools-pane-count",h.style.minWidth="18px",h.textContent=`${v+1}`;const S=document.createElement("span");S.className="tools-pane-dot",S.style.backgroundColor=Ae(m.type);const w=document.createElement("span");w.className="tools-pane-name",w.textContent=m.label;const N=document.createElement("span");N.className="tools-pane-count",N.textContent=m.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",C=>{var B;C.stopPropagation(),(B=e.onWalkTrailRemove)==null||B.call(e,m.id)}),L.appendChild(h),L.appendChild(S),L.appendChild(w),L.appendChild(N),L.appendChild(I),L.addEventListener("click",()=>{e.onNavigateToNode(m.id)}),p.appendChild(L)}const y=document.createElement("div");if(y.className="tools-pane-export-row",e.onWalkIsolate){const v=document.createElement("button");v.className="tools-pane-export-btn",v.textContent="Isolate (I)",v.addEventListener("click",()=>e.onWalkIsolate()),y.appendChild(v)}if(e.onWalkSaveSnippet&&T.length>=2){const v=document.createElement("button");v.className="tools-pane-export-btn",v.textContent="Save snippet",v.addEventListener("click",()=>{Bt("Save snippet","Name for this snippet").then(m=>{m&&e.onWalkSaveSnippet(m)})}),y.appendChild(v)}p.appendChild(y)}))}function U(){if(!l||!s)return;const p=F();H.appendChild(se("Focused",y=>{for(const L of x.types){const h=l.types.find(B=>B.name===L);if(!h)continue;const S=document.createElement("div");S.className="tools-pane-row tools-pane-clickable";const w=document.createElement("span");w.className="tools-pane-dot",w.style.backgroundColor=Ae(h.name);const N=document.createElement("span");N.className="tools-pane-name",N.textContent=h.name;const I=document.createElement("span");I.className="tools-pane-count",I.textContent=`${h.count} nodes`;const C=document.createElement("button");C.className="tools-pane-edit tools-pane-focus-active",C.style.opacity="1",C.textContent="×",C.title=`Remove ${h.name} from focus`,S.appendChild(w),S.appendChild(N),S.appendChild(I),S.appendChild(C),C.addEventListener("click",B=>{B.stopPropagation(),x.types.delete(h.name),z(),K()}),y.appendChild(S)}for(const L of x.nodeIds){const h=s.nodes.find($=>$.id===L);if(!h)continue;const S=Wn(h.properties)??h.id,w=document.createElement("div");w.className="tools-pane-row tools-pane-clickable";const N=document.createElement("span");N.className="tools-pane-dot",N.style.backgroundColor=Ae(h.type);const I=document.createElement("span");I.className="tools-pane-name",I.textContent=S;const C=document.createElement("span");C.className="tools-pane-count",C.textContent=h.type;const B=document.createElement("button");B.className="tools-pane-edit tools-pane-focus-active",B.style.opacity="1",B.textContent="×",B.title=`Remove ${S} from focus`,w.appendChild(N),w.appendChild(I),w.appendChild(C),w.appendChild(B),w.addEventListener("click",$=>{$.target.closest(".tools-pane-edit")||e.onNavigateToNode(L)}),B.addEventListener("click",$=>{$.stopPropagation(),x.nodeIds.delete(L),z(),K()}),y.appendChild(w)}const v=document.createElement("div");v.className="tools-pane-row tools-pane-clickable tools-pane-focus-clear";const m=document.createElement("span");m.className="tools-pane-name",m.style.color="var(--accent)",m.textContent=`${p.length} total`;const b=document.createElement("span");b.className="tools-pane-badge",b.textContent="clear all",v.appendChild(m),v.appendChild(b),v.addEventListener("click",()=>{x.types.clear(),x.nodeIds.clear(),z(),K()}),y.appendChild(v)}))}function de(p){const y=document.createElement("div");y.className="tools-pane-row tools-pane-clickable",r===p.name&&y.classList.add("active");const v=document.createElement("span");v.className="tools-pane-dot",v.style.backgroundColor=Ae(p.name);const m=document.createElement("span");m.className="tools-pane-name",m.textContent=p.name;const b=document.createElement("span");b.className="tools-pane-count",b.textContent=String(p.count);const L=document.createElement("button");L.className="tools-pane-edit tools-pane-focus-toggle",x.types.has(p.name)&&L.classList.add("tools-pane-focus-active"),L.textContent="◎",L.title=x.types.has(p.name)?`Remove ${p.name} from focus`:`Add ${p.name} to focus`;const h=document.createElement("button");return h.className="tools-pane-edit",h.textContent="✎",h.title=`Rename all ${p.name} nodes`,y.appendChild(v),y.appendChild(m),y.appendChild(b),y.appendChild(L),y.appendChild(h),y.addEventListener("click",S=>{S.target.closest(".tools-pane-edit")||(r===p.name?(r=null,e.onFilterByType(null)):(r=p.name,e.onFilterByType(p.name)),K())}),L.addEventListener("click",S=>{S.stopPropagation(),x.types.has(p.name)?x.types.delete(p.name):x.types.add(p.name),z(),K()}),h.addEventListener("click",S=>{S.stopPropagation(),ge(y,p.name,w=>{w&&w!==p.name&&e.onRenameNodeType(p.name,w)})}),y}function xe(p,y,v){const m=document.createElement("input");return m.type="text",m.className="tools-pane-search",m.placeholder=p,m.value=y,m.addEventListener("input",()=>v(m.value)),m}function ee(p){if(!l)return;const y=i.toLowerCase();if(l.types.length){const m=l.types.filter(b=>!x.types.has(b.name)).filter(b=>!y||b.name.toLowerCase().includes(y));m.length>0&&p.appendChild(se("Node Types",b=>{for(const L of m)b.appendChild(de(L))}))}const v=l.edgeTypes.filter(m=>!y||m.name.toLowerCase().includes(y));v.length&&p.appendChild(se("Edge Types",m=>{for(const b of v){const L=document.createElement("div");L.className="tools-pane-row tools-pane-clickable";const h=document.createElement("span");h.className="tools-pane-name",h.textContent=b.name;const S=document.createElement("span");S.className="tools-pane-count",S.textContent=String(b.count);const w=document.createElement("button");w.className="tools-pane-edit",w.textContent="✎",w.title=`Rename all ${b.name} edges`,L.appendChild(h),L.appendChild(S),L.appendChild(w),w.addEventListener("click",N=>{N.stopPropagation(),ge(L,b.name,I=>{I&&I!==b.name&&e.onRenameEdgeType(b.name,I)})}),m.appendChild(L)}}))}function G(p){if(!l)return;const y=a.toLowerCase(),v=l.starred.filter(I=>!y||I.label.toLowerCase().includes(y)||I.type.toLowerCase().includes(y));v.length&&p.appendChild(se("★ Starred",I=>{for(const $ of v){const te=document.createElement("div");te.className="tools-pane-row tools-pane-clickable";const ie=document.createElement("span");ie.className="tools-pane-dot",ie.style.backgroundColor=Ae($.type);const le=document.createElement("span");le.className="tools-pane-name",le.textContent=$.label;const me=document.createElement("button");me.className="tools-pane-edit tools-pane-focus-toggle",W($.id)&&me.classList.add("tools-pane-focus-active"),me.textContent="◎",me.title=W($.id)?`Remove ${$.label} from focus`:`Add ${$.label} to focus`,te.appendChild(ie),te.appendChild(le),te.appendChild(me),te.addEventListener("click",ve=>{ve.target.closest(".tools-pane-edit")||e.onNavigateToNode($.id)}),me.addEventListener("click",ve=>{ve.stopPropagation(),x.nodeIds.has($.id)?x.nodeIds.delete($.id):x.nodeIds.add($.id),z(),K()}),I.appendChild(te)}const C=document.createElement("div");C.className="tools-pane-row tools-pane-actions";const B=document.createElement("button");if(B.className="tools-pane-action-btn",B.textContent="Focus all",B.title="Enter focus mode with all starred nodes",B.addEventListener("click",()=>{x.nodeIds.clear(),x.types.clear();for(const $ of l.starred)x.nodeIds.add($.id);z(),K()}),C.appendChild(B),e.onStarredSaveSnippet){const $=document.createElement("button");$.className="tools-pane-action-btn",$.textContent="Save as snippet",$.title="Save starred nodes as a reusable snippet",$.addEventListener("click",async()=>{const te=await Bt("Snippet name","starred");te&&e.onStarredSaveSnippet(te,l.starred.map(ie=>ie.id))}),C.appendChild($)}I.appendChild(C)}));const m=l.mostConnected.filter(I=>!y||I.label.toLowerCase().includes(y)||I.type.toLowerCase().includes(y));m.length&&p.appendChild(se("Most Connected",I=>{for(const C of m){const B=document.createElement("div");B.className="tools-pane-row tools-pane-clickable";const $=document.createElement("span");$.className="tools-pane-dot",$.style.backgroundColor=Ae(C.type);const te=document.createElement("span");te.className="tools-pane-name",te.textContent=C.label;const ie=document.createElement("span");ie.className="tools-pane-count",ie.textContent=`${C.connections}`;const le=document.createElement("button");le.className="tools-pane-edit tools-pane-focus-toggle",W(C.id)&&le.classList.add("tools-pane-focus-active"),le.textContent="◎",le.title=W(C.id)?`Remove ${C.label} from focus`:`Add ${C.label} to focus`,B.appendChild($),B.appendChild(te),B.appendChild(ie),B.appendChild(le),B.addEventListener("click",me=>{me.target.closest(".tools-pane-edit")||e.onNavigateToNode(C.id)}),le.addEventListener("click",me=>{me.stopPropagation(),x.nodeIds.has(C.id)?x.nodeIds.delete(C.id):x.nodeIds.add(C.id),z(),K()}),I.appendChild(B)}}));const b=l.orphans.filter(I=>!y||I.label.toLowerCase().includes(y)||I.type.toLowerCase().includes(y)),L=l.singletons.filter(I=>!y||I.name.toLowerCase().includes(y)),h=l.emptyNodes.filter(I=>!y||I.label.toLowerCase().includes(y)||I.type.toLowerCase().includes(y)),S=b.length>0,w=L.length>0,N=h.length>0;if(!S&&!w&&!N){const I=document.createElement("div");I.className="tools-pane-empty-msg",I.textContent="No issues found",p.appendChild(I);return}S&&p.appendChild(se("Orphans",I=>{for(const C of b.slice(0,5)){const B=document.createElement("div");B.className="tools-pane-row tools-pane-clickable tools-pane-issue";const $=document.createElement("span");$.className="tools-pane-dot",$.style.backgroundColor=Ae(C.type);const te=document.createElement("span");te.className="tools-pane-name",te.textContent=C.label;const ie=document.createElement("span");ie.className="tools-pane-badge",ie.textContent="orphan";const le=document.createElement("button");le.className="tools-pane-edit tools-pane-focus-toggle",W(C.id)&&le.classList.add("tools-pane-focus-active"),le.textContent="◎",le.title=W(C.id)?`Remove ${C.label} from focus`:`Add ${C.label} to focus`,B.appendChild($),B.appendChild(te),B.appendChild(ie),B.appendChild(le),B.addEventListener("click",me=>{me.target.closest(".tools-pane-edit")||e.onNavigateToNode(C.id)}),le.addEventListener("click",me=>{me.stopPropagation(),x.nodeIds.has(C.id)?x.nodeIds.delete(C.id):x.nodeIds.add(C.id),z(),K()}),I.appendChild(B)}if(b.length>5){const C=document.createElement("div");C.className="tools-pane-more",C.textContent=`+ ${b.length-5} more orphans`,I.appendChild(C)}})),w&&p.appendChild(se("Singletons",I=>{for(const C of L.slice(0,5)){const B=document.createElement("div");B.className="tools-pane-row tools-pane-issue";const $=document.createElement("span");$.className="tools-pane-dot",$.style.backgroundColor=Ae(C.name);const te=document.createElement("span");te.className="tools-pane-name",te.textContent=C.name;const ie=document.createElement("span");ie.className="tools-pane-badge",ie.textContent="1 node",B.appendChild($),B.appendChild(te),B.appendChild(ie),I.appendChild(B)}})),N&&p.appendChild(se("Empty Nodes",I=>{for(const C of h.slice(0,5)){const B=document.createElement("div");B.className="tools-pane-row tools-pane-issue";const $=document.createElement("span");$.className="tools-pane-dot",$.style.backgroundColor=Ae(C.type);const te=document.createElement("span");te.className="tools-pane-name",te.textContent=C.label;const ie=document.createElement("span");ie.className="tools-pane-badge",ie.textContent="empty",B.appendChild($),B.appendChild(te),B.appendChild(ie),I.appendChild(B)}if(l.emptyNodes.length>5){const C=document.createElement("div");C.className="tools-pane-more",C.textContent=`+ ${l.emptyNodes.length-5} more empty nodes`,I.appendChild(C)}}))}function ye(p){p.appendChild(se("Display",y=>{const v=document.createElement("div");v.className="tools-pane-row tools-pane-clickable";const m=document.createElement("input");m.type="checkbox",m.checked=d,m.className="tools-pane-checkbox";const b=document.createElement("span");b.className="tools-pane-name",b.textContent="Edge labels",v.appendChild(m),v.appendChild(b),v.addEventListener("click",C=>{C.target!==m&&(m.checked=!m.checked),d=m.checked,e.onToggleEdgeLabels(d)}),y.appendChild(v);const L=document.createElement("div");L.className="tools-pane-row tools-pane-clickable";const h=document.createElement("input");h.type="checkbox",h.checked=o,h.className="tools-pane-checkbox";const S=document.createElement("span");S.className="tools-pane-name",S.textContent="Type regions",L.appendChild(h),L.appendChild(S),L.addEventListener("click",C=>{C.target!==h&&(h.checked=!h.checked),o=h.checked,e.onToggleTypeHulls(o)}),y.appendChild(L);const w=document.createElement("div");w.className="tools-pane-row tools-pane-clickable";const N=document.createElement("input");N.type="checkbox",N.checked=n,N.className="tools-pane-checkbox";const I=document.createElement("span");I.className="tools-pane-name",I.textContent="Minimap",w.appendChild(N),w.appendChild(I),w.addEventListener("click",C=>{C.target!==N&&(N.checked=!N.checked),n=N.checked,e.onToggleMinimap(n)}),y.appendChild(w)})),p.appendChild(se("Layout",y=>{y.appendChild(Ce("Clustering",0,1,.02,.08,v=>{e.onLayoutChange("clusterStrength",v)})),y.appendChild(Ce("Spacing",.5,20,.5,1.5,v=>{e.onLayoutChange("spacing",v)})),y.appendChild(Ce("Pan speed",20,200,10,60,v=>{e.onPanSpeedChange(v)}))})),p.appendChild(se("Export",y=>{const v=document.createElement("div");v.className="tools-pane-export-row";const m=document.createElement("button");m.className="tools-pane-export-btn",m.textContent="Export PNG",m.addEventListener("click",()=>e.onExport("png"));const b=document.createElement("button");b.className="tools-pane-export-btn",b.textContent="Export SVG",b.addEventListener("click",()=>e.onExport("svg")),v.appendChild(m),v.appendChild(b),y.appendChild(v)})),(e.onSnapshot||e.onRollback)&&p.appendChild(se("Versions",y=>{const v=document.createElement("div");v.className="tools-pane-export-row";const m=document.createElement("button");if(m.className="tools-pane-export-btn",m.textContent="Save snapshot",m.addEventListener("click",()=>{Bt("Save snapshot","Label (optional)").then(b=>{var L;(L=e.onSnapshot)==null||L.call(e,b||void 0)})}),v.appendChild(m),y.appendChild(v),M.length>0)for(const b of M){const L=document.createElement("div");L.className="tools-pane-row";const h=document.createElement("span");h.className="tools-pane-name";const S=Go(b.timestamp);h.textContent=b.label?`#${b.version} ${b.label}`:`#${b.version}`,h.title=`${S} — ${b.nodeCount} nodes, ${b.edgeCount} edges`;const w=document.createElement("span");w.className="tools-pane-count",w.textContent=S;const N=document.createElement("button");N.className="tools-pane-edit",N.style.opacity="1",N.textContent="↩",N.title="Restore this snapshot",N.addEventListener("click",()=>{jn("Restore snapshot",`Restore snapshot #${b.version}? Current state will be lost unless you save a snapshot first.`).then(I=>{var C;I&&((C=e.onRollback)==null||C.call(e,b.version))})}),L.appendChild(h),L.appendChild(w),L.appendChild(N),y.appendChild(L)}else{const b=document.createElement("div");b.className="tools-pane-empty-msg",b.textContent="No snapshots yet",y.appendChild(b)}}))}function Ce(p,y,v,m,b,L){const h=document.createElement("div");h.className="tools-pane-slider-row";const S=document.createElement("span");S.className="tools-pane-slider-label",S.textContent=p;const w=document.createElement("input");w.type="range",w.className="tools-pane-slider",w.min=String(y),w.max=String(v),w.step=String(m),w.value=String(b);const N=document.createElement("span");return N.className="tools-pane-slider-value",N.textContent=String(b),w.addEventListener("input",()=>{const I=parseFloat(w.value);N.textContent=I%1===0?String(I):I.toFixed(2),L(I)}),h.appendChild(S),h.appendChild(w),h.appendChild(N),h}function se(p,y){const v=document.createElement("div");v.className="tools-pane-section";const m=document.createElement("div");return m.className="tools-pane-heading",m.textContent=p,v.appendChild(m),y(v),v}function ge(p,y,v){const m=document.createElement("input");m.className="tools-pane-inline-input",m.value=y,m.type="text";const b=ho(p);p.replaceChildren(m),p.classList.add("tools-pane-editing"),m.focus(),m.select();function L(){fo(p,b)}function h(){const S=m.value.trim();p.classList.remove("tools-pane-editing"),S&&S!==y?v(S):L()}m.addEventListener("keydown",S=>{S.key==="Enter"&&(S.preventDefault(),h()),S.key==="Escape"&&(L(),p.classList.remove("tools-pane-editing"))}),m.addEventListener("blur",h)}function be(p){const y=new Map,v=new Map,m=new Map,b=new Set;for(const C of p.nodes)y.set(C.type,(y.get(C.type)??0)+1);for(const C of p.edges)v.set(C.type,(v.get(C.type)??0)+1),m.set(C.sourceId,(m.get(C.sourceId)??0)+1),m.set(C.targetId,(m.get(C.targetId)??0)+1),b.add(C.sourceId),b.add(C.targetId);const L=C=>Wn(C.properties)??C.id,h=p.nodes.filter(C=>C.properties._starred===!0).map(C=>({id:C.id,label:L(C),type:C.type})),S=p.nodes.filter(C=>!b.has(C.id)).map(C=>({id:C.id,label:L(C),type:C.type})),w=[...y.entries()].filter(([,C])=>C===1).map(([C])=>({name:C})),N=p.nodes.filter(C=>Object.keys(C.properties).length===0).map(C=>({id:C.id,label:C.id,type:C.type})),I=p.nodes.map(C=>({id:C.id,label:L(C),type:C.type,connections:m.get(C.id)??0})).filter(C=>C.connections>0).sort((C,B)=>B.connections-C.connections).slice(0,5);return{nodeCount:p.nodes.length,edgeCount:p.edges.length,types:[...y.entries()].sort((C,B)=>B[1]-C[1]).map(([C,B])=>({name:C,count:B})),edgeTypes:[...v.entries()].sort((C,B)=>B[1]-C[1]).map(([C,B])=>({name:C,count:B})),starred:h,orphans:S,singletons:w,emptyNodes:N,mostConnected:I}}return{collapse(){u=!0,H.classList.add("hidden"),X.classList.remove("active")},addToFocusSet(p){for(const y of p)x.nodeIds.add(y);z(),K()},clearFocusSet(){x.types.clear(),x.nodeIds.clear(),z(),K()},setData(p){s=p,r=null,x.types.clear(),x.nodeIds.clear(),s&&s.nodes.length>0?(l=be(s),X.classList.remove("hidden"),K()):(l=null,X.classList.add("hidden"),H.classList.add("hidden"))},setSnapshots(p){M=p,g==="controls"&&q()},setWalkTrail(p){T=p,K()}}}function Go(t){const e=Date.now()-new Date(t).getTime(),s=Math.floor(e/6e4);if(s<1)return"just now";if(s<60)return`${s}m ago`;const l=Math.floor(s/60);return l<24?`${l}h ago`:`${Math.floor(l/24)}d ago`}function Wn(t){for(const e of Object.values(t))if(typeof e=="string")return e;return null}function Vo(t,e){const s=e.split("+"),l=s.pop(),u=s.map(n=>n.toLowerCase()),r=u.includes("ctrl")||u.includes("cmd")||u.includes("meta"),d=u.includes("shift"),o=u.includes("alt");return r!==(t.ctrlKey||t.metaKey)||!r&&(t.ctrlKey||t.metaKey)||d&&!t.shiftKey||o!==t.altKey?!1:l.toLowerCase()==="escape"?t.key==="Escape":l.toLowerCase()==="tab"?t.key==="Tab":u.length>0?t.key.toLowerCase()===l.toLowerCase():t.key===l}function Jo(){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",resetPins:"Release all pinned nodes (reset manual layout)"}}const Ko=[{key:"Click",description:"Select node"},{key:"Shift+Click",description:"Multi-select nodes"},{key:"Drag node",description:"Move & pin node (temporary)"},{key:"Drag selection",description:"Move selected group (temporary)"},{key:"Shift+drag empty",description:"Rubber-band select"},{key:"Drag empty",description:"Pan canvas"},{key:"Scroll",description:"Zoom in/out"}],Zo=["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","resetPins","escape"];function Qo(t){return t.split("+").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("+")}function es(t,e){const s=Jo(),l=document.createElement("div");l.className="shortcuts-overlay hidden";const u=document.createElement("div");u.className="shortcuts-modal";const r=document.createElement("h3");r.className="shortcuts-title",r.textContent="Keyboard Shortcuts";const d=document.createElement("div");d.className="shortcuts-list";for(const a of Zo){const M=e[a];if(!M)continue;const T=document.createElement("div");T.className="shortcuts-row";const x=document.createElement("div");x.className="shortcuts-keys";const F=document.createElement("kbd");F.textContent=Qo(M),x.appendChild(F);const W=document.createElement("span");W.className="shortcuts-desc",W.textContent=s[a],T.appendChild(x),T.appendChild(W),d.appendChild(T)}for(const a of Ko){const M=document.createElement("div");M.className="shortcuts-row";const T=document.createElement("div");T.className="shortcuts-keys";const x=document.createElement("kbd");x.textContent=a.key,T.appendChild(x);const F=document.createElement("span");F.className="shortcuts-desc",F.textContent=a.description,M.appendChild(T),M.appendChild(F),d.appendChild(M)}const o=document.createElement("button");o.className="shortcuts-close",o.textContent="×",u.appendChild(o),u.appendChild(r),u.appendChild(d),l.appendChild(u),t.appendChild(l);function n(){l.classList.remove("hidden")}function g(){l.classList.add("hidden")}function i(){l.classList.toggle("hidden")}return o.addEventListener("click",g),l.addEventListener("click",a=>{a.target===l&&g()}),{show:n,hide:g,toggle:i}}function ts(t){const e=document.createElement("div");e.className="empty-state";const s=document.createElement("div");s.className="empty-state-bg";for(const T of["c1","c2","c3","c4","c5"]){const x=document.createElement("div");x.className=`empty-state-circle ${T}`,s.appendChild(x)}s.appendChild(ct({size:0,viewBox:"0 0 400 300",className:"empty-state-lines"},[{tag:"line",attrs:{x1:80,y1:60,x2:220,y2:140,"stroke-width":"0.5",opacity:"0.15"}},{tag:"line",attrs:{x1:220,y1:140,x2:320,y2:80,"stroke-width":"0.5",opacity:"0.15"}},{tag:"line",attrs:{x1:220,y1:140,x2:160,y2:240,"stroke-width":"0.5",opacity:"0.15"}},{tag:"line",attrs:{x1:160,y1:240,x2:300,y2:220,"stroke-width":"0.5",opacity:"0.15"}}]));const l=s.querySelector(".empty-state-lines");l&&(l.removeAttribute("width"),l.removeAttribute("height"),l.setAttribute("preserveAspectRatio","xMidYMid slice")),e.appendChild(s);const u=document.createElement("div");u.className="empty-state-content";const r=document.createElement("div");r.className="empty-state-icon",r.appendChild(ct({size:48,strokeWidth:1.5,strokeLinecap:"round",strokeLinejoin:"round"},[{tag:"path",attrs:{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"}},{tag:"polyline",attrs:{points:"3.27 6.96 12 12.01 20.73 6.96"}},{tag:"line",attrs:{x1:12,y1:22.08,x2:12,y2:12}}])),u.appendChild(r);const d=document.createElement("h2");d.className="empty-state-title",d.textContent="No learning graphs yet",u.appendChild(d);const o=document.createElement("p");o.className="empty-state-desc",o.textContent="Connect Backpack to Claude, then start a conversation. Claude will build your first learning graph automatically.",u.appendChild(o);const n=document.createElement("div");n.className="empty-state-setup";const g=document.createElement("div");g.className="empty-state-label",g.textContent="Add Backpack to Claude Code:";const i=document.createElement("code");i.className="empty-state-code",i.textContent="claude mcp add backpack-local -s user -- npx backpack-ontology@latest",n.append(g,i),u.appendChild(n);const a=document.createElement("p");a.className="empty-state-hint",a.append("Press ");const M=document.createElement("kbd");return M.textContent="?",a.appendChild(M),a.append(" for keyboard shortcuts"),u.appendChild(a),e.appendChild(u),t.appendChild(e),{show(){e.classList.remove("hidden")},hide(){e.classList.add("hidden")}}}const ns=30;function os(){let t=[],e=[];return{push(s){t.push(JSON.stringify(s)),t.length>ns&&t.shift(),e=[]},undo(s){return t.length===0?null:(e.push(JSON.stringify(s)),JSON.parse(t.pop()))},redo(s){return e.length===0?null:(t.push(JSON.stringify(s)),JSON.parse(e.pop()))},canUndo(){return t.length>0},canRedo(){return e.length>0},clear(){t=[],e=[]}}}function ss(t,e){let s=null;function l(d,o,n,g,i){u(),s=document.createElement("div"),s.className="context-menu",s.style.left=`${g}px`,s.style.top=`${i}px`;const a=[{label:n?"★ Unstar":"☆ Star",action:()=>e.onStar(d),premium:!1},{label:"◎ Focus on node",action:()=>e.onFocusNode(d),premium:!1},{label:"⑂ Explore in branch",action:()=>e.onExploreInBranch(d),premium:!1},{label:"⎘ Copy ID",action:()=>e.onCopyId(d),premium:!1}];e.onExpand&&a.push({label:"⊕ Expand node",action:()=>e.onExpand(d),premium:!0}),e.onExplainPath&&a.push({label:"↔ Explain path to…",action:()=>e.onExplainPath(d),premium:!0}),e.onEnrich&&a.push({label:"≡ Enrich from web",action:()=>e.onEnrich(d),premium:!0});let M=!1;for(const x of a){if(!M&&x.premium){const W=document.createElement("div");W.className="context-menu-separator",s.appendChild(W),M=!0}const F=document.createElement("div");F.className="context-menu-item",F.textContent=x.label,F.addEventListener("click",()=>{x.action(),u()}),s.appendChild(F)}t.appendChild(s);const T=s.getBoundingClientRect();T.right>window.innerWidth&&(s.style.left=`${g-T.width}px`),T.bottom>window.innerHeight&&(s.style.top=`${i-T.height}px`),setTimeout(()=>document.addEventListener("click",u),0),document.addEventListener("keydown",r)}function u(){s&&(s.remove(),s=null),document.removeEventListener("click",u),document.removeEventListener("keydown",r)}function r(d){d.key==="Escape"&&u()}return{show:l,hide:u}}function as(t){const e=document.createElement("button");return e.className="copy-prompt-btn",e.title="Copy a prompt about the current view to clipboard",e.textContent="Copy Prompt",e.addEventListener("click",async()=>{const s=t();if(!s.graphName||!s.data){wt("No graph loaded");return}const l=cs(s);try{await navigator.clipboard.writeText(l),wt("Prompt copied — paste into Claude")}catch{wt("Failed to copy to clipboard")}}),e}function Un(t){return Object.values(t.properties).find(s=>typeof s=="string")??t.id}function cs(t){const{graphName:e,data:s,selection:l,focus:u}=t,r=[];r.push(`I'm looking at the Backpack learning graph "${e}" in the viewer.`),r.push(`It has ${s.nodes.length} nodes and ${s.edges.length} edges.`);const d=new Map;for(const o of s.nodes)d.set(o.type,(d.get(o.type)??0)+1);if(d.size>0){const o=Array.from(d.entries()).sort((n,g)=>g[1]-n[1]).slice(0,8).map(([n,g])=>`${n} (${g})`).join(", ");r.push(`Node types: ${o}`)}if(u&&u.seedNodeIds.length>0){r.push(""),r.push(`I'm focused on ${u.seedNodeIds.length} seed node(s) at ${u.hops} hop(s):`);for(const o of u.seedNodeIds.slice(0,10)){const n=s.nodes.find(g=>g.id===o);n&&r.push(`- ${Un(n)} (type: ${n.type})`)}}if(l.length>0){r.push(""),r.push(`I have ${l.length} node(s) selected:`);for(const o of l.slice(0,20)){const n=s.nodes.find(i=>i.id===o);if(!n)continue;const g=Object.entries(n.properties).filter(([i])=>!i.startsWith("_")).slice(0,5).map(([i,a])=>`${i}=${JSON.stringify(a)}`).join(", ");r.push(`- ${Un(n)} (type: ${n.type}, id: ${o}${g?`, ${g}`:""})`)}}return r.push(""),r.push("If you have access to the Backpack MCP tools, please use them to answer questions about this graph (backpack_get_node, backpack_get_neighbors, backpack_search, etc)."),r.push(""),r.push("My question: "),r.join(`
|
|
6
|
+
`)}const is=200;let Ht=null,Yt=null;function ls(t){Ht=t,Yt===null&&(Yt=window.setTimeout(async()=>{const e=Ht;if(Ht=null,Yt=null,!!e)try{await fetch("/api/viewer-state",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({...e,updatedAt:new Date().toISOString()})})}catch{}},is))}function rs(){const t=new Map;return{emit(e){const s=t.get(e);if(s)for(const l of s)try{l()}catch(u){console.error(`[backpack-viewer] event handler for ${e} threw:`,u)}},subscribe(e,s){let l=t.get(e);return l||(l=new Set,t.set(e,l)),l.add(s),()=>{const u=t.get(e);u==null||u.delete(s)}}}}const Kt="1";function ds(t,e,s,l){function u(){return Date.now().toString(36)+Math.random().toString(36).slice(2,10)}function r(){const d=e.getGraph();if(!d)throw new Error("no graph loaded in viewer");return d}return{name:t,viewerApiVersion:Kt,getGraph:()=>e.getGraph(),getGraphName:()=>e.getGraphName(),getSelection:()=>e.getSelection(),getFocus:()=>e.getFocus(),on(d,o){return e.subscribe(d,o)},async addNode(d,o){if(!d)throw new Error("addNode: type is required");const n=r();e.snapshotForUndo();const g=u(),i=new Date().toISOString();return n.nodes.push({id:g,type:d,properties:o,createdAt:i,updatedAt:i}),await e.saveCurrentGraph(),g},async updateNode(d,o){const g=r().nodes.find(i=>i.id===d);if(!g)throw new Error(`updateNode: node not found: ${d}`);e.snapshotForUndo(),g.properties={...g.properties,...o},g.updatedAt=new Date().toISOString(),await e.saveCurrentGraph()},async removeNode(d){const o=r();if(!o.nodes.find(g=>g.id===d))throw new Error(`removeNode: node not found: ${d}`);e.snapshotForUndo(),o.nodes=o.nodes.filter(g=>g.id!==d),o.edges=o.edges.filter(g=>g.sourceId!==d&&g.targetId!==d),await e.saveCurrentGraph()},async addEdge(d,o,n){if(!d||!o||!n)throw new Error("addEdge: sourceId, targetId, and type are required");const g=r();if(!g.nodes.find(a=>a.id===d))throw new Error(`addEdge: source not found: ${d}`);if(!g.nodes.find(a=>a.id===o))throw new Error(`addEdge: target not found: ${o}`);e.snapshotForUndo();const i=u();return g.edges.push({id:i,sourceId:d,targetId:o,type:n}),await e.saveCurrentGraph(),i},async removeEdge(d){const o=r();if(!o.edges.find(g=>g.id===d))throw new Error(`removeEdge: edge not found: ${d}`);e.snapshotForUndo(),o.edges=o.edges.filter(g=>g.id!==d),await e.saveCurrentGraph()},panToNode:d=>e.panToNode(d),focusNodes:(d,o)=>e.focusNodes(d,o),exitFocus:()=>e.exitFocus(),registerTaskbarIcon(d){return s.register(d)},mountPanel(d,o){return l.mount(t,d,o)},settings:{async get(d){const o=`/api/extensions/${encodeURIComponent(t)}/settings`,n=await fetch(o);if(!n.ok)return null;const g=await n.json();return d in g?g[d]:null},async set(d,o){const n=`/api/extensions/${encodeURIComponent(t)}/settings/${encodeURIComponent(d)}`,g=await fetch(n,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:o})});if(!g.ok){const i=await g.text().catch(()=>"");throw new Error(`settings.set failed: ${i||g.status}`)}},async remove(d){const o=`/api/extensions/${encodeURIComponent(t)}/settings/${encodeURIComponent(d)}`,n=await fetch(o,{method:"DELETE"});if(!n.ok){const g=await n.text().catch(()=>"");throw new Error(`settings.remove failed: ${g||n.status}`)}}},async fetch(d,o={}){const n=`/api/extensions/${encodeURIComponent(t)}/fetch`,g={};if(o.headers)if(o.headers instanceof Headers)o.headers.forEach((a,M)=>{g[M]=a});else if(Array.isArray(o.headers))for(const[a,M]of o.headers)g[a]=M;else Object.assign(g,o.headers);const i=typeof o.body=="string"||o.body==null?o.body:(()=>{throw new Error("viewer.fetch only supports string bodies in v1")})();return fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:d,method:o.method??"POST",headers:g,body:i})})}}}const ps="bottom-right";function us(t){const e={"top-left":t.topLeft,"top-right":t.topRight,"bottom-left":t.bottomLeft,"bottom-right":t.bottomRight},s={"top-left":0,"top-right":0,"bottom-left":0,"bottom-right":0};function l(r){e[r].classList.toggle("has-icons",s[r]>0)}l("top-left"),l("top-right"),l("bottom-left"),l("bottom-right");function u(r){const d=r.position??ps,o=e[d];if(!o)throw new Error(`unknown taskbar position: ${d}`);const n=document.createElement("button");if(n.className="extension-taskbar-icon",n.title=r.label,n.setAttribute("aria-label",r.label),r.iconText){const i=document.createElement("span");i.className="extension-taskbar-icon-symbol",i.textContent=r.iconText,n.appendChild(i)}const g=document.createElement("span");return g.className="extension-taskbar-icon-label",g.textContent=r.label,n.appendChild(g),n.addEventListener("click",()=>{try{r.onClick()}catch(i){console.error(`[backpack-viewer] taskbar icon "${r.label}" onClick threw:`,i)}}),o.appendChild(n),s[d]++,l(d),()=>{n.remove(),s[d]=Math.max(0,s[d]-1),l(d)}}return{register:u}}async function ms(t,e){const s=us(t.taskbarSlots);let l=[];try{const r=await fetch("/api/extensions");if(!r.ok)return console.warn(`[backpack-viewer] /api/extensions returned ${r.status}; no extensions will be loaded`),[];l=await r.json()}catch(r){return console.error(`[backpack-viewer] failed to fetch extensions list: ${r.message}`),[]}const u=[];for(const r of l){if(r.viewerApi!==Kt){console.warn(`[backpack-viewer] extension "${r.name}" targets viewerApi "${r.viewerApi}" but this viewer supports "${Kt}"; skipping`);continue}if(r.stylesheet)try{hs(r.name,r.stylesheet)}catch(o){console.error(`[backpack-viewer] extension "${r.name}" stylesheet load failed:`,o)}const d=ds(r.name,t,s,e);try{const g=(await import(`/extensions/${encodeURIComponent(r.name)}/${r.entry}`)).activate;if(typeof g!="function"){console.error(`[backpack-viewer] extension "${r.name}" has no exported activate(api) function; skipping`);continue}await g(d),u.push({info:r,api:d}),console.log(`[backpack-viewer] loaded extension "${r.name}" v${r.version}`)}catch(o){console.error(`[backpack-viewer] extension "${r.name}" failed to activate:`,o)}}return u}function hs(t,e){if(e.includes(".."))throw new Error(`stylesheet path "${e}" is invalid`);const s=document.createElement("link");s.rel="stylesheet",s.href=`/extensions/${encodeURIComponent(t)}/${e}`,s.dataset.extension=t,document.head.appendChild(s)}const fs={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",resetPins:"r"},gs={host:"127.0.0.1",port:5173},ys={edges:!0,edgeLabels:!0,typeHulls:!0,minimap:!0,theme:"system"},Cs={spacing:1.5,clustering:.08},vs={panSpeed:60,panFastMultiplier:3,zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300},bs={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15},xs={pulseSpeed:.02},Es={maxSearchResults:8,maxQualityItems:5,maxMostConnected:5,searchDebounceMs:150},ws={disabled:[],external:[]},ks={keybindings:fs,server:gs,display:ys,layout:Cs,navigation:vs,lod:bs,walk:xs,limits:Es,extensions:ws};let ce="",A=null,qt=new Set,Xt=!1;async function Ss(){var ut,mt;const t=document.getElementById("canvas-container"),e={...ks};try{const c=await fetch("/api/config");if(c.ok){const E=await c.json();Object.assign(e.keybindings,E.keybindings??{}),Object.assign(e.display,E.display??{}),Object.assign(e.layout,E.layout??{}),Object.assign(e.navigation,E.navigation??{}),Object.assign(e.lod,E.lod??{}),Object.assign(e.limits,E.limits??{})}}catch{}const s=e.keybindings,l=window.matchMedia("(prefers-color-scheme: dark)"),u=e.display.theme==="system"?l.matches?"dark":"light":e.display.theme,d=localStorage.getItem("backpack-theme")??u;document.documentElement.setAttribute("data-theme",d);const o=document.createElement("button");o.className="theme-toggle",o.textContent=d==="light"?"☾":"☼",o.title="Toggle light/dark mode",o.addEventListener("click",()=>{const E=document.documentElement.getAttribute("data-theme")==="light"?"dark":"light";document.documentElement.setAttribute("data-theme",E),localStorage.setItem("backpack-theme",E),o.textContent=E==="light"?"☾":"☼"}),t.appendChild(o);const n=os();async function g(){if(!ce||!A)return;A.metadata.updatedAt=new Date().toISOString(),await Ot(ce,A),a.loadGraph(A),ee.setLearningGraphData(A),G.setData(A);const c=await vt();S.setSummaries(c),de.emit("graph-changed")}async function i(c){A=c,await Ot(ce,A),a.loadGraph(A),ee.setLearningGraphData(A),G.setData(A);const E=await vt();S.setSummaries(E)}let a;const M=Yo(t),T=Fo(t,M,{onUpdateNode(c,E){if(!A)return;n.push(A);const R=A.nodes.find(Y=>Y.id===c);R&&(R.properties={...R.properties,...E},R.updatedAt=new Date().toISOString(),g().then(()=>T.show([c],A)))},onChangeNodeType(c,E){if(!A)return;n.push(A);const R=A.nodes.find(Y=>Y.id===c);R&&(R.type=E,R.updatedAt=new Date().toISOString(),g().then(()=>T.show([c],A)))},onDeleteNode(c){A&&(n.push(A),A.nodes=A.nodes.filter(E=>E.id!==c),A.edges=A.edges.filter(E=>E.sourceId!==c&&E.targetId!==c),g())},onDeleteEdge(c){var R;if(!A)return;n.push(A);const E=(R=A.edges.find(Y=>Y.id===c))==null?void 0:R.sourceId;A.edges=A.edges.filter(Y=>Y.id!==c),g().then(()=>{E&&A&&T.show([E],A)})},onAddProperty(c,E,R){if(!A)return;n.push(A);const Y=A.nodes.find(ne=>ne.id===c);Y&&(Y.properties[E]=R,Y.updatedAt=new Date().toISOString(),g().then(()=>T.show([c],A)))}},c=>{a.panToNode(c)},c=>{G.addToFocusSet(c)}),x=window.matchMedia("(max-width: 768px)");let F=[],W=e.display.edges,V=e.navigation.panSpeed,z=-1,X=null;function H(c){X&&X.remove(),X=document.createElement("div"),X.className="focus-indicator";const E=document.createElement("span");E.className="focus-indicator-label",E.textContent=`Focused: ${c.totalNodes} nodes`;const R=document.createElement("span");R.className="focus-indicator-hops",R.textContent=`${c.hops}`;const Y=document.createElement("button");Y.className="focus-indicator-btn",Y.textContent="−",Y.title="Fewer hops",Y.disabled=c.hops===0,Y.addEventListener("click",()=>{a.enterFocus(c.seedNodeIds,Math.max(0,c.hops-1))});const ne=document.createElement("button");ne.className="focus-indicator-btn",ne.textContent="+",ne.title="More hops",ne.disabled=!1,ne.addEventListener("click",()=>{a.enterFocus(c.seedNodeIds,c.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",()=>G.clearFocusSet());const pe=document.createElement("button");pe.className="walk-indicator",a.getWalkMode()&&pe.classList.add("active"),pe.textContent="Walk",pe.title="Toggle walk mode (W) — click nodes to traverse",pe.addEventListener("click",()=>{a.setWalkMode(!a.getWalkMode()),pe.classList.toggle("active",a.getWalkMode())}),X.appendChild(E),X.appendChild(Y),X.appendChild(R),X.appendChild(ne),X.appendChild(pe),X.appendChild(ae)}function K(){X&&(X.remove(),X=null)}const q=document.createElement("div");q.className="path-bar hidden",t.appendChild(q);function ue(c){if(q.replaceChildren(),!A)return;for(let R=0;R<c.nodeIds.length;R++){const Y=c.nodeIds[R],ne=A.nodes.find(he=>he.id===Y);if(!ne)continue;const ae=Object.values(ne.properties).find(he=>typeof he=="string")??ne.id;if(R>0){const he=c.edgeIds[R-1],we=A.edges.find(De=>De.id===he),Le=document.createElement("span");Le.className="path-bar-edge",Le.textContent=we?`→ ${we.type} →`:"→",q.appendChild(Le)}const pe=document.createElement("span");pe.className="path-bar-node",pe.textContent=ae,pe.addEventListener("click",()=>a.panToNode(Y)),q.appendChild(pe)}const E=document.createElement("button");E.className="path-bar-close",E.textContent="×",E.addEventListener("click",U),q.appendChild(E),q.classList.remove("hidden")}function U(){q.classList.add("hidden"),q.replaceChildren(),a.clearHighlightedPath()}const de=rs();function xe(){ce&&ls({graph:ce,selection:F,focus:(a==null?void 0:a.getFocusInfo())??null})}a=Bo(t,c=>{if(F=c??[],xe(),de.emit("selection-changed"),!a.getWalkMode())if(c&&c.length===2){const E=a.findPath(c[0],c[1]);E&&E.nodeIds.length>0?(a.setHighlightedPath(E.nodeIds,E.edgeIds),ue(E)):U()}else U();c&&c.length>0&&A?(T.show(c,A),x.matches&&G.collapse(),le(ce,c)):(T.hide(),ce&&le(ce))},c=>{if(c){H(c);const E=t.querySelector(".canvas-top-left");E&&X&&E.appendChild(X),le(ce,c.seedNodeIds),T.setFocusDisabled(c.hops===0),N()}else K(),T.setFocusDisabled(!1),ce&&le(ce),N();xe(),de.emit("focus-changed")},{lod:e.lod,navigation:e.navigation,walk:e.walk});const ee=qo(t,{maxResults:e.limits.maxSearchResults,debounceMs:e.limits.searchDebounceMs}),G=Xo(t,{onFilterByType(c){if(A)if(c===null)a.setFilteredNodeIds(null);else{const E=new Set(((A==null?void 0:A.nodes)??[]).filter(R=>R.type===c).map(R=>R.id));a.setFilteredNodeIds(E)}},onNavigateToNode(c){a.panToNode(c),A&&T.show([c],A)},onWalkTrailRemove(c){a.removeFromWalkTrail(c),N()},onWalkIsolate(){if(!A)return;const c=a.getWalkTrail();c.length!==0&&a.enterFocus(c,0)},async onWalkSaveSnippet(c){if(!ce||!A)return;const E=a.getWalkTrail();if(E.length<2)return;const R=new Set(E),Y=A.edges.filter(ne=>R.has(ne.sourceId)&&R.has(ne.targetId)).map(ne=>ne.id);await yn(ce,c,E,Y),await B(ce)},async onStarredSaveSnippet(c,E){if(!ce||!A)return;const R=new Set(E),Y=A.edges.filter(ne=>R.has(ne.sourceId)&&R.has(ne.targetId)).map(ne=>ne.id);await yn(ce,c,E,Y),await B(ce)},onFocusChange(c){c&&c.length>0?a.enterFocus(c,0):a.isFocused()&&a.exitFocus()},onRenameNodeType(c,E){if(A){n.push(A);for(const R of A.nodes)R.type===c&&(R.type=E,R.updatedAt=new Date().toISOString());g()}},onRenameEdgeType(c,E){if(A){n.push(A);for(const R of A.edges)R.type===c&&(R.type=E);g()}},onToggleEdgeLabels(c){a.setEdgeLabels(c)},onToggleTypeHulls(c){a.setTypeHulls(c)},onToggleMinimap(c){a.setMinimap(c)},onLayoutChange(c,E){lt({[c]:E}),a.reheat()},onPanSpeedChange(c){V=c},onExport(c){const E=a.exportImage(c);if(!E)return;const R=document.createElement("a");R.download=`${ce||"graph"}.${c}`,R.href=E,R.click()},onSnapshot:async c=>{ce&&(await io(ce,c),await C(ce))},onRollback:async c=>{ce&&(await lo(ce,c),A=await Mt(ce),a.loadGraph(A),ee.setLearningGraphData(A),G.setData(A),await C(ce))},onOpen(){x.matches&&T.hide()}}),ye=document.createElement("div");ye.className="canvas-top-bar";const Ce=document.createElement("div");Ce.className="canvas-top-left";const se=document.createElement("div");se.className="canvas-top-center";const ge=document.createElement("div");ge.className="canvas-top-right";const be=t.querySelector(".tools-pane-toggle");be&&Ce.appendChild(be);const p=t.querySelector(".zoom-controls");p&&ge.appendChild(p),ge.appendChild(o);const y=document.createElement("div");y.className="ext-slot ext-slot-top-left",se.appendChild(y);const v=t.querySelector(".search-overlay");v&&se.appendChild(v);const m=document.createElement("div");m.className="ext-slot ext-slot-top-right",se.appendChild(m);const b=as(()=>({graphName:ce,data:A,selection:F,focus:a.getFocusInfo()}));se.appendChild(b);const L=document.createElement("div");L.className="ext-slot ext-slot-bottom-left",t.appendChild(L);const h=document.createElement("div");h.className="ext-slot ext-slot-bottom-right",t.appendChild(h),ye.appendChild(Ce),ye.appendChild(se),ye.appendChild(ge),t.appendChild(ye),ee.onFilterChange(c=>{a.setFilteredNodeIds(c)}),ee.onNodeSelect(c=>{a.isFocused()&&G.clearFocusSet(),a.panToNode(c),A&&T.show([c],A)});const S=go(document.getElementById("sidebar"),{onSelect:c=>ve(c),onRename:async(c,E)=>{await oo(c,E),ce===c&&(ce=E);const R=await vt();S.setSummaries(R),S.setActive(ce),ce===E&&(A=await Mt(E),a.loadGraph(A),ee.setLearningGraphData(A),G.setData(A))},onBranchSwitch:async(c,E)=>{await gn(c,E),await I(c),A=await Mt(c),a.loadGraph(A),ee.setLearningGraphData(A),G.setData(A),await C(c)},onBranchCreate:async(c,E)=>{await fn(c,E),await I(c)},onBranchDelete:async(c,E)=>{await ao(c,E),await I(c)},onSnippetLoad:async(c,E)=>{var Y;const R=await po(c,E);((Y=R==null?void 0:R.nodeIds)==null?void 0:Y.length)>0&&a.enterFocus(R.nodeIds,0)},onSnippetDelete:async(c,E)=>{await uo(c,E),await B(c)},onBackpackSwitch:async c=>{await fetch("/api/backpacks/switch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:c})}),await w()},onBackpackRegister:async(c,E)=>{await fetch("/api/backpacks",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:c,activate:E})}),await w()}});async function w(){try{const E=await(await fetch("/api/backpacks")).json();S.setBackpacks(E)}catch{}try{const c=await vt();S.setSummaries(c),ce&&!c.some(E=>E.name===ce)&&(ce="",A=null,a.loadGraph({metadata:{name:"",description:"",createdAt:"",updatedAt:""},nodes:[],edges:[]}))}catch{}}function N(){const c=a.getWalkTrail();if(!A||c.length===0){G.setWalkTrail([]),U();return}const E=[],R=c.map((Y,ne)=>{const ae=A.nodes.find(he=>he.id===Y);let pe;if(ne>0){const he=c[ne-1],we=A.edges.find(Le=>Le.sourceId===he&&Le.targetId===Y||Le.targetId===he&&Le.sourceId===Y);pe=we==null?void 0:we.type,we&&E.push(we.id)}return{id:Y,label:ae?Object.values(ae.properties).find(he=>typeof he=="string")??ae.id:Y,type:(ae==null?void 0:ae.type)??"?",edgeType:pe}});G.setWalkTrail(R),c.length>=2?(a.setHighlightedPath(c,E),ue({nodeIds:c,edgeIds:E})):U()}async function I(c){const E=await so(c),R=E.find(Y=>Y.active);R&&S.setActiveBranch(c,R.name,E)}async function C(c){const E=await co(c);G.setSnapshots(E)}async function B(c){const E=await ro(c);S.setSnippets(c,E)}Ce.insertBefore(S.expandBtn,Ce.firstChild);const $=es(t,s),te=ts(t),ie=ss(t,{onStar(c){if(!A)return;const E=A.nodes.find(Y=>Y.id===c);if(!E)return;const R=E.properties._starred===!0;E.properties._starred=!R,Ot(ce,A),a.loadGraph(A)},onFocusNode(c){G.addToFocusSet([c])},onExploreInBranch(c){if(ce){const E=`explore-${c.slice(0,8)}`;fn(ce,E).then(()=>{gn(ce,E).then(()=>{a.enterFocus([c],1)})})}},onCopyId(c){navigator.clipboard.writeText(c)}});t.addEventListener("contextmenu",c=>{c.preventDefault();const E=t.querySelector("canvas");if(!E||!A)return;const R=E.getBoundingClientRect(),Y=c.clientX-R.left,ne=c.clientY-R.top,ae=a.nodeAtScreen(Y,ne);if(!ae)return;const pe=A.nodes.find(Le=>Le.id===ae.id);if(!pe)return;const he=Object.values(pe.properties).find(Le=>typeof Le=="string")??pe.id,we=pe.properties._starred===!0;ie.show(pe.id,he,we,c.clientX-R.left,c.clientY-R.top)}),e.display.edges||a.setEdges(!1),e.display.edgeLabels||a.setEdgeLabels(!1),e.display.typeHulls||a.setTypeHulls(!1),e.display.minimap||a.setMinimap(!1);function le(c,E){const R=[];E!=null&&E.length&&R.push("node="+E.map(encodeURIComponent).join(","));const Y=a.getFocusInfo();Y&&(R.push("focus="+Y.seedNodeIds.map(encodeURIComponent).join(",")),R.push("hops="+Y.hops));const ne="#"+encodeURIComponent(c)+(R.length?"?"+R.join("&"):"");history.replaceState(null,"",ne)}function me(){const c=window.location.hash.slice(1);if(!c)return{graph:null,nodes:[],focus:[],hops:1};const[E,R]=c.split("?"),Y=E?decodeURIComponent(E):null;let ne=[],ae=[],pe=1;if(R){const he=new URLSearchParams(R),we=he.get("node");we&&(ne=we.split(",").map(decodeURIComponent));const Le=he.get("focus");Le&&(ae=Le.split(",").map(decodeURIComponent));const De=he.get("hops");De&&(pe=Math.max(0,parseInt(De,10)||1))}return{graph:Y,nodes:ne,focus:ae,hops:pe}}async function ve(c,E,R,Y){ce=c,Xt=qt.has(c),S.setActive(c),T.hide(),K(),ee.clear(),n.clear(),A=Xt?await no(c):await Mt(c);const ne=xo(A.nodes.length);if(lt({spacing:Math.max(e.layout.spacing,ne.spacing),clusterStrength:Math.max(e.layout.clustering,ne.clusterStrength)}),a.loadGraph(A),ee.setLearningGraphData(A),G.setData(A),te.hide(),le(c),xe(),de.emit("graph-switched"),de.emit("graph-changed"),Xt||(await I(c),await C(c),await B(c)),R!=null&&R.length&&A){const ae=R.filter(pe=>A.nodes.some(he=>he.id===pe));if(ae.length){setTimeout(()=>{a.enterFocus(ae,Y??1)},500);return}}if(E!=null&&E.length&&A){const ae=E.filter(pe=>A.nodes.some(he=>he.id===pe));ae.length&&setTimeout(()=>{a.panToNodes(ae),A&&T.show(ae,A),le(c,ae)},500)}}try{const E=await(await fetch("/api/backpacks")).json();S.setBackpacks(E)}catch{}fetch("/api/version-check").then(c=>c.json()).then(c=>{c.stale&&c.latest&&S.setStaleVersionBanner(c.current,c.latest)}).catch(()=>{});const qe=new URLSearchParams(window.location.search).get("share");if(qe)try{const c=await fetch(`/v1/share/${qe}/meta`);if(!c.ok)throw new Error("Share link not found or expired");const E=await c.json(),R=await fetch(`/v1/share/${qe}`);if(!R.ok)throw new Error("Failed to download shared backpack");const Y=new Uint8Array(await R.arrayBuffer());if(Y.length<9||Y[0]!==66||Y[1]!==80||Y[2]!==65||Y[3]!==75)throw new Error("Invalid share data: not a BPAK envelope");const ne=new DataView(Y.buffer,Y.byteOffset,Y.byteLength).getUint32(5,!1);if(9+ne>Y.length)throw new Error("Invalid envelope: header length exceeds data");const ae=JSON.parse(new TextDecoder().decode(Y.slice(9,9+ne))),pe=Y.slice(9+ne);let he;if(ae.format!=="plaintext"){const we=window.location.hash.slice(1),Le=new URLSearchParams(we).get("k")??we.split("k=")[1];if(!Le)throw new Error("Missing decryption key in URL fragment");const{Decrypter:De}=await eo(async()=>{const{Decrypter:Oe}=await import("./index-D-H7agBH.js");return{Decrypter:Oe}},[]),tt=atob(Le.replace(/-/g,"+").replace(/_/g,"/")),ht=new De;ht.addIdentity(tt);const kt=await ht.decrypt(pe);he=JSON.parse(new TextDecoder().decode(kt))}else he=JSON.parse(new TextDecoder().decode(pe));ce=E.backpack_name||"Shared Backpack",A=he,a.loadGraph(he),ee.setLearningGraphData(he),S.setSummaries([{name:ce,description:"",nodeCount:((ut=he.nodes)==null?void 0:ut.length)??0,edgeCount:((mt=he.edges)==null?void 0:mt.length)??0,nodeTypes:[]}]),S.setActive(ce),document.title=`${ce} — Backpack`}catch(c){const E=c instanceof Error?c.message:"Failed to load shared backpack";wt(E,5e3),te.show()}else{const[c,E]=await Promise.all([vt(),to().catch(()=>[])]);S.setSummaries(c),S.setRemotes(E),qt=new Set(E.map(ne=>ne.name));const R=me(),Y=R.graph&&c.some(ne=>ne.name===R.graph)||R.graph&&qt.has(R.graph)?R.graph:c.length>0?c[0].name:E.length>0?E[0].name:null;Y?await ve(Y,R.nodes.length?R.nodes:void 0,R.focus.length?R.focus:void 0,R.hops):te.show()}ms({getGraph:()=>A,getGraphName:()=>ce,getSelection:()=>[...F],getFocus:()=>a.getFocusInfo(),saveCurrentGraph:async()=>{await g()},snapshotForUndo:()=>{A&&n.push(A)},panToNode:c=>a.panToNode(c),focusNodes:(c,E)=>a.enterFocus(c,E),exitFocus:()=>{a.isFocused()&&a.exitFocus()},taskbarSlots:{topLeft:y,topRight:m,bottomLeft:L,bottomRight:h},subscribe:(c,E)=>de.subscribe(c,E)},M).catch(c=>{console.error("[backpack-viewer] extension loader failed:",c)});const Je={search(){ee.focus()},searchAlt(){ee.focus()},undo(){if(A){const c=n.undo(A);c&&i(c)}},redo(){if(A){const c=n.redo(A);c&&i(c)}},focus(){a.isFocused()?G.clearFocusSet():F.length>0&&G.addToFocusSet(F)},hopsDecrease(){const c=a.getFocusInfo();c&&c.hops>0&&a.enterFocus(c.seedNodeIds,c.hops-1)},hopsIncrease(){const c=a.getFocusInfo();c&&a.enterFocus(c.seedNodeIds,c.hops+1)},nextNode(){const c=a.getNodeIds();c.length>0&&(z=(z+1)%c.length,a.panToNode(c[z]),A&&T.show([c[z]],A))},prevNode(){const c=a.getNodeIds();c.length>0&&(z=z<=0?c.length-1:z-1,a.panToNode(c[z]),A&&T.show([c[z]],A))},nextConnection(){const c=T.cycleConnection(1);c&&a.panToNode(c)},prevConnection(){const c=T.cycleConnection(-1);c&&a.panToNode(c)},historyBack(){T.goBack()},historyForward(){T.goForward()},center(){a.centerView()},toggleEdges(){W=!W,a.setEdges(W)},panLeft(){a.panBy(-V,0)},panDown(){a.panBy(0,V)},panUp(){a.panBy(0,-V)},panRight(){a.panBy(V,0)},panFastLeft(){a.panBy(-V*e.navigation.panFastMultiplier,0)},zoomOut(){a.zoomBy(1/e.navigation.zoomFactor)},zoomIn(){a.zoomBy(e.navigation.zoomFactor)},panFastRight(){a.panBy(V*e.navigation.panFastMultiplier,0)},spacingDecrease(){const c=Et();lt({spacing:Math.max(.5,c.spacing-.5)}),a.reheat()},spacingIncrease(){const c=Et();lt({spacing:Math.min(20,c.spacing+.5)}),a.reheat()},clusteringDecrease(){const c=Et();lt({clusterStrength:Math.max(0,c.clusterStrength-.03)}),a.reheat()},clusteringIncrease(){const c=Et();lt({clusterStrength:Math.min(1,c.clusterStrength+.03)}),a.reheat()},help(){$.toggle()},toggleSidebar(){S.toggle()},resetPins(){a.releaseAllPins()&&wt("Manual layout reset — pins released")},walkIsolate(){if(!A)return;const c=a.getWalkTrail();c.length!==0&&a.enterFocus(c,0)},walkMode(){!a.isFocused()&&F.length>0&&G.addToFocusSet(F),a.setWalkMode(!a.getWalkMode());const c=t.querySelector(".walk-indicator");c&&c.classList.toggle("active",a.getWalkMode()),N()},escape(){a.getSelectedNodeIds().length>0?a.clearSelection():a.isFocused()?G.clearFocusSet():$.hide()}};document.addEventListener("keydown",c=>{var E;if(!(c.target instanceof HTMLInputElement||c.target instanceof HTMLTextAreaElement)){for(const[R,Y]of Object.entries(s))if(Vo(c,Y)){(R==="search"||R==="searchAlt"||R==="undo"||R==="redo"||R==="toggleSidebar")&&c.preventDefault(),(E=Je[R])==null||E.call(Je);return}}}),window.addEventListener("hashchange",()=>{const c=me();if(c.graph&&c.graph!==ce)ve(c.graph,c.nodes.length?c.nodes:void 0,c.focus.length?c.focus:void 0,c.hops);else if(c.graph&&c.focus.length&&A)a.enterFocus(c.focus,c.hops);else if(c.graph&&c.nodes.length&&A){a.isFocused()&&a.exitFocus();const E=c.nodes.filter(R=>A.nodes.some(Y=>Y.id===R));E.length&&(a.panToNodes(E),T.show(E,A))}})}Ss();
|
package/dist/app/index.html
CHANGED
|
@@ -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-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-Lvl7EMM_.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DE73ngo-.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="app">
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Viewer ↔ MCP bridge.
|
|
3
|
+
*
|
|
4
|
+
* Publishes the viewer's current state (active graph, selection, focus) to
|
|
5
|
+
* the local serve.js, which writes it to a state file in dataDir(). The
|
|
6
|
+
* backpack-ontology MCP server exposes that file as the
|
|
7
|
+
* `backpack://viewer/current` resource so any MCP client (Claude Code,
|
|
8
|
+
* Claude Desktop, etc.) can ask "what is the user looking at?" and get a
|
|
9
|
+
* grounded answer.
|
|
10
|
+
*
|
|
11
|
+
* Calls are debounced — selection changes can fire rapidly during walk
|
|
12
|
+
* mode, and we don't need to publish each frame.
|
|
13
|
+
*/
|
|
14
|
+
export interface ViewerState {
|
|
15
|
+
graph: string;
|
|
16
|
+
selection: string[];
|
|
17
|
+
focus: {
|
|
18
|
+
seedNodeIds: string[];
|
|
19
|
+
hops: number;
|
|
20
|
+
} | null;
|
|
21
|
+
}
|
|
22
|
+
export declare function publishViewerState(state: ViewerState): void;
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Viewer ↔ MCP bridge.
|
|
3
|
+
*
|
|
4
|
+
* Publishes the viewer's current state (active graph, selection, focus) to
|
|
5
|
+
* the local serve.js, which writes it to a state file in dataDir(). The
|
|
6
|
+
* backpack-ontology MCP server exposes that file as the
|
|
7
|
+
* `backpack://viewer/current` resource so any MCP client (Claude Code,
|
|
8
|
+
* Claude Desktop, etc.) can ask "what is the user looking at?" and get a
|
|
9
|
+
* grounded answer.
|
|
10
|
+
*
|
|
11
|
+
* Calls are debounced — selection changes can fire rapidly during walk
|
|
12
|
+
* mode, and we don't need to publish each frame.
|
|
13
|
+
*/
|
|
14
|
+
const PUBLISH_DEBOUNCE_MS = 200;
|
|
15
|
+
let pending = null;
|
|
16
|
+
let scheduled = null;
|
|
17
|
+
export function publishViewerState(state) {
|
|
18
|
+
pending = state;
|
|
19
|
+
if (scheduled !== null)
|
|
20
|
+
return;
|
|
21
|
+
scheduled = window.setTimeout(async () => {
|
|
22
|
+
const payload = pending;
|
|
23
|
+
pending = null;
|
|
24
|
+
scheduled = null;
|
|
25
|
+
if (!payload)
|
|
26
|
+
return;
|
|
27
|
+
try {
|
|
28
|
+
await fetch("/api/viewer-state", {
|
|
29
|
+
method: "PUT",
|
|
30
|
+
headers: { "Content-Type": "application/json" },
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
...payload,
|
|
33
|
+
updatedAt: new Date().toISOString(),
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Best-effort. The bridge is non-critical — viewer keeps working.
|
|
39
|
+
}
|
|
40
|
+
}, PUBLISH_DEBOUNCE_MS);
|
|
41
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -26,6 +26,16 @@ export function loadViewerConfig() {
|
|
|
26
26
|
lod: { ...defaultConfig.lod, ...(user.lod ?? {}) },
|
|
27
27
|
walk: { ...defaultConfig.walk, ...(user.walk ?? {}) },
|
|
28
28
|
limits: { ...defaultConfig.limits, ...(user.limits ?? {}) },
|
|
29
|
+
extensions: {
|
|
30
|
+
...defaultConfig.extensions,
|
|
31
|
+
...(user.extensions ?? {}),
|
|
32
|
+
disabled: Array.isArray(user.extensions?.disabled)
|
|
33
|
+
? user.extensions.disabled
|
|
34
|
+
: defaultConfig.extensions.disabled,
|
|
35
|
+
external: Array.isArray(user.extensions?.external)
|
|
36
|
+
? user.extensions.external
|
|
37
|
+
: defaultConfig.extensions.external,
|
|
38
|
+
},
|
|
29
39
|
};
|
|
30
40
|
}
|
|
31
41
|
catch {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { LearningGraphData } from "backpack-ontology";
|
|
2
|
+
export interface CopyPromptContext {
|
|
3
|
+
graphName: string;
|
|
4
|
+
data: LearningGraphData | null;
|
|
5
|
+
selection: string[];
|
|
6
|
+
focus: {
|
|
7
|
+
seedNodeIds: string[];
|
|
8
|
+
hops: number;
|
|
9
|
+
} | null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* "Copy Prompt" button — universal floor for users with no in-viewer chat
|
|
13
|
+
* and no MCP-capable client. Builds a prompt that describes the current
|
|
14
|
+
* view (graph, selection, focus) and copies it to the clipboard so the
|
|
15
|
+
* user can paste into Claude.ai web, Claude Desktop, or anywhere else.
|
|
16
|
+
*/
|
|
17
|
+
export declare function initCopyPromptButton(getCtx: () => CopyPromptContext): HTMLButtonElement;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { showToast } from "./dialog";
|
|
2
|
+
/**
|
|
3
|
+
* "Copy Prompt" button — universal floor for users with no in-viewer chat
|
|
4
|
+
* and no MCP-capable client. Builds a prompt that describes the current
|
|
5
|
+
* view (graph, selection, focus) and copies it to the clipboard so the
|
|
6
|
+
* user can paste into Claude.ai web, Claude Desktop, or anywhere else.
|
|
7
|
+
*/
|
|
8
|
+
export function initCopyPromptButton(getCtx) {
|
|
9
|
+
const btn = document.createElement("button");
|
|
10
|
+
btn.className = "copy-prompt-btn";
|
|
11
|
+
btn.title = "Copy a prompt about the current view to clipboard";
|
|
12
|
+
btn.textContent = "Copy Prompt";
|
|
13
|
+
btn.addEventListener("click", async () => {
|
|
14
|
+
const ctx = getCtx();
|
|
15
|
+
if (!ctx.graphName || !ctx.data) {
|
|
16
|
+
showToast("No graph loaded");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const prompt = buildPrompt(ctx);
|
|
20
|
+
try {
|
|
21
|
+
await navigator.clipboard.writeText(prompt);
|
|
22
|
+
showToast("Prompt copied — paste into Claude");
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
showToast("Failed to copy to clipboard");
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return btn;
|
|
29
|
+
}
|
|
30
|
+
function labelOf(node) {
|
|
31
|
+
const first = Object.values(node.properties).find((v) => typeof v === "string");
|
|
32
|
+
return first ?? node.id;
|
|
33
|
+
}
|
|
34
|
+
function buildPrompt(ctx) {
|
|
35
|
+
const { graphName, data, selection, focus } = ctx;
|
|
36
|
+
const lines = [];
|
|
37
|
+
lines.push(`I'm looking at the Backpack learning graph "${graphName}" in the viewer.`);
|
|
38
|
+
lines.push(`It has ${data.nodes.length} nodes and ${data.edges.length} edges.`);
|
|
39
|
+
const typeCounts = new Map();
|
|
40
|
+
for (const n of data.nodes) {
|
|
41
|
+
typeCounts.set(n.type, (typeCounts.get(n.type) ?? 0) + 1);
|
|
42
|
+
}
|
|
43
|
+
if (typeCounts.size > 0) {
|
|
44
|
+
const summary = Array.from(typeCounts.entries())
|
|
45
|
+
.sort((a, b) => b[1] - a[1])
|
|
46
|
+
.slice(0, 8)
|
|
47
|
+
.map(([t, c]) => `${t} (${c})`)
|
|
48
|
+
.join(", ");
|
|
49
|
+
lines.push(`Node types: ${summary}`);
|
|
50
|
+
}
|
|
51
|
+
if (focus && focus.seedNodeIds.length > 0) {
|
|
52
|
+
lines.push("");
|
|
53
|
+
lines.push(`I'm focused on ${focus.seedNodeIds.length} seed node(s) at ${focus.hops} hop(s):`);
|
|
54
|
+
for (const id of focus.seedNodeIds.slice(0, 10)) {
|
|
55
|
+
const node = data.nodes.find((n) => n.id === id);
|
|
56
|
+
if (!node)
|
|
57
|
+
continue;
|
|
58
|
+
lines.push(`- ${labelOf(node)} (type: ${node.type})`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (selection.length > 0) {
|
|
62
|
+
lines.push("");
|
|
63
|
+
lines.push(`I have ${selection.length} node(s) selected:`);
|
|
64
|
+
for (const id of selection.slice(0, 20)) {
|
|
65
|
+
const node = data.nodes.find((n) => n.id === id);
|
|
66
|
+
if (!node)
|
|
67
|
+
continue;
|
|
68
|
+
const props = Object.entries(node.properties)
|
|
69
|
+
.filter(([k]) => !k.startsWith("_"))
|
|
70
|
+
.slice(0, 5)
|
|
71
|
+
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
|
72
|
+
.join(", ");
|
|
73
|
+
lines.push(`- ${labelOf(node)} (type: ${node.type}, id: ${id}${props ? `, ${props}` : ""})`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
lines.push("");
|
|
77
|
+
lines.push("If you have access to the Backpack MCP tools, please use them to answer questions about this graph (backpack_get_node, backpack_get_neighbors, backpack_search, etc).");
|
|
78
|
+
lines.push("");
|
|
79
|
+
lines.push("My question: ");
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
}
|
package/dist/default-config.json
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny DOM helpers used across the viewer to avoid `innerHTML` for
|
|
3
|
+
* static SVG icons and other markup that previously got built as
|
|
4
|
+
* strings. Keeps every call site CSP-clean and XSS-safe by construction
|
|
5
|
+
* (no string concatenation, no parsing of user content).
|
|
6
|
+
*/
|
|
7
|
+
export interface SvgIconOptions {
|
|
8
|
+
/** SVG viewBox attribute, e.g. "0 0 24 24" */
|
|
9
|
+
viewBox?: string;
|
|
10
|
+
/** Width and height in pixels (sets both attributes) */
|
|
11
|
+
size?: number;
|
|
12
|
+
/** Stroke width */
|
|
13
|
+
strokeWidth?: number;
|
|
14
|
+
/** Stroke linecap */
|
|
15
|
+
strokeLinecap?: "round" | "square" | "butt";
|
|
16
|
+
/** Stroke linejoin */
|
|
17
|
+
strokeLinejoin?: "round" | "miter" | "bevel";
|
|
18
|
+
/** Optional CSS class for the root <svg> */
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build an SVG icon from a list of child element specs. Each spec is
|
|
23
|
+
* `{ tag, attrs }` where tag is one of the standard SVG element names.
|
|
24
|
+
*
|
|
25
|
+
* Example:
|
|
26
|
+
* makeSvgIcon({ size: 14 }, [
|
|
27
|
+
* { tag: "polyline", attrs: { points: "11 17 6 12 11 7" } },
|
|
28
|
+
* { tag: "polyline", attrs: { points: "18 17 13 12 18 7" } },
|
|
29
|
+
* ])
|
|
30
|
+
*/
|
|
31
|
+
export declare function makeSvgIcon(opts: SvgIconOptions, children: {
|
|
32
|
+
tag: string;
|
|
33
|
+
attrs: Record<string, string | number>;
|
|
34
|
+
}[]): SVGSVGElement;
|
|
35
|
+
/**
|
|
36
|
+
* Snapshot the current children of an element so they can be restored
|
|
37
|
+
* later via `restoreChildren()`. Used by inline-edit flows that
|
|
38
|
+
* temporarily replace a row's contents with an input.
|
|
39
|
+
*
|
|
40
|
+
* Returns a frozen array of cloned nodes — the original references are
|
|
41
|
+
* NOT preserved (which would break if the parent is mutated). Cloning
|
|
42
|
+
* is fine because the snapshotted markup is static — no event handlers
|
|
43
|
+
* to lose.
|
|
44
|
+
*/
|
|
45
|
+
export declare function snapshotChildren(el: Element): Node[];
|
|
46
|
+
export declare function restoreChildren(el: Element, snapshot: Node[]): void;
|