backpack-viewer 0.2.7 → 0.2.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backpack-viewer",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Web-based graph visualizer for backpack-ontology — Canvas 2D, force-directed layout, live reload",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Noah Irzinger",
@@ -14,10 +14,10 @@
14
14
  ],
15
15
  "scripts": {
16
16
  "dev": "vite",
17
- "build": "tsc && vite build",
17
+ "build": "rm -rf dist && tsc && cp src/style.css dist/ && vite build",
18
18
  "preview": "vite preview",
19
19
  "serve": "node bin/serve.js",
20
- "prepublishOnly": "npm run build",
20
+ "prepare": "npm run build",
21
21
  "release:patch": "npm version patch && git push && git push --tags",
22
22
  "release:minor": "npm version minor && git push && git push --tags",
23
23
  "release:major": "npm version major && git push && git push --tags"
@@ -1 +0,0 @@
1
- *{margin:0;padding:0;box-sizing:border-box}:root{--bg: #141414;--bg-surface: #1a1a1a;--bg-hover: #222222;--bg-active: #2a2a2a;--bg-elevated: #1e1e1e;--bg-inset: #111111;--border: #2a2a2a;--text: #d4d4d4;--text-strong: #e5e5e5;--text-muted: #737373;--text-dim: #525252;--accent: #d4a27f;--accent-hover: #e8b898;--badge-text: #141414;--glass-bg: rgba(20, 20, 20, .85);--glass-border: rgba(255, 255, 255, .08);--chip-bg: rgba(42, 42, 42, .7);--chip-bg-active: rgba(42, 42, 42, .9);--chip-bg-hover: rgba(50, 50, 50, .9);--chip-border-active: rgba(255, 255, 255, .06);--shadow: rgba(0, 0, 0, .6);--shadow-strong: rgba(0, 0, 0, .5);--canvas-edge: rgba(255, 255, 255, .08);--canvas-edge-highlight: rgba(212, 162, 127, .5);--canvas-edge-dim: rgba(255, 255, 255, .03);--canvas-edge-label: rgba(255, 255, 255, .2);--canvas-edge-label-highlight: rgba(212, 162, 127, .7);--canvas-edge-label-dim: rgba(255, 255, 255, .05);--canvas-arrow: rgba(255, 255, 255, .12);--canvas-arrow-highlight: rgba(212, 162, 127, .5);--canvas-node-label: #a3a3a3;--canvas-node-label-dim: rgba(212, 212, 212, .2);--canvas-type-badge: rgba(115, 115, 115, .5);--canvas-type-badge-dim: rgba(115, 115, 115, .15);--canvas-selection-border: #d4d4d4;--canvas-node-border: rgba(255, 255, 255, .15)}[data-theme=light]{--bg: #f5f5f4;--bg-surface: #fafaf9;--bg-hover: #f0efee;--bg-active: #e7e5e4;--bg-elevated: #f0efee;--bg-inset: #e7e5e4;--border: #d6d3d1;--text: #292524;--text-strong: #1c1917;--text-muted: #78716c;--text-dim: #a8a29e;--accent: #c17856;--accent-hover: #b07a5e;--badge-text: #fafaf9;--glass-bg: rgba(250, 250, 249, .85);--glass-border: rgba(0, 0, 0, .08);--chip-bg: rgba(214, 211, 209, .5);--chip-bg-active: rgba(214, 211, 209, .8);--chip-bg-hover: rgba(200, 197, 195, .8);--chip-border-active: rgba(0, 0, 0, .08);--shadow: rgba(0, 0, 0, .1);--shadow-strong: rgba(0, 0, 0, .15);--canvas-edge: rgba(0, 0, 0, .1);--canvas-edge-highlight: rgba(193, 120, 86, .6);--canvas-edge-dim: rgba(0, 0, 0, .03);--canvas-edge-label: rgba(0, 0, 0, .25);--canvas-edge-label-highlight: rgba(193, 120, 86, .8);--canvas-edge-label-dim: rgba(0, 0, 0, .06);--canvas-arrow: rgba(0, 0, 0, .15);--canvas-arrow-highlight: rgba(193, 120, 86, .6);--canvas-node-label: #57534e;--canvas-node-label-dim: rgba(87, 83, 78, .2);--canvas-type-badge: rgba(87, 83, 78, .5);--canvas-type-badge-dim: rgba(87, 83, 78, .15);--canvas-selection-border: #292524;--canvas-node-border: rgba(0, 0, 0, .1)}body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);overflow:hidden}#app{display:flex;height:100vh;width:100vw}#sidebar{width:280px;min-width:280px;background:var(--bg-surface);border-right:1px solid var(--border);display:flex;flex-direction:column;padding:16px;overflow-y:auto}#sidebar h2{font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:14px}#sidebar input{width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font-size:13px;outline:none;margin-bottom:12px}#sidebar input:focus{border-color:var(--accent)}#sidebar input::placeholder{color:var(--text-dim)}#ontology-list{list-style:none;display:flex;flex-direction:column;gap:2px}.ontology-item{padding:10px 12px;border-radius:6px;cursor:pointer;transition:background .15s}.ontology-item:hover{background:var(--bg-hover)}.ontology-item.active{background:var(--bg-active)}.ontology-item .name{display:block;font-size:13px;font-weight:500;color:var(--text)}.ontology-item .stats{display:block;font-size:11px;color:var(--text-dim);margin-top:2px}.sidebar-edit-btn{position:absolute;right:8px;top:10px;background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;opacity:0;transition:opacity .1s}.ontology-item{position:relative}.ontology-item:hover .sidebar-edit-btn{opacity:.7}.sidebar-edit-btn:hover{opacity:1!important;color:var(--text)}.sidebar-rename-input{background:transparent;border:none;border-bottom:1px solid var(--accent);color:var(--text);font:inherit;font-size:13px;font-weight:500;outline:none;width:100%;padding:0}.sidebar-footer{margin-top:auto;padding-top:16px;border-top:1px solid var(--border);text-align:center}.sidebar-footer a{display:block;font-size:12px;font-weight:500;color:var(--accent);text-decoration:none;margin-bottom:4px}.sidebar-footer a:hover{color:var(--accent-hover)}.sidebar-footer span{display:block;font-size:10px;color:var(--text-dim)}.theme-toggle{position:absolute;top:16px;right:16px;z-index:30;background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;color:var(--text-muted);font-size:18px;cursor:pointer;padding:6px 10px;line-height:1;transition:color .15s,border-color .15s,background .15s;box-shadow:0 2px 8px var(--shadow)}.theme-toggle:hover{color:var(--text);border-color:var(--text-muted);background:var(--bg-hover)}#canvas-container{flex:1;position:relative;overflow:hidden}#graph-canvas{position:absolute;top:0;left:0;width:100%;height:100%;cursor:grab}#graph-canvas:active{cursor:grabbing}.search-overlay{position:absolute;top:16px;left:50%;transform:translate(-50%);z-index:20;display:flex;flex-direction:column;align-items:center;gap:8px;max-height:calc(100vh - 48px);pointer-events:none}.search-overlay>*{pointer-events:auto}.search-overlay.hidden{display:none}.search-input-wrap{position:relative;width:380px;max-width:calc(100vw - 340px)}.search-input{width:100%;padding:10px 36px 10px 16px;border:1px solid var(--glass-border);border-radius:10px;background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);color:var(--text);font-size:14px;outline:none;transition:border-color .15s,box-shadow .15s}.search-input:focus{border-color:#d4a27f66;box-shadow:0 0 0 3px #d4a27f1a}.search-input::placeholder{color:var(--text-dim)}.search-kbd{position:absolute;right:10px;top:50%;transform:translateY(-50%);padding:2px 7px;border:1px solid var(--border);border-radius:4px;background:var(--bg-surface);color:var(--text-dim);font-size:11px;font-family:monospace;pointer-events:none}.search-kbd.hidden{display:none}.search-results{list-style:none;width:380px;max-width:calc(100vw - 340px);background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border);border-radius:10px;overflow:hidden;box-shadow:0 8px 32px var(--shadow-strong)}.search-results.hidden{display:none}.search-result-item{display:flex;align-items:center;gap:8px;padding:8px 14px;cursor:pointer;transition:background .1s}.search-result-item:hover{background:var(--bg-hover)}.search-result-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.search-result-label{font-size:13px;color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.search-result-type{font-size:11px;color:var(--text-dim);flex-shrink:0}.type-chips{display:flex;flex-wrap:wrap;gap:4px;justify-content:center;max-width:500px;max-height:200px;overflow-y:auto;padding:4px;border-radius:10px}.type-chip{display:flex;align-items:center;gap:4px;padding:3px 10px;border:1px solid transparent;border-radius:12px;background:var(--chip-bg);color:var(--text-dim);font-size:11px;cursor:pointer;transition:all .15s;white-space:nowrap}.type-chip.active{background:var(--chip-bg-active);color:var(--text-muted);border-color:var(--chip-border-active)}.type-chip:hover{background:var(--chip-bg-hover)}.type-chip-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.type-chip:not(.active) .type-chip-dot{opacity:.3}.info-panel{position:absolute;top:56px;right:16px;width:360px;max-height:calc(100vh - 72px);background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;overflow-y:auto;padding:20px;z-index:10;box-shadow:0 8px 32px var(--shadow);transition:top .25s ease,right .25s ease,bottom .25s ease,left .25s ease,width .25s ease,max-height .25s ease,border-radius .25s ease}.info-panel.hidden{display:none}.info-panel.info-panel-maximized{top:0;right:0;bottom:0;left:0;width:auto;max-height:none;border-radius:0;z-index:40}.info-panel-toolbar{position:absolute;top:12px;right:14px;display:flex;align-items:center;gap:2px;z-index:1}.info-toolbar-btn{background:none;border:none;color:var(--text-muted);font-size:16px;cursor:pointer;padding:4px 6px;line-height:1;border-radius:4px;transition:color .15s,background .15s}.info-toolbar-btn:hover:not(:disabled){color:var(--text);background:var(--bg-hover)}.info-toolbar-btn:disabled{color:var(--text-dim);cursor:default;opacity:.3}.info-close-btn{font-size:20px}.info-connection-link{cursor:pointer;transition:background .15s}.info-connection-link:hover{background:var(--bg-active)}.info-connection-link .info-target{color:var(--accent);text-decoration:underline;text-decoration-color:transparent;transition:text-decoration-color .15s}.info-connection-link:hover .info-target{text-decoration-color:var(--accent)}.info-header{margin-bottom:16px}.info-type-badge{display:inline-block;padding:3px 10px;border-radius:12px;font-size:11px;font-weight:600;color:var(--badge-text);margin-bottom:8px}.info-label{font-size:18px;font-weight:600;color:var(--text-strong);margin-bottom:4px;word-break:break-word}.info-id{display:block;font-size:11px;color:var(--text-dim);font-family:monospace}.info-section{margin-bottom:16px}.info-section-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border)}.info-props{display:grid;grid-template-columns:auto 1fr;gap:4px 12px}.info-props dt{font-size:12px;color:var(--text-muted);padding-top:2px}.info-props dd{font-size:12px;color:var(--text);word-break:break-word;display:flex;align-items:center;gap:4px}.info-value{white-space:pre-wrap}.info-array{display:flex;flex-wrap:wrap;gap:4px}.info-tag{display:inline-block;padding:2px 8px;background:var(--bg-hover);border-radius:4px;font-size:11px;color:var(--text-muted)}.info-json{font-size:11px;font-family:monospace;color:var(--text-muted);background:var(--bg-inset);padding:6px 8px;border-radius:4px;overflow-x:auto;white-space:pre}.info-connections{list-style:none;display:flex;flex-direction:column;gap:6px}.info-connection{display:flex;align-items:center;gap:6px;padding:6px 8px;background:var(--bg-elevated);border-radius:6px;font-size:12px;flex-wrap:wrap}.info-target-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.info-arrow{color:var(--text-dim);font-size:14px;flex-shrink:0}.info-edge-type{color:var(--text-muted);font-size:11px;font-weight:500}.info-target{color:var(--text);font-weight:500}.info-edge-props{width:100%;padding-top:4px;padding-left:20px}.info-edge-prop{display:block;font-size:11px;color:var(--text-dim)}.info-editable{cursor:default;position:relative}.info-inline-edit{background:none;border:none;color:var(--badge-text);opacity:0;font-size:10px;cursor:pointer;margin-left:4px;transition:opacity .15s}.info-editable:hover .info-inline-edit{opacity:.8}.info-inline-edit:hover{opacity:1!important}.info-edit-inline-input{background:transparent;border:none;border-bottom:1px solid var(--accent);color:var(--badge-text);font:inherit;font-size:inherit;outline:none;width:100%;padding:0}.info-edit-input{background:var(--bg-inset);border:1px solid var(--border);border-radius:4px;padding:3px 6px;font-size:12px;color:var(--text);flex:1;min-width:0}.info-edit-input:focus{outline:none;border-color:var(--accent)}.info-delete-prop{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;padding:0 2px;flex-shrink:0;opacity:0;transition:opacity .1s,color .1s}.info-props dd:hover .info-delete-prop{opacity:1}.info-delete-prop:hover{color:#ef4444}.info-add-btn{background:none;border:1px dashed var(--border);border-radius:4px;padding:6px 10px;font-size:12px;color:var(--text-dim);cursor:pointer;width:100%;margin-top:8px;transition:border-color .15s,color .15s}.info-add-btn:hover{border-color:var(--accent);color:var(--text)}.info-add-row{display:flex;gap:4px;margin-top:6px}.info-add-save{background:var(--accent);border:none;border-radius:4px;padding:3px 10px;font-size:12px;color:var(--badge-text);cursor:pointer;flex-shrink:0}.info-add-save:hover{background:var(--accent-hover)}.info-delete-edge{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;margin-left:auto;padding:0 2px;opacity:0;transition:opacity .1s,color .1s}.info-connection:hover .info-delete-edge{opacity:1}.info-delete-edge:hover{color:#ef4444}.info-danger{margin-top:8px;padding-top:12px;border-top:1px solid var(--border)}.info-delete-node{background:none;border:1px solid rgba(239,68,68,.3);border-radius:6px;padding:6px 12px;font-size:12px;color:#ef4444;cursor:pointer;width:100%;transition:background .15s}.info-delete-node:hover{background:#ef44441a}
@@ -1 +0,0 @@
1
- (function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))t(d);new MutationObserver(d=>{for(const e of d)if(e.type==="childList")for(const i of e.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&t(i)}).observe(document,{childList:!0,subtree:!0});function o(d){const e={};return d.integrity&&(e.integrity=d.integrity),d.referrerPolicy&&(e.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?e.credentials="include":d.crossOrigin==="anonymous"?e.credentials="omit":e.credentials="same-origin",e}function t(d){if(d.ep)return;d.ep=!0;const e=o(d);fetch(d.href,e)}})();async function ae(){const s=await fetch("/api/ontologies");return s.ok?s.json():[]}async function ce(s){const n=await fetch(`/api/ontologies/${encodeURIComponent(s)}`);if(!n.ok)throw new Error(`Failed to load ontology: ${s}`);return n.json()}async function be(s,n){if(!(await fetch(`/api/ontologies/${encodeURIComponent(s)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).ok)throw new Error(`Failed to save ontology: ${s}`)}async function Le(s,n){if(!(await fetch(`/api/ontologies/${encodeURIComponent(s)}/rename`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n})})).ok)throw new Error(`Failed to rename ontology: ${s}`)}function we(s,n){const o=typeof n=="function"?{onSelect:n}:n,t=document.createElement("h2");t.textContent="Backpack Ontology Viewer";const d=document.createElement("input");d.type="text",d.placeholder="Filter...",d.id="filter";const e=document.createElement("ul");e.id="ontology-list";const i=document.createElement("div");i.className="sidebar-footer",i.innerHTML='<a href="mailto:support@backpackontology.com">support@backpackontology.com</a><span>Feedback & support</span>',s.appendChild(t),s.appendChild(d),s.appendChild(e),s.appendChild(i);let v=[],h="";return d.addEventListener("input",()=>{const g=d.value.toLowerCase();for(const m of v){const a=m.dataset.name??"";m.style.display=a.includes(g)?"":"none"}}),{setSummaries(g){e.innerHTML="",v=g.map(m=>{const a=document.createElement("li");a.className="ontology-item",a.dataset.name=m.name;const y=document.createElement("span");y.className="name",y.textContent=m.name;const A=document.createElement("span");if(A.className="stats",A.textContent=`${m.nodeCount} nodes, ${m.edgeCount} edges`,a.appendChild(y),a.appendChild(A),o.onRename){const I=document.createElement("button");I.className="sidebar-edit-btn",I.textContent="✎",I.title="Rename";const K=o.onRename;I.addEventListener("click",f=>{f.stopPropagation();const x=document.createElement("input");x.type="text",x.className="sidebar-rename-input",x.value=m.name,y.textContent="",y.appendChild(x),I.style.display="none",x.focus(),x.select();const p=()=>{const C=x.value.trim();C&&C!==m.name?K(m.name,C):(y.textContent=m.name,I.style.display="")};x.addEventListener("blur",p),x.addEventListener("keydown",C=>{C.key==="Enter"&&x.blur(),C.key==="Escape"&&(x.value=m.name,x.blur())})}),a.appendChild(I)}return a.addEventListener("click",()=>o.onSelect(m.name)),e.appendChild(a),a}),h&&this.setActive(h)},setActive(g){h=g;for(const m of v)m.classList.toggle("active",m.dataset.name===g)}}}const Ne=5e3,Se=.005,Ce=150,pe=.9,ue=.01,me=30,ie=50;function Me(s,n){for(const o of Object.values(s))if(typeof o=="string")return o;return n}function Ae(s){const n=Math.sqrt(s.nodes.length)*Ce*.5,o=new Map,t=s.nodes.map((e,i)=>{const v=2*Math.PI*i/s.nodes.length,h={id:e.id,x:Math.cos(v)*n,y:Math.sin(v)*n,vx:0,vy:0,label:Me(e.properties,e.id),type:e.type};return o.set(e.id,h),h}),d=s.edges.map(e=>({sourceId:e.sourceId,targetId:e.targetId,type:e.type}));return{nodes:t,edges:d,nodeMap:o}}function Ie(s,n){const{nodes:o,edges:t,nodeMap:d}=s;for(let e=0;e<o.length;e++)for(let i=e+1;i<o.length;i++){const v=o[e],h=o[i];let g=h.x-v.x,m=h.y-v.y,a=Math.sqrt(g*g+m*m);a<me&&(a=me);const y=Ne*n/(a*a),A=g/a*y,I=m/a*y;v.vx-=A,v.vy-=I,h.vx+=A,h.vy+=I}for(const e of t){const i=d.get(e.sourceId),v=d.get(e.targetId);if(!i||!v)continue;const h=v.x-i.x,g=v.y-i.y,m=Math.sqrt(h*h+g*g);if(m===0)continue;const a=Se*(m-Ce)*n,y=h/m*a,A=g/m*a;i.vx+=y,i.vy+=A,v.vx-=y,v.vy-=A}for(const e of o)e.vx-=e.x*ue*n,e.vy-=e.y*ue*n;for(const e of o){e.vx*=pe,e.vy*=pe;const i=Math.sqrt(e.vx*e.vx+e.vy*e.vy);i>ie&&(e.vx=e.vx/i*ie,e.vy=e.vy/i*ie),e.x+=e.vx,e.y+=e.vy}return n*.995}const fe=["#d4a27f","#c17856","#b07a5e","#d4956b","#a67c5a","#cc9e7c","#c4866a","#cb8e6c","#b8956e","#a88a70","#d9b08c","#c4a882","#e8b898","#b5927a","#a8886e","#d1a990"],he=new Map;function J(s){const n=he.get(s);if(n)return n;let o=0;for(let d=0;d<s.length;d++)o=(o<<5)-o+s.charCodeAt(d)|0;const t=fe[Math.abs(o)%fe.length];return he.set(s,t),t}function q(s){return getComputedStyle(document.documentElement).getPropertyValue(s).trim()}const G=20,ke=.001;function Te(s,n){const o=s.querySelector("canvas"),t=o.getContext("2d"),d=window.devicePixelRatio||1;let e={x:0,y:0,scale:1},i=null,v=1,h=0,g=new Set,m=null,a=null,y=null;const A=300;function I(){o.width=o.clientWidth*d,o.height=o.clientHeight*d,p()}const K=new ResizeObserver(I);K.observe(s),I();function f(r,c){return[r/e.scale+e.x,c/e.scale+e.y]}function x(r,c){if(!i)return null;const[u,l]=f(r,c);for(let E=i.nodes.length-1;E>=0;E--){const b=i.nodes[E],w=u-b.x,T=l-b.y;if(w*w+T*T<=G*G)return b}return null}function p(){if(!i){t.clearRect(0,0,o.width,o.height);return}const r=q("--canvas-edge"),c=q("--canvas-edge-highlight"),u=q("--canvas-edge-dim"),l=q("--canvas-edge-label"),E=q("--canvas-edge-label-highlight"),b=q("--canvas-edge-label-dim"),w=q("--canvas-arrow"),T=q("--canvas-arrow-highlight"),S=q("--canvas-node-label"),B=q("--canvas-node-label-dim"),O=q("--canvas-type-badge"),Q=q("--canvas-type-badge-dim"),Z=q("--canvas-selection-border"),H=q("--canvas-node-border");t.save(),t.setTransform(d,0,0,d,0,0),t.clearRect(0,0,o.clientWidth,o.clientHeight),t.save(),t.translate(-e.x*e.scale,-e.y*e.scale),t.scale(e.scale,e.scale);for(const M of i.edges){const Y=i.nodeMap.get(M.sourceId),U=i.nodeMap.get(M.targetId);if(!Y||!U)continue;const se=m===null||m.has(M.sourceId),te=m===null||m.has(M.targetId),ee=se&&te;if(m!==null&&!se&&!te)continue;const V=g.size>0&&(g.has(M.sourceId)||g.has(M.targetId))||m!==null&&ee,re=m!==null&&!ee;if(M.sourceId===M.targetId){N(Y,M.type,V,r,c,l,E);continue}t.beginPath(),t.moveTo(Y.x,Y.y),t.lineTo(U.x,U.y),t.strokeStyle=V?c:re?u:r,t.lineWidth=V?2.5:1.5,t.stroke(),C(Y.x,Y.y,U.x,U.y,V,w,T);const ve=(Y.x+U.x)/2,Ee=(Y.y+U.y)/2;t.fillStyle=V?E:re?b:l,t.font="9px system-ui, sans-serif",t.textAlign="center",t.textBaseline="bottom",t.fillText(M.type,ve,Ee-4)}for(const M of i.nodes){const Y=J(M.type),U=g.has(M.id),se=g.size>0&&i.edges.some(V=>g.has(V.sourceId)&&V.targetId===M.id||g.has(V.targetId)&&V.sourceId===M.id),te=m!==null&&!m.has(M.id),ee=te||g.size>0&&!U&&!se;U&&(t.save(),t.shadowColor=Y,t.shadowBlur=20,t.beginPath(),t.arc(M.x,M.y,G+3,0,Math.PI*2),t.fillStyle=Y,t.globalAlpha=.3,t.fill(),t.restore()),t.beginPath(),t.arc(M.x,M.y,G,0,Math.PI*2),t.fillStyle=Y,t.globalAlpha=te?.1:ee?.3:1,t.fill(),t.strokeStyle=U?Z:H,t.lineWidth=U?3:1.5,t.stroke();const de=M.label.length>24?M.label.slice(0,22)+"...":M.label;t.fillStyle=ee?B:S,t.font="11px system-ui, sans-serif",t.textAlign="center",t.textBaseline="top",t.fillText(de,M.x,M.y+G+4),t.fillStyle=ee?Q:O,t.font="9px system-ui, sans-serif",t.textBaseline="bottom",t.fillText(M.type,M.x,M.y-G-3),t.globalAlpha=1}t.restore(),t.restore()}function C(r,c,u,l,E,b,w){const T=Math.atan2(l-c,u-r),S=u-Math.cos(T)*G,B=l-Math.sin(T)*G,O=8;t.beginPath(),t.moveTo(S,B),t.lineTo(S-O*Math.cos(T-.4),B-O*Math.sin(T-.4)),t.lineTo(S-O*Math.cos(T+.4),B-O*Math.sin(T+.4)),t.closePath(),t.fillStyle=E?w:b,t.fill()}function N(r,c,u,l,E,b,w){const T=r.x+G+15,S=r.y-G-15;t.beginPath(),t.arc(T,S,15,0,Math.PI*2),t.strokeStyle=u?E:l,t.lineWidth=u?2.5:1.5,t.stroke(),t.fillStyle=u?w:b,t.font="9px system-ui, sans-serif",t.textAlign="center",t.fillText(c,T,S-18)}function D(){if(!a||!y)return;const r=performance.now()-y.time,c=Math.min(r/A,1),u=1-Math.pow(1-c,3);e.x=y.x+(a.x-y.x)*u,e.y=y.y+(a.y-y.y)*u,p(),c<1?requestAnimationFrame(D):(a=null,y=null)}function z(){!i||v<ke||(v=Ie(i,v),p(),h=requestAnimationFrame(z))}let F=!1,j=!1,X=0,W=0;o.addEventListener("mousedown",r=>{F=!0,j=!1,X=r.clientX,W=r.clientY}),o.addEventListener("mousemove",r=>{if(!F)return;const c=r.clientX-X,u=r.clientY-W;(Math.abs(c)>2||Math.abs(u)>2)&&(j=!0),e.x-=c/e.scale,e.y-=u/e.scale,X=r.clientX,W=r.clientY,p()}),o.addEventListener("mouseup",r=>{if(F=!1,j)return;const c=o.getBoundingClientRect(),u=r.clientX-c.left,l=r.clientY-c.top,E=x(u,l),b=r.ctrlKey||r.metaKey;if(E){b?g.has(E.id)?g.delete(E.id):g.add(E.id):g.size===1&&g.has(E.id)?g.clear():(g.clear(),g.add(E.id));const w=[...g];n==null||n(w.length>0?w:null)}else g.clear(),n==null||n(null);p()}),o.addEventListener("mouseleave",()=>{F=!1}),o.addEventListener("wheel",r=>{r.preventDefault();const c=o.getBoundingClientRect(),u=r.clientX-c.left,l=r.clientY-c.top,[E,b]=f(u,l),w=r.ctrlKey?1-r.deltaY*.01:r.deltaY>0?.9:1.1;e.scale=Math.max(.05,Math.min(10,e.scale*w)),e.x=E-u/e.scale,e.y=b-l/e.scale,p()},{passive:!1});let R=[],k=0,P=1;o.addEventListener("touchstart",r=>{r.preventDefault(),R=Array.from(r.touches),R.length===2?(k=$(R[0],R[1]),P=e.scale):R.length===1&&(X=R[0].clientX,W=R[0].clientY)},{passive:!1}),o.addEventListener("touchmove",r=>{r.preventDefault();const c=Array.from(r.touches);if(c.length===2&&R.length===2){const l=$(c[0],c[1])/k;e.scale=Math.max(.05,Math.min(10,P*l)),p()}else if(c.length===1){const u=c[0].clientX-X,l=c[0].clientY-W;e.x-=u/e.scale,e.y-=l/e.scale,X=c[0].clientX,W=c[0].clientY,p()}R=c},{passive:!1});function $(r,c){const u=r.clientX-c.clientX,l=r.clientY-c.clientY;return Math.sqrt(u*u+l*l)}return{loadGraph(r){if(cancelAnimationFrame(h),i=Ae(r),v=1,g=new Set,m=null,e={x:0,y:0,scale:1},i.nodes.length>0){let c=1/0,u=1/0,l=-1/0,E=-1/0;for(const B of i.nodes)B.x<c&&(c=B.x),B.y<u&&(u=B.y),B.x>l&&(l=B.x),B.y>E&&(E=B.y);const b=(c+l)/2,w=(u+E)/2,T=o.clientWidth,S=o.clientHeight;e.x=b-T/2,e.y=w-S/2}z()},setFilteredNodeIds(r){m=r,p()},panToNode(r){if(!i)return;const c=i.nodeMap.get(r);if(!c)return;g=new Set([r]),n==null||n([r]);const u=o.clientWidth,l=o.clientHeight;y={x:e.x,y:e.y,time:performance.now()},a={x:c.x-u/(2*e.scale),y:c.y-l/(2*e.scale)},D()},destroy(){cancelAnimationFrame(h),K.disconnect()}}}function ne(s){for(const n of Object.values(s.properties))if(typeof n=="string")return n;return s.id}const Oe="✎";function De(s,n,o){const t=document.createElement("div");t.id="info-panel",t.className="info-panel hidden",s.appendChild(t);let d=!1,e=[],i=-1,v=!1,h=null;function g(){t.classList.add("hidden"),t.classList.remove("info-panel-maximized"),t.innerHTML="",d=!1,e=[],i=-1}function m(f){!h||!o||(i<e.length-1&&(e=e.slice(0,i+1)),e.push(f),i=e.length-1,v=!0,o(f),v=!1)}function a(){i<=0||!h||!o||(i--,v=!0,o(e[i]),v=!1)}function y(){i>=e.length-1||!h||!o||(i++,v=!0,o(e[i]),v=!1)}function A(){const f=document.createElement("div");f.className="info-panel-toolbar";const x=document.createElement("button");x.className="info-toolbar-btn",x.textContent="←",x.title="Back",x.disabled=i<=0,x.addEventListener("click",a),f.appendChild(x);const p=document.createElement("button");p.className="info-toolbar-btn",p.textContent="→",p.title="Forward",p.disabled=i>=e.length-1,p.addEventListener("click",y),f.appendChild(p);const C=document.createElement("button");C.className="info-toolbar-btn",C.textContent=d?"⎘":"⛶",C.title=d?"Restore":"Maximize",C.addEventListener("click",()=>{d=!d,t.classList.toggle("info-panel-maximized",d),C.textContent=d?"⎘":"⛶",C.title=d?"Restore":"Maximize"}),f.appendChild(C);const N=document.createElement("button");return N.className="info-toolbar-btn info-close-btn",N.textContent="×",N.title="Close",N.addEventListener("click",g),f.appendChild(N),f}function I(f,x){const p=x.nodes.find(c=>c.id===f);if(!p)return;const C=x.edges.filter(c=>c.sourceId===f||c.targetId===f);t.innerHTML="",t.classList.remove("hidden"),d&&t.classList.add("info-panel-maximized"),t.appendChild(A());const N=document.createElement("div");N.className="info-header";const D=document.createElement("span");if(D.className="info-type-badge",D.textContent=p.type,D.style.backgroundColor=J(p.type),n){D.classList.add("info-editable");const c=document.createElement("button");c.className="info-inline-edit",c.textContent=Oe,c.addEventListener("click",u=>{u.stopPropagation();const l=document.createElement("input");l.type="text",l.className="info-edit-inline-input",l.value=p.type,D.textContent="",D.appendChild(l),l.focus(),l.select();const E=()=>{const b=l.value.trim();b&&b!==p.type?n.onChangeNodeType(f,b):(D.textContent=p.type,D.appendChild(c))};l.addEventListener("blur",E),l.addEventListener("keydown",b=>{b.key==="Enter"&&l.blur(),b.key==="Escape"&&(l.value=p.type,l.blur())})}),D.appendChild(c)}const z=document.createElement("h3");z.className="info-label",z.textContent=ne(p);const F=document.createElement("span");F.className="info-id",F.textContent=p.id,N.appendChild(D),N.appendChild(z),N.appendChild(F),t.appendChild(N);const j=Object.keys(p.properties),X=oe("Properties");if(j.length>0){const c=document.createElement("dl");c.className="info-props";for(const u of j){const l=document.createElement("dt");l.textContent=u;const E=document.createElement("dd");if(n){const b=le(p.properties[u]),w=document.createElement("input");w.type="text",w.className="info-edit-input",w.value=b,w.addEventListener("keydown",S=>{S.key==="Enter"&&w.blur()}),w.addEventListener("blur",()=>{const S=w.value;S!==b&&n.onUpdateNode(f,{[u]:Be(S)})}),E.appendChild(w);const T=document.createElement("button");T.className="info-delete-prop",T.textContent="×",T.title=`Remove ${u}`,T.addEventListener("click",()=>{const S={...p.properties};delete S[u],n.onUpdateNode(f,S)}),E.appendChild(T)}else E.appendChild(Pe(p.properties[u]));c.appendChild(l),c.appendChild(E)}X.appendChild(c)}if(n){const c=document.createElement("button");c.className="info-add-btn",c.textContent="+ Add property",c.addEventListener("click",()=>{const u=document.createElement("div");u.className="info-add-row";const l=document.createElement("input");l.type="text",l.className="info-edit-input",l.placeholder="key";const E=document.createElement("input");E.type="text",E.className="info-edit-input",E.placeholder="value";const b=document.createElement("button");b.className="info-add-save",b.textContent="Add",b.addEventListener("click",()=>{l.value&&n.onAddProperty(f,l.value,E.value)}),u.appendChild(l),u.appendChild(E),u.appendChild(b),X.appendChild(u),l.focus()}),X.appendChild(c)}if(t.appendChild(X),C.length>0){const c=oe(`Connections (${C.length})`),u=document.createElement("ul");u.className="info-connections";for(const l of C){const E=l.sourceId===f,b=E?l.targetId:l.sourceId,w=x.nodes.find(H=>H.id===b),T=w?ne(w):b,S=document.createElement("li");if(S.className="info-connection",o&&w&&(S.classList.add("info-connection-link"),S.addEventListener("click",H=>{H.target.closest(".info-delete-edge")||m(b)})),w){const H=document.createElement("span");H.className="info-target-dot",H.style.backgroundColor=J(w.type),S.appendChild(H)}const B=document.createElement("span");B.className="info-arrow",B.textContent=E?"→":"←";const O=document.createElement("span");O.className="info-edge-type",O.textContent=l.type;const Q=document.createElement("span");Q.className="info-target",Q.textContent=T,S.appendChild(B),S.appendChild(O),S.appendChild(Q);const Z=Object.keys(l.properties);if(Z.length>0){const H=document.createElement("div");H.className="info-edge-props";for(const M of Z){const Y=document.createElement("span");Y.className="info-edge-prop",Y.textContent=`${M}: ${le(l.properties[M])}`,H.appendChild(Y)}S.appendChild(H)}if(n){const H=document.createElement("button");H.className="info-delete-edge",H.textContent="×",H.title="Remove connection",H.addEventListener("click",M=>{M.stopPropagation(),n.onDeleteEdge(l.id)}),S.appendChild(H)}u.appendChild(S)}c.appendChild(u),t.appendChild(c)}const W=oe("Timestamps"),R=document.createElement("dl");R.className="info-props";const k=document.createElement("dt");k.textContent="created";const P=document.createElement("dd");P.textContent=ge(p.createdAt);const $=document.createElement("dt");$.textContent="updated";const r=document.createElement("dd");if(r.textContent=ge(p.updatedAt),R.appendChild(k),R.appendChild(P),R.appendChild($),R.appendChild(r),W.appendChild(R),t.appendChild(W),n){const c=document.createElement("div");c.className="info-section info-danger";const u=document.createElement("button");u.className="info-delete-node",u.textContent="Delete node",u.addEventListener("click",()=>{n.onDeleteNode(f),g()}),c.appendChild(u),t.appendChild(c)}}function K(f,x){const p=new Set(f),C=x.nodes.filter(k=>p.has(k.id));if(C.length===0)return;const N=x.edges.filter(k=>p.has(k.sourceId)&&p.has(k.targetId));t.innerHTML="",t.classList.remove("hidden"),d&&t.classList.add("info-panel-maximized"),t.appendChild(A());const D=document.createElement("div");D.className="info-header";const z=document.createElement("h3");z.className="info-label",z.textContent=`${C.length} nodes selected`,D.appendChild(z);const F=document.createElement("div");F.style.cssText="display:flex;flex-wrap:wrap;gap:4px;margin-top:6px";const j=new Map;for(const k of C)j.set(k.type,(j.get(k.type)??0)+1);for(const[k,P]of j){const $=document.createElement("span");$.className="info-type-badge",$.style.backgroundColor=J(k),$.textContent=P>1?`${k} (${P})`:k,F.appendChild($)}D.appendChild(F),t.appendChild(D);const X=oe("Selected Nodes"),W=document.createElement("ul");W.className="info-connections";for(const k of C){const P=document.createElement("li");P.className="info-connection",o&&(P.classList.add("info-connection-link"),P.addEventListener("click",()=>{m(k.id)}));const $=document.createElement("span");$.className="info-target-dot",$.style.backgroundColor=J(k.type);const r=document.createElement("span");r.className="info-target",r.textContent=ne(k);const c=document.createElement("span");c.className="info-edge-type",c.textContent=k.type,P.appendChild($),P.appendChild(r),P.appendChild(c),W.appendChild(P)}X.appendChild(W),t.appendChild(X);const R=oe(N.length>0?`Connections Between Selected (${N.length})`:"Connections Between Selected");if(N.length===0){const k=document.createElement("p");k.style.cssText="font-size:12px;color:var(--text-dim)",k.textContent="No direct connections between selected nodes",R.appendChild(k)}else{const k=document.createElement("ul");k.className="info-connections";for(const P of N){const $=x.nodes.find(O=>O.id===P.sourceId),r=x.nodes.find(O=>O.id===P.targetId),c=$?ne($):P.sourceId,u=r?ne(r):P.targetId,l=document.createElement("li");if(l.className="info-connection",$){const O=document.createElement("span");O.className="info-target-dot",O.style.backgroundColor=J($.type),l.appendChild(O)}const E=document.createElement("span");E.className="info-target",E.textContent=c;const b=document.createElement("span");b.className="info-arrow",b.textContent="→";const w=document.createElement("span");w.className="info-edge-type",w.textContent=P.type;const T=document.createElement("span");if(T.className="info-arrow",T.textContent="→",l.appendChild(E),l.appendChild(b),l.appendChild(w),l.appendChild(T),r){const O=document.createElement("span");O.className="info-target-dot",O.style.backgroundColor=J(r.type),l.appendChild(O)}const S=document.createElement("span");S.className="info-target",S.textContent=u,l.appendChild(S);const B=Object.keys(P.properties);if(B.length>0){const O=document.createElement("div");O.className="info-edge-props";for(const Q of B){const Z=document.createElement("span");Z.className="info-edge-prop",Z.textContent=`${Q}: ${le(P.properties[Q])}`,O.appendChild(Z)}l.appendChild(O)}k.appendChild(l)}R.appendChild(k)}t.appendChild(R)}return{show(f,x){if(h=x,f.length===1&&!v){const p=f[0];e[i]!==p&&(i<e.length-1&&(e=e.slice(0,i+1)),e.push(p),i=e.length-1)}f.length===1?I(f[0],x):f.length>1&&K(f,x)},hide:g,get visible(){return!t.classList.contains("hidden")}}}function oe(s){const n=document.createElement("div");n.className="info-section";const o=document.createElement("h4");return o.className="info-section-title",o.textContent=s,n.appendChild(o),n}function Pe(s){if(Array.isArray(s)){const o=document.createElement("div");o.className="info-array";for(const t of s){const d=document.createElement("span");d.className="info-tag",d.textContent=String(t),o.appendChild(d)}return o}if(s!==null&&typeof s=="object"){const o=document.createElement("pre");return o.className="info-json",o.textContent=JSON.stringify(s,null,2),o}const n=document.createElement("span");return n.className="info-value",n.textContent=String(s??""),n}function le(s){return Array.isArray(s)?s.map(String).join(", "):s!==null&&typeof s=="object"?JSON.stringify(s):String(s??"")}function Be(s){const n=s.trim();if(n==="true")return!0;if(n==="false")return!1;if(n!==""&&!isNaN(Number(n)))return Number(n);if(n.startsWith("[")&&n.endsWith("]")||n.startsWith("{")&&n.endsWith("}"))try{return JSON.parse(n)}catch{return s}return s}function ge(s){try{return new Date(s).toLocaleString()}catch{return s}}function xe(s){for(const n of Object.values(s.properties))if(typeof n=="string")return n;return s.id}function ye(s,n){const o=n.toLowerCase();if(xe(s).toLowerCase().includes(o)||s.type.toLowerCase().includes(o))return!0;for(const t of Object.values(s.properties))if(typeof t=="string"&&t.toLowerCase().includes(o))return!0;return!1}function Re(s){let n=null,o=null,t=null,d=new Set,e=null;const i=document.createElement("div");i.className="search-overlay hidden";const v=document.createElement("div");v.className="search-input-wrap";const h=document.createElement("input");h.className="search-input",h.type="text",h.placeholder="Search nodes...",h.setAttribute("autocomplete","off"),h.setAttribute("spellcheck","false");const g=document.createElement("kbd");g.className="search-kbd",g.textContent="/",v.appendChild(h),v.appendChild(g);const m=document.createElement("ul");m.className="search-results hidden";const a=document.createElement("div");a.className="type-chips",i.appendChild(v),i.appendChild(m),i.appendChild(a),s.appendChild(i);function y(){if(a.innerHTML="",!n)return;const f=new Map;for(const p of n.nodes)f.set(p.type,(f.get(p.type)??0)+1);const x=[...f.keys()].sort();d=new Set;for(const p of x){const C=document.createElement("button");C.className="type-chip",C.dataset.type=p;const N=document.createElement("span");N.className="type-chip-dot",N.style.backgroundColor=J(p);const D=document.createElement("span");D.textContent=`${p} (${f.get(p)})`,C.appendChild(N),C.appendChild(D),C.addEventListener("click",()=>{d.has(p)?(d.delete(p),C.classList.remove("active")):(d.add(p),C.classList.add("active")),I()}),a.appendChild(C)}}function A(){if(!n)return null;const f=h.value.trim(),x=d.size===0,p=f.length===0;if(p&&x)return null;const C=new Set;for(const N of n.nodes)!x&&!d.has(N.type)||(p||ye(N,f))&&C.add(N.id);return C}function I(){const f=A();o==null||o(f),K()}function K(){m.innerHTML="";const f=h.value.trim();if(!n||f.length===0){m.classList.add("hidden");return}const x=d.size===0,p=[];for(const C of n.nodes)if(!(!x&&!d.has(C.type))&&ye(C,f)&&(p.push(C),p.length>=8))break;if(p.length===0){m.classList.add("hidden");return}for(const C of p){const N=document.createElement("li");N.className="search-result-item";const D=document.createElement("span");D.className="search-result-dot",D.style.backgroundColor=J(C.type);const z=document.createElement("span");z.className="search-result-label";const F=xe(C);z.textContent=F.length>36?F.slice(0,34)+"...":F;const j=document.createElement("span");j.className="search-result-type",j.textContent=C.type,N.appendChild(D),N.appendChild(z),N.appendChild(j),N.addEventListener("click",()=>{t==null||t(C.id),h.value="",m.classList.add("hidden"),I()}),m.appendChild(N)}m.classList.remove("hidden")}return h.addEventListener("input",()=>{e&&clearTimeout(e),e=setTimeout(I,150)}),h.addEventListener("keydown",f=>{if(f.key==="Escape")h.value="",h.blur(),m.classList.add("hidden"),I();else if(f.key==="Enter"){const x=m.querySelector(".search-result-item");x==null||x.click()}}),document.addEventListener("click",f=>{i.contains(f.target)||m.classList.add("hidden")}),h.addEventListener("focus",()=>g.classList.add("hidden")),h.addEventListener("blur",()=>{h.value.length===0&&g.classList.remove("hidden")}),{setOntologyData(f){n=f,h.value="",m.classList.add("hidden"),n&&n.nodes.length>0?(i.classList.remove("hidden"),y()):i.classList.add("hidden")},onFilterChange(f){o=f},onNodeSelect(f){t=f},clear(){h.value="",m.classList.add("hidden"),d.clear(),o==null||o(null)},focus(){h.focus()}}}let _="",L=null;async function $e(){const s=document.getElementById("canvas-container"),n=window.matchMedia("(prefers-color-scheme: dark)"),t=localStorage.getItem("backpack-theme")??(n.matches?"dark":"light");document.documentElement.setAttribute("data-theme",t);const d=document.createElement("button");d.className="theme-toggle",d.textContent=t==="light"?"☾":"☼",d.title="Toggle light/dark mode",d.addEventListener("click",()=>{const y=document.documentElement.getAttribute("data-theme")==="light"?"dark":"light";document.documentElement.setAttribute("data-theme",y),localStorage.setItem("backpack-theme",y),d.textContent=y==="light"?"☾":"☼"}),s.appendChild(d);async function e(){if(!_||!L)return;L.metadata.updatedAt=new Date().toISOString(),await be(_,L),i.loadGraph(L),h.setOntologyData(L);const a=await ae();g.setSummaries(a)}let i;const v=De(s,{onUpdateNode(a,y){if(!L)return;const A=L.nodes.find(I=>I.id===a);A&&(A.properties={...A.properties,...y},A.updatedAt=new Date().toISOString(),e().then(()=>v.show([a],L)))},onChangeNodeType(a,y){if(!L)return;const A=L.nodes.find(I=>I.id===a);A&&(A.type=y,A.updatedAt=new Date().toISOString(),e().then(()=>v.show([a],L)))},onDeleteNode(a){L&&(L.nodes=L.nodes.filter(y=>y.id!==a),L.edges=L.edges.filter(y=>y.sourceId!==a&&y.targetId!==a),e())},onDeleteEdge(a){var A;if(!L)return;const y=(A=L.edges.find(I=>I.id===a))==null?void 0:A.sourceId;L.edges=L.edges.filter(I=>I.id!==a),e().then(()=>{y&&L&&v.show([y],L)})},onAddProperty(a,y,A){if(!L)return;const I=L.nodes.find(K=>K.id===a);I&&(I.properties[y]=A,I.updatedAt=new Date().toISOString(),e().then(()=>v.show([a],L)))}},a=>{i.panToNode(a)});i=Te(s,a=>{a&&a.length>0&&L?v.show(a,L):v.hide()});const h=Re(s);h.onFilterChange(a=>{i.setFilteredNodeIds(a)}),h.onNodeSelect(a=>{i.panToNode(a),L&&v.show([a],L)});const g=we(document.getElementById("sidebar"),{onSelect:async a=>{_=a,g.setActive(a),v.hide(),h.clear(),L=await ce(a),i.loadGraph(L),h.setOntologyData(L)},onRename:async(a,y)=>{await Le(a,y),_===a&&(_=y);const A=await ae();g.setSummaries(A),g.setActive(_),_===y&&(L=await ce(y),i.loadGraph(L),h.setOntologyData(L))}}),m=await ae();g.setSummaries(m),m.length>0&&(_=m[0].name,g.setActive(_),L=await ce(_),i.loadGraph(L),h.setOntologyData(L)),document.addEventListener("keydown",a=>{a.target instanceof HTMLInputElement||a.target instanceof HTMLTextAreaElement||(a.key==="/"||a.key==="k"&&(a.metaKey||a.ctrlKey))&&(a.preventDefault(),h.focus())})}$e();