backpack-viewer 0.2.16 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,13 +34,85 @@ backpack-ontology (MCP) ──writes──> ~/.local/share/backpack/ontologies/
34
34
  backpack-viewer ──reads──────────────────┘
35
35
  ```
36
36
 
37
+ ## Configuration
38
+
39
+ The viewer reads an optional config file for customizing keybindings and other settings. The config file follows the [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/latest/) convention:
40
+
41
+ ```
42
+ ~/.config/backpack/viewer.json
43
+ ```
44
+
45
+ Override with environment variables:
46
+ - `$XDG_CONFIG_HOME/backpack/viewer.json`
47
+ - `$BACKPACK_DIR/config/viewer.json`
48
+
49
+ ### Keybindings
50
+
51
+ Create `~/.config/backpack/viewer.json` and override any binding. Unspecified keys keep their defaults.
52
+
53
+ ```json
54
+ {
55
+ "keybindings": {
56
+ "search": "s",
57
+ "focus": "g",
58
+ "panLeft": "a",
59
+ "panDown": "s",
60
+ "panUp": "w",
61
+ "panRight": "d"
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### Available actions
67
+
68
+ | Action | Default | Description |
69
+ |---|---|---|
70
+ | `search` | `/` | Focus the search bar |
71
+ | `searchAlt` | `ctrl+k` | Focus the search bar (alternate) |
72
+ | `undo` | `ctrl+z` | Undo last edit |
73
+ | `redo` | `ctrl+shift+z` | Redo last edit |
74
+ | `help` | `?` | Toggle keyboard shortcuts help |
75
+ | `escape` | `Escape` | Exit focus mode or close panel |
76
+ | `focus` | `f` | Focus on selected nodes / exit focus |
77
+ | `toggleEdges` | `e` | Toggle edge visibility |
78
+ | `center` | `c` | Center view on the graph |
79
+ | `nextNode` | `.` | Cycle to next node in view |
80
+ | `prevNode` | `,` | Cycle to previous node in view |
81
+ | `nextConnection` | `>` | Cycle to next connection in info panel |
82
+ | `prevConnection` | `<` | Cycle to previous connection in info panel |
83
+ | `historyBack` | `(` | Go back in node inspection history |
84
+ | `historyForward` | `)` | Go forward in node inspection history |
85
+ | `hopsIncrease` | `=` | Increase hops in focus mode |
86
+ | `hopsDecrease` | `-` | Decrease hops in focus mode |
87
+ | `panLeft` | `h` | Pan camera left |
88
+ | `panDown` | `j` | Pan camera down |
89
+ | `panUp` | `k` | Pan camera up |
90
+ | `panRight` | `l` | Pan camera right |
91
+ | `panFastLeft` | `H` | Pan camera left (fast) |
92
+ | `panFastRight` | `L` | Pan camera right (fast) |
93
+ | `zoomIn` | `K` | Zoom in |
94
+ | `zoomOut` | `J` | Zoom out |
95
+ | `spacingDecrease` | `[` | Decrease node spacing |
96
+ | `spacingIncrease` | `]` | Increase node spacing |
97
+ | `clusteringDecrease` | `{` | Decrease type clustering |
98
+ | `clusteringIncrease` | `}` | Increase type clustering |
99
+
100
+ ### Binding format
101
+
102
+ Bindings are strings with optional modifier prefixes separated by `+`:
103
+
104
+ - Single keys: `"f"`, `"/"`, `"?"`, `"Escape"`
105
+ - With modifiers: `"ctrl+z"`, `"ctrl+shift+z"`, `"alt+s"`
106
+ - `ctrl` and `cmd`/`meta` are treated as equivalent (works on both Mac and Linux)
107
+
37
108
  ## Reference
38
109
 
39
110
  | Variable | Effect |
40
111
  |---|---|
41
112
  | `PORT` | Override the default port (default: `5173`) |
113
+ | `XDG_CONFIG_HOME` | Override config location (default: `~/.config`) |
42
114
  | `XDG_DATA_HOME` | Override data location (default: `~/.local/share`) |
43
- | `BACKPACK_DIR` | Override data directory |
115
+ | `BACKPACK_DIR` | Override both config and data directories |
44
116
 
45
117
  ## Support
46
118
 
package/bin/serve.js CHANGED
@@ -15,9 +15,11 @@ const hasDistBuild = fs.existsSync(path.join(distDir, "index.html"));
15
15
  if (hasDistBuild) {
16
16
  // --- Production: static file server + API (zero native deps) ---
17
17
  const { JsonFileBackend, dataDir } = await import("backpack-ontology");
18
+ const { loadViewerConfig } = await import("../dist/config.js");
18
19
 
19
20
  const storage = new JsonFileBackend();
20
21
  await storage.initialize();
22
+ const viewerConfig = loadViewerConfig();
21
23
 
22
24
  const MIME_TYPES = {
23
25
  ".html": "text/html",
@@ -33,6 +35,159 @@ if (hasDistBuild) {
33
35
  const url = req.url?.replace(/\?.*$/, "") || "/";
34
36
 
35
37
  // --- API routes ---
38
+ if (url === "/api/config") {
39
+ res.writeHead(200, { "Content-Type": "application/json" });
40
+ res.end(JSON.stringify(viewerConfig));
41
+ return;
42
+ }
43
+
44
+ // --- Branch routes ---
45
+ const branchSwitchMatch = url.match(/^\/api\/graphs\/(.+)\/branches\/switch$/);
46
+ if (branchSwitchMatch && req.method === "POST") {
47
+ const graphName = decodeURIComponent(branchSwitchMatch[1]);
48
+ let body = "";
49
+ req.on("data", (chunk) => { body += chunk.toString(); });
50
+ req.on("end", async () => {
51
+ try {
52
+ const { name: branchName } = JSON.parse(body);
53
+ await storage.switchBranch(graphName, branchName);
54
+ res.writeHead(200, { "Content-Type": "application/json" });
55
+ res.end(JSON.stringify({ ok: true }));
56
+ } catch (err) {
57
+ res.writeHead(400, { "Content-Type": "application/json" });
58
+ res.end(JSON.stringify({ error: err.message }));
59
+ }
60
+ });
61
+ return;
62
+ }
63
+
64
+ const deleteBranchMatch = url.match(/^\/api\/graphs\/(.+)\/branches\/(.+)$/);
65
+ if (deleteBranchMatch && req.method === "DELETE") {
66
+ const graphName = decodeURIComponent(deleteBranchMatch[1]);
67
+ const branchName = decodeURIComponent(deleteBranchMatch[2]);
68
+ try {
69
+ await storage.deleteBranch(graphName, branchName);
70
+ res.writeHead(200, { "Content-Type": "application/json" });
71
+ res.end(JSON.stringify({ ok: true }));
72
+ } catch (err) {
73
+ res.writeHead(400, { "Content-Type": "application/json" });
74
+ res.end(JSON.stringify({ error: err.message }));
75
+ }
76
+ return;
77
+ }
78
+
79
+ const branchMatch = url.match(/^\/api\/graphs\/(.+)\/branches$/);
80
+ if (branchMatch && req.method === "GET") {
81
+ const graphName = decodeURIComponent(branchMatch[1]);
82
+ try {
83
+ const branches = await storage.listBranches(graphName);
84
+ res.writeHead(200, { "Content-Type": "application/json" });
85
+ res.end(JSON.stringify(branches));
86
+ } catch (err) {
87
+ res.writeHead(500, { "Content-Type": "application/json" });
88
+ res.end(JSON.stringify({ error: err.message }));
89
+ }
90
+ return;
91
+ }
92
+
93
+ if (branchMatch && req.method === "POST") {
94
+ const graphName = decodeURIComponent(branchMatch[1]);
95
+ let body = "";
96
+ req.on("data", (chunk) => { body += chunk.toString(); });
97
+ req.on("end", async () => {
98
+ try {
99
+ const { name: branchName, from } = JSON.parse(body);
100
+ await storage.createBranch(graphName, branchName, from);
101
+ res.writeHead(200, { "Content-Type": "application/json" });
102
+ res.end(JSON.stringify({ ok: true }));
103
+ } catch (err) {
104
+ res.writeHead(400, { "Content-Type": "application/json" });
105
+ res.end(JSON.stringify({ error: err.message }));
106
+ }
107
+ });
108
+ return;
109
+ }
110
+
111
+ // --- Snapshot routes ---
112
+ const snapshotMatch = url.match(/^\/api\/graphs\/(.+)\/snapshots$/);
113
+ if (snapshotMatch && req.method === "GET") {
114
+ const graphName = decodeURIComponent(snapshotMatch[1]);
115
+ try {
116
+ const snapshots = await storage.listSnapshots(graphName);
117
+ res.writeHead(200, { "Content-Type": "application/json" });
118
+ res.end(JSON.stringify(snapshots));
119
+ } catch (err) {
120
+ res.writeHead(500, { "Content-Type": "application/json" });
121
+ res.end(JSON.stringify({ error: err.message }));
122
+ }
123
+ return;
124
+ }
125
+
126
+ if (snapshotMatch && req.method === "POST") {
127
+ const graphName = decodeURIComponent(snapshotMatch[1]);
128
+ let body = "";
129
+ req.on("data", (chunk) => { body += chunk.toString(); });
130
+ req.on("end", async () => {
131
+ try {
132
+ const { label } = JSON.parse(body);
133
+ await storage.createSnapshot(graphName, label);
134
+ res.writeHead(200, { "Content-Type": "application/json" });
135
+ res.end(JSON.stringify({ ok: true }));
136
+ } catch (err) {
137
+ res.writeHead(400, { "Content-Type": "application/json" });
138
+ res.end(JSON.stringify({ error: err.message }));
139
+ }
140
+ });
141
+ return;
142
+ }
143
+
144
+ // --- Rollback route ---
145
+ const rollbackMatch = url.match(/^\/api\/graphs\/(.+)\/rollback$/);
146
+ if (rollbackMatch && req.method === "POST") {
147
+ const graphName = decodeURIComponent(rollbackMatch[1]);
148
+ let body = "";
149
+ req.on("data", (chunk) => { body += chunk.toString(); });
150
+ req.on("end", async () => {
151
+ try {
152
+ const { version } = JSON.parse(body);
153
+ await storage.rollback(graphName, version);
154
+ res.writeHead(200, { "Content-Type": "application/json" });
155
+ res.end(JSON.stringify({ ok: true }));
156
+ } catch (err) {
157
+ res.writeHead(400, { "Content-Type": "application/json" });
158
+ res.end(JSON.stringify({ error: err.message }));
159
+ }
160
+ });
161
+ return;
162
+ }
163
+
164
+ // --- Diff route ---
165
+ const diffMatch = url.match(/^\/api\/graphs\/(.+)\/diff\/(\d+)$/);
166
+ if (diffMatch && req.method === "GET") {
167
+ const graphName = decodeURIComponent(diffMatch[1]);
168
+ const version = parseInt(diffMatch[2], 10);
169
+ try {
170
+ const current = await storage.loadOntology(graphName);
171
+ const snapshot = await storage.loadSnapshot(graphName, version);
172
+ const currentNodeIds = new Set(current.nodes.map(n => n.id));
173
+ const snapshotNodeIds = new Set(snapshot.nodes.map(n => n.id));
174
+ const currentEdgeIds = new Set(current.edges.map(e => e.id));
175
+ const snapshotEdgeIds = new Set(snapshot.edges.map(e => e.id));
176
+ const diff = {
177
+ nodesAdded: current.nodes.filter(n => !snapshotNodeIds.has(n.id)).length,
178
+ nodesRemoved: snapshot.nodes.filter(n => !currentNodeIds.has(n.id)).length,
179
+ edgesAdded: current.edges.filter(e => !snapshotEdgeIds.has(e.id)).length,
180
+ edgesRemoved: snapshot.edges.filter(e => !currentEdgeIds.has(e.id)).length,
181
+ };
182
+ res.writeHead(200, { "Content-Type": "application/json" });
183
+ res.end(JSON.stringify(diff));
184
+ } catch (err) {
185
+ res.writeHead(500, { "Content-Type": "application/json" });
186
+ res.end(JSON.stringify({ error: err.message }));
187
+ }
188
+ return;
189
+ }
190
+
36
191
  if (url === "/api/ontologies") {
37
192
  try {
38
193
  const summaries = await storage.listOntologies();
package/dist/api.d.ts CHANGED
@@ -3,3 +3,30 @@ export declare function listOntologies(): Promise<LearningGraphSummary[]>;
3
3
  export declare function loadOntology(name: string): Promise<LearningGraphData>;
4
4
  export declare function saveOntology(name: string, data: LearningGraphData): Promise<void>;
5
5
  export declare function renameOntology(oldName: string, newName: string): Promise<void>;
6
+ export interface BranchInfo {
7
+ name: string;
8
+ nodeCount: number;
9
+ edgeCount: number;
10
+ active: boolean;
11
+ }
12
+ export declare function listBranches(graphName: string): Promise<BranchInfo[]>;
13
+ export declare function createBranch(graphName: string, branchName: string, from?: string): Promise<void>;
14
+ export declare function switchBranch(graphName: string, branchName: string): Promise<void>;
15
+ export declare function deleteBranch(graphName: string, branchName: string): Promise<void>;
16
+ export interface SnapshotInfo {
17
+ version: number;
18
+ timestamp: string;
19
+ nodeCount: number;
20
+ edgeCount: number;
21
+ label?: string;
22
+ }
23
+ export declare function listSnapshots(graphName: string): Promise<SnapshotInfo[]>;
24
+ export declare function createSnapshot(graphName: string, label?: string): Promise<void>;
25
+ export declare function rollbackSnapshot(graphName: string, version: number): Promise<void>;
26
+ export interface DiffResult {
27
+ nodesAdded: number;
28
+ nodesRemoved: number;
29
+ edgesAdded: number;
30
+ edgesRemoved: number;
31
+ }
32
+ export declare function diffSnapshot(graphName: string, version: number): Promise<DiffResult>;
package/dist/api.js CHANGED
@@ -28,3 +28,74 @@ export async function renameOntology(oldName, newName) {
28
28
  if (!res.ok)
29
29
  throw new Error(`Failed to rename ontology: ${oldName}`);
30
30
  }
31
+ export async function listBranches(graphName) {
32
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/branches`);
33
+ if (!res.ok)
34
+ return [];
35
+ return res.json();
36
+ }
37
+ export async function createBranch(graphName, branchName, from) {
38
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/branches`, {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json" },
41
+ body: JSON.stringify({ name: branchName, from }),
42
+ });
43
+ if (!res.ok) {
44
+ const d = await res.json().catch(() => ({}));
45
+ throw new Error(d.error || "Failed to create branch");
46
+ }
47
+ }
48
+ export async function switchBranch(graphName, branchName) {
49
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/branches/switch`, {
50
+ method: "POST",
51
+ headers: { "Content-Type": "application/json" },
52
+ body: JSON.stringify({ name: branchName }),
53
+ });
54
+ if (!res.ok) {
55
+ const d = await res.json().catch(() => ({}));
56
+ throw new Error(d.error || "Failed to switch branch");
57
+ }
58
+ }
59
+ export async function deleteBranch(graphName, branchName) {
60
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/branches/${encodeURIComponent(branchName)}`, {
61
+ method: "DELETE",
62
+ });
63
+ if (!res.ok) {
64
+ const d = await res.json().catch(() => ({}));
65
+ throw new Error(d.error || "Failed to delete branch");
66
+ }
67
+ }
68
+ export async function listSnapshots(graphName) {
69
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/snapshots`);
70
+ if (!res.ok)
71
+ return [];
72
+ return res.json();
73
+ }
74
+ export async function createSnapshot(graphName, label) {
75
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/snapshots`, {
76
+ method: "POST",
77
+ headers: { "Content-Type": "application/json" },
78
+ body: JSON.stringify({ label }),
79
+ });
80
+ if (!res.ok) {
81
+ const d = await res.json().catch(() => ({}));
82
+ throw new Error(d.error || "Failed to create snapshot");
83
+ }
84
+ }
85
+ export async function rollbackSnapshot(graphName, version) {
86
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/rollback`, {
87
+ method: "POST",
88
+ headers: { "Content-Type": "application/json" },
89
+ body: JSON.stringify({ version }),
90
+ });
91
+ if (!res.ok) {
92
+ const d = await res.json().catch(() => ({}));
93
+ throw new Error(d.error || "Failed to rollback");
94
+ }
95
+ }
96
+ export async function diffSnapshot(graphName, version) {
97
+ const res = await fetch(`/api/graphs/${encodeURIComponent(graphName)}/diff/${version}`);
98
+ if (!res.ok)
99
+ throw new Error("Failed to compute diff");
100
+ return res.json();
101
+ }
@@ -0,0 +1,21 @@
1
+ (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))p(l);new MutationObserver(l=>{for(const E of l)if(E.type==="childList")for(const s of E.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&p(s)}).observe(document,{childList:!0,subtree:!0});function i(l){const E={};return l.integrity&&(E.integrity=l.integrity),l.referrerPolicy&&(E.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?E.credentials="include":l.crossOrigin==="anonymous"?E.credentials="omit":E.credentials="same-origin",E}function p(l){if(l.ep)return;l.ep=!0;const E=i(l);fetch(l.href,E)}})();async function Be(){const o=await fetch("/api/ontologies");return o.ok?o.json():[]}async function De(o){const t=await fetch(`/api/ontologies/${encodeURIComponent(o)}`);if(!t.ok)throw new Error(`Failed to load ontology: ${o}`);return t.json()}async function He(o,t){if(!(await fetch(`/api/ontologies/${encodeURIComponent(o)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).ok)throw new Error(`Failed to save ontology: ${o}`)}async function dt(o,t){if(!(await fetch(`/api/ontologies/${encodeURIComponent(o)}/rename`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t})})).ok)throw new Error(`Failed to rename ontology: ${o}`)}async function rt(o){const t=await fetch(`/api/graphs/${encodeURIComponent(o)}/branches`);return t.ok?t.json():[]}async function pt(o,t,i){const p=await fetch(`/api/graphs/${encodeURIComponent(o)}/branches`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t,from:i})});if(!p.ok){const l=await p.json().catch(()=>({}));throw new Error(l.error||"Failed to create branch")}}async function ut(o,t){const i=await fetch(`/api/graphs/${encodeURIComponent(o)}/branches/switch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t})});if(!i.ok){const p=await i.json().catch(()=>({}));throw new Error(p.error||"Failed to switch branch")}}async function mt(o,t){const i=await fetch(`/api/graphs/${encodeURIComponent(o)}/branches/${encodeURIComponent(t)}`,{method:"DELETE"});if(!i.ok){const p=await i.json().catch(()=>({}));throw new Error(p.error||"Failed to delete branch")}}async function ht(o){const t=await fetch(`/api/graphs/${encodeURIComponent(o)}/snapshots`);return t.ok?t.json():[]}async function ft(o,t){const i=await fetch(`/api/graphs/${encodeURIComponent(o)}/snapshots`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:t})});if(!i.ok){const p=await i.json().catch(()=>({}));throw new Error(p.error||"Failed to create snapshot")}}async function gt(o,t){const i=await fetch(`/api/graphs/${encodeURIComponent(o)}/rollback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({version:t})});if(!i.ok){const p=await i.json().catch(()=>({}));throw new Error(p.error||"Failed to rollback")}}const Ue="bp-dialog-overlay";function Ze(){var t;(t=document.querySelector(`.${Ue}`))==null||t.remove();const o=document.createElement("div");return o.className=Ue,document.body.appendChild(o),o}function Qe(o,t){const i=document.createElement("div");i.className="bp-dialog";const p=document.createElement("h4");return p.className="bp-dialog-title",p.textContent=t,i.appendChild(p),o.appendChild(i),o.addEventListener("click",l=>{l.target===o&&o.remove()}),i}function et(o,t){const i=document.createElement("div");i.className="bp-dialog-buttons";for(const p of t){const l=document.createElement("button");l.className="bp-dialog-btn",p.accent&&l.classList.add("bp-dialog-btn-accent"),p.danger&&l.classList.add("bp-dialog-btn-danger"),l.textContent=p.label,l.addEventListener("click",p.onClick),i.appendChild(l)}o.appendChild(i)}function tt(o,t){return new Promise(i=>{var s;const p=Ze(),l=Qe(p,o),E=document.createElement("p");E.className="bp-dialog-message",E.textContent=t,l.appendChild(E),et(l,[{label:"Cancel",onClick:()=>{p.remove(),i(!1)}},{label:"Confirm",accent:!0,onClick:()=>{p.remove(),i(!0)}}]),(s=l.querySelector(".bp-dialog-btn-accent"))==null||s.focus()})}function nt(o,t,i){return new Promise(p=>{const l=Ze(),E=Qe(l,o),s=document.createElement("input");s.type="text",s.className="bp-dialog-input",s.placeholder=t??"",s.value="",E.appendChild(s);const n=()=>{const S=s.value.trim();l.remove(),p(S||null)};s.addEventListener("keydown",S=>{S.key==="Enter"&&n(),S.key==="Escape"&&(l.remove(),p(null))}),et(E,[{label:"Cancel",onClick:()=>{l.remove(),p(null)}},{label:"OK",accent:!0,onClick:n}]),s.focus(),s.select()})}function yt(o,t){const i=typeof t=="function"?{onSelect:t}:t,p=document.createElement("h2");p.textContent="Backpack Viewer";const l=document.createElement("input");l.type="text",l.placeholder="Filter...",l.id="filter";const E=document.createElement("ul");E.id="ontology-list";const s=document.createElement("div");s.className="sidebar-footer",s.innerHTML='<a href="mailto:support@backpackontology.com">support@backpackontology.com</a><span>Feedback & support</span>';const n=document.createElement("button");n.className="sidebar-collapse-btn",n.title="Toggle sidebar (Tab)",n.innerHTML='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>';let S=!1;function c(){S=!S,o.classList.toggle("sidebar-collapsed",S),n.innerHTML=S?'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 7 18 12 13 17"/><polyline points="6 7 11 12 6 17"/></svg>':'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>'}n.addEventListener("click",c);const v=document.createElement("div");v.className="sidebar-heading-row",v.appendChild(p),v.appendChild(n),o.appendChild(v),o.appendChild(l),o.appendChild(E),o.appendChild(s);let m=[],M="";return l.addEventListener("input",()=>{const $=l.value.toLowerCase();for(const j of m){const H=j.dataset.name??"";j.style.display=H.includes($)?"":"none"}}),{setSummaries($){E.innerHTML="",m=$.map(j=>{const H=document.createElement("li");H.className="ontology-item",H.dataset.name=j.name;const _=document.createElement("span");_.className="name",_.textContent=j.name;const I=document.createElement("span");I.className="stats",I.textContent=`${j.nodeCount} nodes, ${j.edgeCount} edges`;const Y=document.createElement("span");if(Y.className="sidebar-branch",Y.dataset.graph=j.name,H.appendChild(_),H.appendChild(I),H.appendChild(Y),i.onRename){const P=document.createElement("button");P.className="sidebar-edit-btn",P.textContent="✎",P.title="Rename";const k=i.onRename;P.addEventListener("click",z=>{z.stopPropagation();const R=document.createElement("input");R.type="text",R.className="sidebar-rename-input",R.value=j.name,_.textContent="",_.appendChild(R),P.style.display="none",R.focus(),R.select();const G=()=>{const W=R.value.trim();W&&W!==j.name?k(j.name,W):(_.textContent=j.name,P.style.display="")};R.addEventListener("blur",G),R.addEventListener("keydown",W=>{W.key==="Enter"&&R.blur(),W.key==="Escape"&&(R.value=j.name,R.blur())})}),H.appendChild(P)}return H.addEventListener("click",()=>i.onSelect(j.name)),E.appendChild(H),H}),M&&this.setActive(M)},setActive($){M=$;for(const j of m)j.classList.toggle("active",j.dataset.name===$)},setActiveBranch($,j,H){const _=E.querySelectorAll(`.sidebar-branch[data-graph="${$}"]`);for(const I of _){I.textContent=`/ ${j}`,I.title="Click to switch branch",I.style.cursor="pointer";const Y=I.cloneNode(!0);I.replaceWith(Y),Y.addEventListener("click",P=>{P.stopPropagation(),y($,Y,H??[])})}},toggle:c};function y($,j,H){const _=o.querySelector(".branch-picker");_&&_.remove();const I=document.createElement("div");I.className="branch-picker";for(const P of H){const k=document.createElement("div");k.className="branch-picker-item",P.active&&k.classList.add("branch-picker-active");const z=document.createElement("span");if(z.textContent=P.name,k.appendChild(z),!P.active&&i.onBranchDelete){const R=document.createElement("button");R.className="branch-picker-delete",R.textContent="×",R.title=`Delete ${P.name}`,R.addEventListener("click",G=>{G.stopPropagation(),tt("Delete branch",`Delete branch "${P.name}"?`).then(W=>{W&&(i.onBranchDelete($,P.name),I.remove())})}),k.appendChild(R)}P.active||k.addEventListener("click",()=>{var R;(R=i.onBranchSwitch)==null||R.call(i,$,P.name),I.remove()}),I.appendChild(k)}if(i.onBranchCreate){const P=document.createElement("div");P.className="branch-picker-item branch-picker-create",P.textContent="+ New branch",P.addEventListener("click",()=>{nt("New branch","Branch name").then(k=>{k&&(i.onBranchCreate($,k),I.remove())})}),I.appendChild(P)}j.after(I);const Y=P=>{I.contains(P.target)||(I.remove(),document.removeEventListener("click",Y))};setTimeout(()=>document.addEventListener("click",Y),0)}}const ot={clusterStrength:.08,spacing:1.5},Ct=6e3,vt=12e3,xt=.004,st=140,at=350,je=.9,Ye=.01,Xe=30,Oe=50;let ke={...ot};function Ae(o){o.clusterStrength!==void 0&&(ke.clusterStrength=o.clusterStrength),o.spacing!==void 0&&(ke.spacing=o.spacing)}function Pe(){return{...ke}}function Et(o){if(o<=30)return{...ot};const t=Math.log2(o/30);return{clusterStrength:Math.min(.5,.08+.06*t),spacing:Math.min(15,1.5+1.2*t)}}function bt(o,t){for(const i of Object.values(o))if(typeof i=="string")return i;return t}function wt(o,t,i){const p=new Set(t);let l=new Set(t);for(let E=0;E<i;E++){const s=new Set;for(const n of o.edges)l.has(n.sourceId)&&!p.has(n.targetId)&&s.add(n.targetId),l.has(n.targetId)&&!p.has(n.sourceId)&&s.add(n.sourceId);for(const n of s)p.add(n);if(l=s,s.size===0)break}return{nodes:o.nodes.filter(E=>p.has(E.id)),edges:o.edges.filter(E=>p.has(E.sourceId)&&p.has(E.targetId)),metadata:o.metadata}}function qe(o){const t=new Map,i=[...new Set(o.nodes.map(S=>S.type))],p=Math.sqrt(i.length)*at*.6*Math.max(1,ke.spacing),l=new Map,E=new Map;for(const S of o.nodes)E.set(S.type,(E.get(S.type)??0)+1);const s=o.nodes.map(S=>{const c=i.indexOf(S.type),v=2*Math.PI*c/Math.max(i.length,1),m=Math.cos(v)*p,M=Math.sin(v)*p,y=l.get(S.type)??0;l.set(S.type,y+1);const $=E.get(S.type)??1,j=2*Math.PI*y/$,H=st*.6,_={id:S.id,x:m+Math.cos(j)*H,y:M+Math.sin(j)*H,vx:0,vy:0,label:bt(S.properties,S.id),type:S.type};return t.set(S.id,_),_}),n=o.edges.map(S=>({sourceId:S.sourceId,targetId:S.targetId,type:S.type}));return{nodes:s,edges:n,nodeMap:t}}function Lt(o,t){const{nodes:i,edges:p,nodeMap:l}=o;for(let s=0;s<i.length;s++)for(let n=s+1;n<i.length;n++){const S=i[s],c=i[n];let v=c.x-S.x,m=c.y-S.y,M=Math.sqrt(v*v+m*m);M<Xe&&(M=Xe);const $=(S.type===c.type?Ct:vt*ke.spacing)*t/(M*M),j=v/M*$,H=m/M*$;S.vx-=j,S.vy-=H,c.vx+=j,c.vy+=H}for(const s of p){const n=l.get(s.sourceId),S=l.get(s.targetId);if(!n||!S)continue;const c=S.x-n.x,v=S.y-n.y,m=Math.sqrt(c*c+v*v);if(m===0)continue;const M=n.type===S.type?st*ke.spacing:at*ke.spacing,y=xt*(m-M)*t,$=c/m*y,j=v/m*y;n.vx+=$,n.vy+=j,S.vx-=$,S.vy-=j}for(const s of i)s.vx-=s.x*Ye*t,s.vy-=s.y*Ye*t;const E=new Map;for(const s of i){const n=E.get(s.type)??{x:0,y:0,count:0};n.x+=s.x,n.y+=s.y,n.count++,E.set(s.type,n)}for(const s of E.values())s.x/=s.count,s.y/=s.count;for(const s of i){const n=E.get(s.type);s.vx+=(n.x-s.x)*ke.clusterStrength*t,s.vy+=(n.y-s.y)*ke.clusterStrength*t}for(const s of i){s.vx*=je,s.vy*=je;const n=Math.sqrt(s.vx*s.vx+s.vy*s.vy);n>Oe&&(s.vx=s.vx/n*Oe,s.vy=s.vy/n*Oe),s.x+=s.vx,s.y+=s.vy}return t*.995}const We=["#d4a27f","#c17856","#b07a5e","#d4956b","#a67c5a","#cc9e7c","#c4866a","#cb8e6c","#b8956e","#a88a70","#d9b08c","#c4a882","#e8b898","#b5927a","#a8886e","#d1a990"],_e=new Map;function fe(o){const t=_e.get(o);if(t)return t;let i=0;for(let l=0;l<o.length;l++)i=(i<<5)-i+o.charCodeAt(l)|0;const p=We[Math.abs(i)%We.length];return _e.set(o,p),p}function ue(o){return getComputedStyle(document.documentElement).getPropertyValue(o).trim()}const be=20,Nt=.001,St={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15},kt={zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300};function $e(o,t,i,p,l,E=100){const s=(o-i.x)*i.scale,n=(t-i.y)*i.scale;return s>=-E&&s<=p+E&&n>=-E&&n<=l+E}function It(o,t,i,p){const l={...St,...(p==null?void 0:p.lod)??{}},E={...kt,...(p==null?void 0:p.navigation)??{}},s=o.querySelector("canvas"),n=s.getContext("2d"),S=window.devicePixelRatio||1;let c={x:0,y:0,scale:1},v=null,m=1,M=0,y=new Set,$=null,j=!0,H=!0,_=!0,I=!0,Y=null,P=null,k=1,z=null,R=null,G=null,W=null;const ne=E.panAnimationMs;function le(){s.width=s.clientWidth*S,s.height=s.clientHeight*S,J()}const me=new ResizeObserver(le);me.observe(o),le();function Q(d,x){return[d/c.scale+c.x,x/c.scale+c.y]}function ae(d,x){if(!v)return null;const[A,O]=Q(d,x);for(let U=v.nodes.length-1;U>=0;U--){const K=v.nodes[U],X=A-K.x,Z=O-K.y;if(X*X+Z*Z<=be*be)return K}return null}function J(){if(!v){n.clearRect(0,0,s.width,s.height);return}const d=ue("--canvas-edge"),x=ue("--canvas-edge-highlight"),A=ue("--canvas-edge-dim"),O=ue("--canvas-edge-label"),U=ue("--canvas-edge-label-highlight"),K=ue("--canvas-edge-label-dim"),X=ue("--canvas-arrow"),Z=ue("--canvas-arrow-highlight"),re=ue("--canvas-node-label"),te=ue("--canvas-node-label-dim"),xe=ue("--canvas-type-badge"),ge=ue("--canvas-type-badge-dim"),Ie=ue("--canvas-selection-border"),Me=ue("--canvas-node-border");if(n.save(),n.setTransform(S,0,0,S,0,0),n.clearRect(0,0,s.clientWidth,s.clientHeight),n.save(),n.translate(-c.x*c.scale,-c.y*c.scale),n.scale(c.scale,c.scale),_&&c.scale>=l.smallNodes){const q=new Map;for(const ce of v.nodes){if($!==null&&!$.has(ce.id))continue;const de=q.get(ce.type)??[];de.push(ce),q.set(ce.type,de)}for(const[ce,de]of q){if(de.length<2)continue;const Le=fe(ce),Ee=be*2.5;let ye=1/0,Ce=1/0,pe=-1/0,se=-1/0;for(const Te of de)Te.x<ye&&(ye=Te.x),Te.y<Ce&&(Ce=Te.y),Te.x>pe&&(pe=Te.x),Te.y>se&&(se=Te.y);n.beginPath();const Ne=(pe-ye)/2+Ee,Se=(se-Ce)/2+Ee,it=(ye+pe)/2,lt=(Ce+se)/2;n.ellipse(it,lt,Ne,Se,0,0,Math.PI*2),n.fillStyle=Le,n.globalAlpha=.05,n.fill(),n.strokeStyle=Le,n.globalAlpha=.12,n.lineWidth=1,n.setLineDash([4,4]),n.stroke(),n.setLineDash([]),n.globalAlpha=1}}if(j)for(const q of v.edges){const ce=v.nodeMap.get(q.sourceId),de=v.nodeMap.get(q.targetId);if(!ce||!de||!$e(ce.x,ce.y,c,s.clientWidth,s.clientHeight,200)&&!$e(de.x,de.y,c,s.clientWidth,s.clientHeight,200))continue;const Le=$===null||$.has(q.sourceId),Ee=$===null||$.has(q.targetId),ye=Le&&Ee;if($!==null&&!Le&&!Ee)continue;const pe=y.size>0&&(y.has(q.sourceId)||y.has(q.targetId))||$!==null&&ye,se=$!==null&&!ye;if(q.sourceId===q.targetId){w(ce,q.type,pe,d,x,O,U);continue}if(n.beginPath(),n.moveTo(ce.x,ce.y),n.lineTo(de.x,de.y),n.strokeStyle=pe?x:se?A:d,n.lineWidth=c.scale<l.hideArrows?1:pe?2.5:1.5,n.stroke(),c.scale>=l.hideArrows&&h(ce.x,ce.y,de.x,de.y,pe,X,Z),H&&c.scale>=l.hideEdgeLabels){const Ne=(ce.x+de.x)/2,Se=(ce.y+de.y)/2;n.fillStyle=pe?U:se?K:O,n.font="9px system-ui, sans-serif",n.textAlign="center",n.textBaseline="bottom",n.fillText(q.type,Ne,Se-4)}}for(const q of v.nodes){if(!$e(q.x,q.y,c,s.clientWidth,s.clientHeight))continue;const ce=fe(q.type),de=y.has(q.id),Le=y.size>0&&v.edges.some(pe=>y.has(pe.sourceId)&&pe.targetId===q.id||y.has(pe.targetId)&&pe.sourceId===q.id),Ee=$!==null&&!$.has(q.id),ye=Ee||y.size>0&&!de&&!Le,Ce=c.scale<l.smallNodes?be*.5:be;if(de&&(n.save(),n.shadowColor=ce,n.shadowBlur=20,n.beginPath(),n.arc(q.x,q.y,Ce+3,0,Math.PI*2),n.fillStyle=ce,n.globalAlpha=.3,n.fill(),n.restore()),n.beginPath(),n.arc(q.x,q.y,Ce,0,Math.PI*2),n.fillStyle=ce,n.globalAlpha=Ee?.1:ye?.3:1,n.fill(),n.strokeStyle=de?Ie:Me,n.lineWidth=de?3:1.5,n.stroke(),c.scale>=l.hideLabels){const pe=q.label.length>24?q.label.slice(0,22)+"...":q.label;n.fillStyle=ye?te:re,n.font="11px system-ui, sans-serif",n.textAlign="center",n.textBaseline="top",n.fillText(pe,q.x,q.y+Ce+4)}c.scale>=l.hideBadges&&(n.fillStyle=ye?ge:xe,n.font="9px system-ui, sans-serif",n.textBaseline="bottom",n.fillText(q.type,q.x,q.y-Ce-3)),n.globalAlpha=1}n.restore(),n.restore(),I&&v.nodes.length>1&&f()}function f(){if(!v)return;const d=140,x=100,A=8,O=s.clientWidth-d-16,U=s.clientHeight-x-16;let K=1/0,X=1/0,Z=-1/0,re=-1/0;for(const se of v.nodes)se.x<K&&(K=se.x),se.y<X&&(X=se.y),se.x>Z&&(Z=se.x),se.y>re&&(re=se.y);const te=Z-K||1,xe=re-X||1,ge=Math.min((d-A*2)/te,(x-A*2)/xe),Ie=O+A+(d-A*2-te*ge)/2,Me=U+A+(x-A*2-xe*ge)/2;n.save(),n.setTransform(S,0,0,S,0,0),n.fillStyle=ue("--bg-surface")||"#1a1a1a",n.globalAlpha=.85,n.beginPath(),n.roundRect(O,U,d,x,8),n.fill(),n.strokeStyle=ue("--border")||"#2a2a2a",n.globalAlpha=1,n.lineWidth=1,n.stroke(),n.globalAlpha=.15,n.strokeStyle=ue("--canvas-edge")||"#555",n.lineWidth=.5;for(const se of v.edges){const Ne=v.nodeMap.get(se.sourceId),Se=v.nodeMap.get(se.targetId);!Ne||!Se||se.sourceId===se.targetId||(n.beginPath(),n.moveTo(Ie+(Ne.x-K)*ge,Me+(Ne.y-X)*ge),n.lineTo(Ie+(Se.x-K)*ge,Me+(Se.y-X)*ge),n.stroke())}n.globalAlpha=.8;for(const se of v.nodes){const Ne=Ie+(se.x-K)*ge,Se=Me+(se.y-X)*ge;n.beginPath(),n.arc(Ne,Se,2,0,Math.PI*2),n.fillStyle=fe(se.type),n.fill()}const q=c.x,ce=c.y,de=c.x+s.clientWidth/c.scale,Le=c.y+s.clientHeight/c.scale,Ee=Ie+(q-K)*ge,ye=Me+(ce-X)*ge,Ce=(de-q)*ge,pe=(Le-ce)*ge;n.globalAlpha=.3,n.strokeStyle=ue("--accent")||"#d4a27f",n.lineWidth=1.5,n.strokeRect(Math.max(O,Math.min(Ee,O+d)),Math.max(U,Math.min(ye,U+x)),Math.min(Ce,d),Math.min(pe,x)),n.globalAlpha=1,n.restore()}function h(d,x,A,O,U,K,X){const Z=Math.atan2(O-x,A-d),re=A-Math.cos(Z)*be,te=O-Math.sin(Z)*be,xe=8;n.beginPath(),n.moveTo(re,te),n.lineTo(re-xe*Math.cos(Z-.4),te-xe*Math.sin(Z-.4)),n.lineTo(re-xe*Math.cos(Z+.4),te-xe*Math.sin(Z+.4)),n.closePath(),n.fillStyle=U?X:K,n.fill()}function w(d,x,A,O,U,K,X){const Z=d.x+be+15,re=d.y-be-15;n.beginPath(),n.arc(Z,re,15,0,Math.PI*2),n.strokeStyle=A?U:O,n.lineWidth=A?2.5:1.5,n.stroke(),H&&(n.fillStyle=A?X:K,n.font="9px system-ui, sans-serif",n.textAlign="center",n.fillText(x,Z,re-18))}function g(){if(!G||!W)return;const d=performance.now()-W.time,x=Math.min(d/ne,1),A=1-Math.pow(1-x,3);c.x=W.x+(G.x-W.x)*A,c.y=W.y+(G.y-W.y)*A,J(),x<1?requestAnimationFrame(g):(G=null,W=null)}function L(){!v||m<Nt||(m=Lt(v,m),J(),M=requestAnimationFrame(L))}let F=!1,B=!1,C=0,N=0;s.addEventListener("mousedown",d=>{F=!0,B=!1,C=d.clientX,N=d.clientY}),s.addEventListener("mousemove",d=>{if(!F)return;const x=d.clientX-C,A=d.clientY-N;(Math.abs(x)>2||Math.abs(A)>2)&&(B=!0),c.x-=x/c.scale,c.y-=A/c.scale,C=d.clientX,N=d.clientY,J()}),s.addEventListener("mouseup",d=>{if(F=!1,B)return;const x=s.getBoundingClientRect(),A=d.clientX-x.left,O=d.clientY-x.top,U=ae(A,O),K=d.ctrlKey||d.metaKey;if(U){K?y.has(U.id)?y.delete(U.id):y.add(U.id):y.size===1&&y.has(U.id)?y.clear():(y.clear(),y.add(U.id));const X=[...y];t==null||t(X.length>0?X:null)}else y.clear(),t==null||t(null);J()}),s.addEventListener("mouseleave",()=>{F=!1}),s.addEventListener("wheel",d=>{d.preventDefault();const x=s.getBoundingClientRect(),A=d.clientX-x.left,O=d.clientY-x.top,[U,K]=Q(A,O),X=d.ctrlKey?1-d.deltaY*.01:d.deltaY>0?.9:1.1;c.scale=Math.max(E.zoomMin,Math.min(E.zoomMax,c.scale*X)),c.x=U-A/c.scale,c.y=K-O/c.scale,J()},{passive:!1});let u=[],e=0,a=1,r=0,D=0,T=!1;s.addEventListener("touchstart",d=>{d.preventDefault(),u=Array.from(d.touches),u.length===2?(e=V(u[0],u[1]),a=c.scale):u.length===1&&(C=u[0].clientX,N=u[0].clientY,r=u[0].clientX,D=u[0].clientY,T=!1)},{passive:!1}),s.addEventListener("touchmove",d=>{d.preventDefault();const x=Array.from(d.touches);if(x.length===2&&u.length===2){const O=V(x[0],x[1])/e;c.scale=Math.max(E.zoomMin,Math.min(E.zoomMax,a*O)),J()}else if(x.length===1){const A=x[0].clientX-C,O=x[0].clientY-N;(Math.abs(x[0].clientX-r)>10||Math.abs(x[0].clientY-D)>10)&&(T=!0),c.x-=A/c.scale,c.y-=O/c.scale,C=x[0].clientX,N=x[0].clientY,J()}u=x},{passive:!1}),s.addEventListener("touchend",d=>{if(d.preventDefault(),T||d.changedTouches.length!==1)return;const x=d.changedTouches[0],A=s.getBoundingClientRect(),O=x.clientX-A.left,U=x.clientY-A.top,K=ae(O,U);if(K){y.size===1&&y.has(K.id)?y.clear():(y.clear(),y.add(K.id));const X=[...y];t==null||t(X.length>0?X:null)}else y.clear(),t==null||t(null);J()},{passive:!1}),s.addEventListener("gesturestart",d=>d.preventDefault()),s.addEventListener("gesturechange",d=>d.preventDefault());function V(d,x){const A=d.clientX-x.clientX,O=d.clientY-x.clientY;return Math.sqrt(A*A+O*O)}const ee=document.createElement("div");ee.className="zoom-controls";const he=document.createElement("button");he.className="zoom-btn",he.textContent="+",he.title="Zoom in",he.addEventListener("click",()=>{const d=s.clientWidth/2,x=s.clientHeight/2,[A,O]=Q(d,x);c.scale=Math.min(E.zoomMax,c.scale*E.zoomFactor),c.x=A-d/c.scale,c.y=O-x/c.scale,J()});const ve=document.createElement("button");ve.className="zoom-btn",ve.textContent="−",ve.title="Zoom out",ve.addEventListener("click",()=>{const d=s.clientWidth/2,x=s.clientHeight/2,[A,O]=Q(d,x);c.scale=Math.max(E.zoomMin,c.scale/E.zoomFactor),c.x=A-d/c.scale,c.y=O-x/c.scale,J()});const oe=document.createElement("button");return oe.className="zoom-btn",oe.textContent="○",oe.title="Reset zoom",oe.addEventListener("click",()=>{if(v){if(c={x:0,y:0,scale:1},v.nodes.length>0){let d=1/0,x=1/0,A=-1/0,O=-1/0;for(const X of v.nodes)X.x<d&&(d=X.x),X.y<x&&(x=X.y),X.x>A&&(A=X.x),X.y>O&&(O=X.y);const U=(d+A)/2,K=(x+O)/2;c.x=U-s.clientWidth/2,c.y=K-s.clientHeight/2}J()}}),ee.appendChild(he),ee.appendChild(oe),ee.appendChild(ve),o.appendChild(ee),{loadGraph(d){if(cancelAnimationFrame(M),Y=d,P=null,z=null,R=null,v=qe(d),m=1,y=new Set,$=null,c={x:0,y:0,scale:1},v.nodes.length>0){let x=1/0,A=1/0,O=-1/0,U=-1/0;for(const te of v.nodes)te.x<x&&(x=te.x),te.y<A&&(A=te.y),te.x>O&&(O=te.x),te.y>U&&(U=te.y);const K=(x+O)/2,X=(A+U)/2,Z=s.clientWidth,re=s.clientHeight;c.x=K-Z/2,c.y=X-re/2}L()},setFilteredNodeIds(d){$=d,J()},panToNode(d){this.panToNodes([d])},panToNodes(d){if(!v||d.length===0)return;const x=d.map(U=>v.nodeMap.get(U)).filter(Boolean);if(x.length===0)return;y=new Set(d),t==null||t(d);const A=s.clientWidth,O=s.clientHeight;if(x.length===1)W={x:c.x,y:c.y,time:performance.now()},G={x:x[0].x-A/(2*c.scale),y:x[0].y-O/(2*c.scale)};else{let U=1/0,K=1/0,X=-1/0,Z=-1/0;for(const q of x)q.x<U&&(U=q.x),q.y<K&&(K=q.y),q.x>X&&(X=q.x),q.y>Z&&(Z=q.y);const re=be*4,te=X-U+re*2,xe=Z-K+re*2,ge=Math.min(A/te,O/xe,c.scale);c.scale=ge;const Ie=(U+X)/2,Me=(K+Z)/2;W={x:c.x,y:c.y,time:performance.now()},G={x:Ie-A/(2*c.scale),y:Me-O/(2*c.scale)}}g()},setEdges(d){j=d,J()},setEdgeLabels(d){H=d,J()},setTypeHulls(d){_=d,J()},setMinimap(d){I=d,J()},centerView(){if(v){if(c={x:0,y:0,scale:1},v.nodes.length>0){let d=1/0,x=1/0,A=-1/0,O=-1/0;for(const U of v.nodes)U.x<d&&(d=U.x),U.y<x&&(x=U.y),U.x>A&&(A=U.x),U.y>O&&(O=U.y);c.x=(d+A)/2-s.clientWidth/2,c.y=(x+O)/2-s.clientHeight/2}J()}},panBy(d,x){c.x+=d/c.scale,c.y+=x/c.scale,J()},zoomBy(d){const x=s.clientWidth/2,A=s.clientHeight/2,[O,U]=Q(x,A);c.scale=Math.max(E.zoomMin,Math.min(E.zoomMax,c.scale*d)),c.x=O-x/c.scale,c.y=U-A/c.scale,J()},reheat(){m=.5,cancelAnimationFrame(M),L()},exportImage(d){if(!v)return"";const x=s.width,A=s.height;if(d==="png"){const X=document.createElement("canvas");X.width=x,X.height=A;const Z=X.getContext("2d");return Z.fillStyle=ue("--bg")||"#141414",Z.fillRect(0,0,x,A),Z.drawImage(s,0,0),we(Z,x,A),X.toDataURL("image/png")}const O=s.toDataURL("image/png"),U=Math.max(16,Math.round(x/80)),K=`<svg xmlns="http://www.w3.org/2000/svg" width="${x}" height="${A}">
2
+ <image href="${O}" width="${x}" height="${A}"/>
3
+ <text x="${x-20}" y="${A-16}" text-anchor="end" font-family="system-ui, sans-serif" font-size="${U}" fill="#ffffff" opacity="0.4">backpackontology.com</text>
4
+ </svg>`;return"data:image/svg+xml;charset=utf-8,"+encodeURIComponent(K)},enterFocus(d,x){if(!Y||!v)return;P||(z=v,R={...c}),P=d,k=x;const A=wt(Y,d,x);if(cancelAnimationFrame(M),v=qe(A),m=1,y=new Set(d),$=null,c={x:0,y:0,scale:1},v.nodes.length>0){let O=1/0,U=1/0,K=-1/0,X=-1/0;for(const te of v.nodes)te.x<O&&(O=te.x),te.y<U&&(U=te.y),te.x>K&&(K=te.x),te.y>X&&(X=te.y);const Z=(O+K)/2,re=(U+X)/2;c.x=Z-s.clientWidth/2,c.y=re-s.clientHeight/2}L(),i==null||i({seedNodeIds:d,hops:x,totalNodes:A.nodes.length})},exitFocus(){!P||!z||(cancelAnimationFrame(M),v=z,c=R??{x:0,y:0,scale:1},P=null,z=null,R=null,y=new Set,$=null,J(),i==null||i(null))},isFocused(){return P!==null},getFocusInfo(){return!P||!v?null:{seedNodeIds:P,hops:k,totalNodes:v.nodes.length}},getNodeIds(){if(!v)return[];if(P){const d=new Set(P),x=v.nodes.filter(O=>d.has(O.id)).map(O=>O.id),A=v.nodes.filter(O=>!d.has(O.id)).map(O=>O.id);return[...x,...A]}return v.nodes.map(d=>d.id)},destroy(){cancelAnimationFrame(M),me.disconnect()}};function we(d,x,A){const O=Math.max(16,Math.round(x/80));d.save(),d.font=`${O}px system-ui, sans-serif`,d.fillStyle="rgba(255, 255, 255, 0.4)",d.textAlign="right",d.textBaseline="bottom",d.fillText("backpackontology.com",x-20,A-16),d.restore()}}function Fe(o){for(const t of Object.values(o.properties))if(typeof t=="string")return t;return o.id}const Mt="✎";function Tt(o,t,i,p){const l=document.createElement("div");l.id="info-panel",l.className="info-panel hidden",o.appendChild(l);let E=!1,s=[],n=-1,S=!1,c=null,v=[],m=!1,M=[],y=-1;function $(){l.classList.add("hidden"),l.classList.remove("info-panel-maximized"),l.innerHTML="",E=!1,s=[],n=-1}function j(k){!c||!i||(n<s.length-1&&(s=s.slice(0,n+1)),s.push(k),n=s.length-1,S=!0,i(k),S=!1)}function H(){if(n<=0||!c)return;n--,S=!0;const k=s[n];i==null||i(k),Y(k,c),S=!1}function _(){if(n>=s.length-1||!c)return;n++,S=!0;const k=s[n];i==null||i(k),Y(k,c),S=!1}function I(){const k=document.createElement("div");k.className="info-panel-toolbar";const z=document.createElement("button");z.className="info-toolbar-btn",z.textContent="←",z.title="Back",z.disabled=n<=0,z.addEventListener("click",H),k.appendChild(z);const R=document.createElement("button");if(R.className="info-toolbar-btn",R.textContent="→",R.title="Forward",R.disabled=n>=s.length-1,R.addEventListener("click",_),k.appendChild(R),p&&v.length>0){const ne=document.createElement("button");ne.className="info-toolbar-btn info-focus-btn",ne.textContent="◎",ne.title="Focus on neighborhood (F)",ne.disabled=m,m&&(ne.style.opacity="0.3"),ne.addEventListener("click",()=>{m||p(v)}),k.appendChild(ne)}const G=document.createElement("button");G.className="info-toolbar-btn",G.textContent=E?"⎘":"⛶",G.title=E?"Restore":"Maximize",G.addEventListener("click",()=>{E=!E,l.classList.toggle("info-panel-maximized",E),G.textContent=E?"⎘":"⛶",G.title=E?"Restore":"Maximize"}),k.appendChild(G);const W=document.createElement("button");return W.className="info-toolbar-btn info-close-btn",W.textContent="×",W.title="Close",W.addEventListener("click",$),k.appendChild(W),k}function Y(k,z){const R=z.nodes.find(C=>C.id===k);if(!R)return;const G=z.edges.filter(C=>C.sourceId===k||C.targetId===k);M=G.map(C=>C.sourceId===k?C.targetId:C.sourceId),y=-1,l.innerHTML="",l.classList.remove("hidden"),E&&l.classList.add("info-panel-maximized");const W=document.createElement("div");W.className="info-panel-header",W.appendChild(I());const ne=document.createElement("div");ne.className="info-header";const le=document.createElement("span");if(le.className="info-type-badge",le.textContent=R.type,le.style.backgroundColor=fe(R.type),t){le.classList.add("info-editable");const C=document.createElement("button");C.className="info-inline-edit",C.textContent=Mt,C.addEventListener("click",N=>{N.stopPropagation();const u=document.createElement("input");u.type="text",u.className="info-edit-inline-input",u.value=R.type,le.textContent="",le.appendChild(u),u.focus(),u.select();const e=()=>{const a=u.value.trim();a&&a!==R.type?t.onChangeNodeType(k,a):(le.textContent=R.type,le.appendChild(C))};u.addEventListener("blur",e),u.addEventListener("keydown",a=>{a.key==="Enter"&&u.blur(),a.key==="Escape"&&(u.value=R.type,u.blur())})}),le.appendChild(C)}const me=document.createElement("h3");me.className="info-label",me.textContent=Fe(R);const Q=document.createElement("span");Q.className="info-id",Q.textContent=R.id,ne.appendChild(le),ne.appendChild(me),ne.appendChild(Q),W.appendChild(ne),l.appendChild(W);const ae=document.createElement("div");ae.className="info-panel-body";const J=Object.keys(R.properties),f=Re("Properties");if(J.length>0){const C=document.createElement("dl");C.className="info-props";for(const N of J){const u=document.createElement("dt");u.textContent=N;const e=document.createElement("dd");if(t){const a=ze(R.properties[N]),r=document.createElement("textarea");r.className="info-edit-input",r.value=a,r.rows=1,r.addEventListener("input",()=>Ge(r)),r.addEventListener("keydown",T=>{T.key==="Enter"&&!T.shiftKey&&(T.preventDefault(),r.blur())}),r.addEventListener("blur",()=>{const T=r.value;T!==a&&t.onUpdateNode(k,{[N]:Ft(T)})}),e.appendChild(r),requestAnimationFrame(()=>Ge(r));const D=document.createElement("button");D.className="info-delete-prop",D.textContent="×",D.title=`Remove ${N}`,D.addEventListener("click",()=>{const T={...R.properties};delete T[N],t.onUpdateNode(k,T)}),e.appendChild(D)}else e.appendChild(At(R.properties[N]));C.appendChild(u),C.appendChild(e)}f.appendChild(C)}if(t){const C=document.createElement("button");C.className="info-add-btn",C.textContent="+ Add property",C.addEventListener("click",()=>{const N=document.createElement("div");N.className="info-add-row";const u=document.createElement("input");u.type="text",u.className="info-edit-input",u.placeholder="key";const e=document.createElement("input");e.type="text",e.className="info-edit-input",e.placeholder="value";const a=document.createElement("button");a.className="info-add-save",a.textContent="Add",a.addEventListener("click",()=>{u.value&&t.onAddProperty(k,u.value,e.value)}),N.appendChild(u),N.appendChild(e),N.appendChild(a),f.appendChild(N),u.focus()}),f.appendChild(C)}if(ae.appendChild(f),G.length>0){const C=Re(`Connections (${G.length})`),N=document.createElement("ul");N.className="info-connections";for(const u of G){const e=u.sourceId===k,a=e?u.targetId:u.sourceId,r=z.nodes.find(oe=>oe.id===a),D=r?Fe(r):a,T=document.createElement("li");if(T.className="info-connection",i&&r&&(T.classList.add("info-connection-link"),T.addEventListener("click",oe=>{oe.target.closest(".info-delete-edge")||j(a)})),r){const oe=document.createElement("span");oe.className="info-target-dot",oe.style.backgroundColor=fe(r.type),T.appendChild(oe)}const V=document.createElement("span");V.className="info-arrow",V.textContent=e?"→":"←";const ee=document.createElement("span");ee.className="info-edge-type",ee.textContent=u.type;const he=document.createElement("span");he.className="info-target",he.textContent=D,T.appendChild(V),T.appendChild(ee),T.appendChild(he);const ve=Object.keys(u.properties);if(ve.length>0){const oe=document.createElement("div");oe.className="info-edge-props";for(const we of ve){const d=document.createElement("span");d.className="info-edge-prop",d.textContent=`${we}: ${ze(u.properties[we])}`,oe.appendChild(d)}T.appendChild(oe)}if(t){const oe=document.createElement("button");oe.className="info-delete-edge",oe.textContent="×",oe.title="Remove connection",oe.addEventListener("click",we=>{we.stopPropagation(),t.onDeleteEdge(u.id)}),T.appendChild(oe)}N.appendChild(T)}C.appendChild(N),ae.appendChild(C)}const h=Re("Timestamps"),w=document.createElement("dl");w.className="info-props";const g=document.createElement("dt");g.textContent="created";const L=document.createElement("dd");L.textContent=Ve(R.createdAt);const F=document.createElement("dt");F.textContent="updated";const B=document.createElement("dd");if(B.textContent=Ve(R.updatedAt),w.appendChild(g),w.appendChild(L),w.appendChild(F),w.appendChild(B),h.appendChild(w),ae.appendChild(h),t){const C=document.createElement("div");C.className="info-section info-danger";const N=document.createElement("button");N.className="info-delete-node",N.textContent="Delete node",N.addEventListener("click",()=>{t.onDeleteNode(k),$()}),C.appendChild(N),ae.appendChild(C)}l.appendChild(ae)}function P(k,z){const R=new Set(k),G=z.nodes.filter(h=>R.has(h.id));if(G.length===0)return;const W=z.edges.filter(h=>R.has(h.sourceId)&&R.has(h.targetId));l.innerHTML="",l.classList.remove("hidden"),E&&l.classList.add("info-panel-maximized"),l.appendChild(I());const ne=document.createElement("div");ne.className="info-header";const le=document.createElement("h3");le.className="info-label",le.textContent=`${G.length} nodes selected`,ne.appendChild(le);const me=document.createElement("div");me.style.cssText="display:flex;flex-wrap:wrap;gap:4px;margin-top:6px";const Q=new Map;for(const h of G)Q.set(h.type,(Q.get(h.type)??0)+1);for(const[h,w]of Q){const g=document.createElement("span");g.className="info-type-badge",g.style.backgroundColor=fe(h),g.textContent=w>1?`${h} (${w})`:h,me.appendChild(g)}ne.appendChild(me),l.appendChild(ne);const ae=Re("Selected Nodes"),J=document.createElement("ul");J.className="info-connections";for(const h of G){const w=document.createElement("li");w.className="info-connection",i&&(w.classList.add("info-connection-link"),w.addEventListener("click",()=>{j(h.id)}));const g=document.createElement("span");g.className="info-target-dot",g.style.backgroundColor=fe(h.type);const L=document.createElement("span");L.className="info-target",L.textContent=Fe(h);const F=document.createElement("span");F.className="info-edge-type",F.textContent=h.type,w.appendChild(g),w.appendChild(L),w.appendChild(F),J.appendChild(w)}ae.appendChild(J),l.appendChild(ae);const f=Re(W.length>0?`Connections Between Selected (${W.length})`:"Connections Between Selected");if(W.length===0){const h=document.createElement("p");h.style.cssText="font-size:12px;color:var(--text-dim)",h.textContent="No direct connections between selected nodes",f.appendChild(h)}else{const h=document.createElement("ul");h.className="info-connections";for(const w of W){const g=z.nodes.find(T=>T.id===w.sourceId),L=z.nodes.find(T=>T.id===w.targetId),F=g?Fe(g):w.sourceId,B=L?Fe(L):w.targetId,C=document.createElement("li");if(C.className="info-connection",g){const T=document.createElement("span");T.className="info-target-dot",T.style.backgroundColor=fe(g.type),C.appendChild(T)}const N=document.createElement("span");N.className="info-target",N.textContent=F;const u=document.createElement("span");u.className="info-arrow",u.textContent="→";const e=document.createElement("span");e.className="info-edge-type",e.textContent=w.type;const a=document.createElement("span");if(a.className="info-arrow",a.textContent="→",C.appendChild(N),C.appendChild(u),C.appendChild(e),C.appendChild(a),L){const T=document.createElement("span");T.className="info-target-dot",T.style.backgroundColor=fe(L.type),C.appendChild(T)}const r=document.createElement("span");r.className="info-target",r.textContent=B,C.appendChild(r);const D=Object.keys(w.properties);if(D.length>0){const T=document.createElement("div");T.className="info-edge-props";for(const V of D){const ee=document.createElement("span");ee.className="info-edge-prop",ee.textContent=`${V}: ${ze(w.properties[V])}`,T.appendChild(ee)}C.appendChild(T)}h.appendChild(C)}f.appendChild(h)}l.appendChild(f)}return{show(k,z){if(c=z,v=k,k.length===1&&!S){const R=k[0];s[n]!==R&&(n<s.length-1&&(s=s.slice(0,n+1)),s.push(R),n=s.length-1)}k.length===1?Y(k[0],z):k.length>1&&P(k,z)},hide:$,goBack:H,goForward:_,cycleConnection(k){if(M.length===0)return null;y===-1?y=k===1?0:M.length-1:(y+=k,y>=M.length&&(y=0),y<0&&(y=M.length-1));const z=l.querySelectorAll(".info-connection");return z.forEach((R,G)=>{R.classList.toggle("info-connection-active",G===y)}),y>=0&&z[y]&&z[y].scrollIntoView({block:"nearest"}),M[y]??null},setFocusDisabled(k){m=k;const z=l.querySelector(".info-focus-btn");z&&(z.disabled=k,z.style.opacity=k?"0.3":"")},get visible(){return!l.classList.contains("hidden")}}}function Re(o){const t=document.createElement("div");t.className="info-section";const i=document.createElement("h4");return i.className="info-section-title",i.textContent=o,t.appendChild(i),t}function At(o){if(Array.isArray(o)){const i=document.createElement("div");i.className="info-array";for(const p of o){const l=document.createElement("span");l.className="info-tag",l.textContent=String(p),i.appendChild(l)}return i}if(o!==null&&typeof o=="object"){const i=document.createElement("pre");return i.className="info-json",i.textContent=JSON.stringify(o,null,2),i}const t=document.createElement("span");return t.className="info-value",t.textContent=String(o??""),t}function ze(o){return Array.isArray(o)?o.map(String).join(", "):o!==null&&typeof o=="object"?JSON.stringify(o):String(o??"")}function Ft(o){const t=o.trim();if(t==="true")return!0;if(t==="false")return!1;if(t!==""&&!isNaN(Number(t)))return Number(t);if(t.startsWith("[")&&t.endsWith("]")||t.startsWith("{")&&t.endsWith("}"))try{return JSON.parse(t)}catch{return o}return o}function Ge(o){o.style.height="auto",o.style.height=o.scrollHeight+"px"}function Ve(o){try{return new Date(o).toLocaleString()}catch{return o}}function ct(o){for(const t of Object.values(o.properties))if(typeof t=="string")return t;return o.id}function Ke(o,t){const i=t.toLowerCase();if(ct(o).toLowerCase().includes(i)||o.type.toLowerCase().includes(i))return!0;for(const p of Object.values(o.properties))if(typeof p=="string"&&p.toLowerCase().includes(i))return!0;return!1}function Rt(o,t){const i=(t==null?void 0:t.maxResults)??8,p=(t==null?void 0:t.debounceMs)??150;let l=null,E=null,s=null,n=null;const S=document.createElement("div");S.className="search-overlay hidden";const c=document.createElement("div");c.className="search-input-wrap";const v=document.createElement("input");v.className="search-input",v.type="text",v.placeholder="Search nodes...",v.setAttribute("autocomplete","off"),v.setAttribute("spellcheck","false");const m=document.createElement("kbd");m.className="search-kbd",m.textContent="/",c.appendChild(v),c.appendChild(m);const M=document.createElement("ul");M.className="search-results hidden",S.appendChild(c),S.appendChild(M),o.appendChild(S);function y(){if(!l)return null;const I=v.value.trim();if(I.length===0)return null;const Y=new Set;for(const P of l.nodes)Ke(P,I)&&Y.add(P.id);return Y}function $(){const I=y();E==null||E(I),j()}function j(){M.innerHTML="",H=-1;const I=v.value.trim();if(!l||I.length===0){M.classList.add("hidden");return}const Y=[];for(const P of l.nodes)if(Ke(P,I)&&(Y.push(P),Y.length>=i))break;if(Y.length===0){M.classList.add("hidden");return}for(const P of Y){const k=document.createElement("li");k.className="search-result-item";const z=document.createElement("span");z.className="search-result-dot",z.style.backgroundColor=fe(P.type);const R=document.createElement("span");R.className="search-result-label";const G=ct(P);R.textContent=G.length>36?G.slice(0,34)+"...":G;const W=document.createElement("span");W.className="search-result-type",W.textContent=P.type,k.appendChild(z),k.appendChild(R),k.appendChild(W),k.addEventListener("click",()=>{s==null||s(P.id),v.value="",M.classList.add("hidden"),$()}),M.appendChild(k)}M.classList.remove("hidden")}v.addEventListener("input",()=>{n&&clearTimeout(n),n=setTimeout($,p)});let H=-1;function _(){const I=M.querySelectorAll(".search-result-item");I.forEach((Y,P)=>{Y.classList.toggle("search-result-active",P===H)}),H>=0&&I[H]&&I[H].scrollIntoView({block:"nearest"})}return v.addEventListener("keydown",I=>{const Y=M.querySelectorAll(".search-result-item");I.key==="ArrowDown"?(I.preventDefault(),Y.length>0&&(H=Math.min(H+1,Y.length-1),_())):I.key==="ArrowUp"?(I.preventDefault(),Y.length>0&&(H=Math.max(H-1,0),_())):I.key==="Enter"?(I.preventDefault(),H>=0&&Y[H]?Y[H].click():Y.length>0&&Y[0].click(),v.blur()):I.key==="Escape"&&(v.value="",v.blur(),M.classList.add("hidden"),H=-1,$())}),document.addEventListener("click",I=>{S.contains(I.target)||M.classList.add("hidden")}),v.addEventListener("focus",()=>m.classList.add("hidden")),v.addEventListener("blur",()=>{v.value.length===0&&m.classList.remove("hidden")}),{setLearningGraphData(I){l=I,v.value="",M.classList.add("hidden"),l&&l.nodes.length>0?S.classList.remove("hidden"):S.classList.add("hidden")},onFilterChange(I){E=I},onNodeSelect(I){s=I},clear(){v.value="",M.classList.add("hidden"),E==null||E(null)},focus(){v.focus()}}}function Bt(o,t){let i=null,p=null,l=!0,E=null,s=!0,n=!0,S=!0,c="types",v="",m="",M=[];const y={types:new Set,nodeIds:new Set};function $(){if(!i)return[];const f=new Set;for(const h of i.nodes)y.types.has(h.type)&&f.add(h.id);for(const h of y.nodeIds)f.add(h);return[...f]}function j(f){if(y.nodeIds.has(f))return!0;const h=i==null?void 0:i.nodes.find(w=>w.id===f);return h?y.types.has(h.type):!1}function H(){return y.types.size===0&&y.nodeIds.size===0}function _(){const f=$();t.onFocusChange(f.length>0?f:null)}const I=document.createElement("button");I.className="tools-pane-toggle hidden",I.title="Graph Inspector",I.innerHTML='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16"/><path d="M4 12h16"/><path d="M4 17h10"/></svg>';const Y=document.createElement("div");Y.className="tools-pane-content hidden",o.appendChild(I),o.appendChild(Y),I.addEventListener("click",()=>{var f;l=!l,Y.classList.toggle("hidden",l),I.classList.toggle("active",!l),l||(f=t.onOpen)==null||f.call(t)});function P(){if(Y.innerHTML="",!p)return;const f=document.createElement("div");f.className="tools-pane-summary",f.innerHTML=`<span>${p.nodeCount} nodes</span><span class="tools-pane-sep">&middot;</span><span>${p.edgeCount} edges</span><span class="tools-pane-sep">&middot;</span><span>${p.types.length} types</span>`,Y.appendChild(f);const h=document.createElement("div");h.className="tools-pane-tabs";const w=[{id:"types",label:"Types"},{id:"insights",label:"Insights"},{id:"controls",label:"Controls"}];for(const L of w){const F=document.createElement("button");F.className="tools-pane-tab",c===L.id&&F.classList.add("tools-pane-tab-active"),F.textContent=L.label,F.addEventListener("click",()=>{c=L.id,P()}),h.appendChild(F)}Y.appendChild(h),H()||z(),c==="types"&&p.types.length>5?Y.appendChild(G("Filter types...",v,L=>{v=L,k()})):c==="insights"&&p.orphans.length+p.singletons.length+p.emptyNodes.length>5&&Y.appendChild(G("Filter issues...",m,F=>{m=F,k()}));const g=document.createElement("div");g.className="tools-pane-tab-content",Y.appendChild(g),k()}function k(){const f=Y.querySelector(".tools-pane-tab-content");f&&(f.innerHTML="",c==="types"?W(f):c==="insights"?ne(f):c==="controls"&&le(f))}function z(){if(!p||!i)return;const f=$();Y.appendChild(Q("Focused",h=>{for(const F of y.types){const B=p.types.find(r=>r.name===F);if(!B)continue;const C=document.createElement("div");C.className="tools-pane-row tools-pane-clickable";const N=document.createElement("span");N.className="tools-pane-dot",N.style.backgroundColor=fe(B.name);const u=document.createElement("span");u.className="tools-pane-name",u.textContent=B.name;const e=document.createElement("span");e.className="tools-pane-count",e.textContent=`${B.count} nodes`;const a=document.createElement("button");a.className="tools-pane-edit tools-pane-focus-active",a.style.opacity="1",a.textContent="×",a.title=`Remove ${B.name} from focus`,C.appendChild(N),C.appendChild(u),C.appendChild(e),C.appendChild(a),a.addEventListener("click",r=>{r.stopPropagation(),y.types.delete(B.name),_(),P()}),h.appendChild(C)}for(const F of y.nodeIds){const B=i.nodes.find(D=>D.id===F);if(!B)continue;const C=Je(B.properties)??B.id,N=document.createElement("div");N.className="tools-pane-row tools-pane-clickable";const u=document.createElement("span");u.className="tools-pane-dot",u.style.backgroundColor=fe(B.type);const e=document.createElement("span");e.className="tools-pane-name",e.textContent=C;const a=document.createElement("span");a.className="tools-pane-count",a.textContent=B.type;const r=document.createElement("button");r.className="tools-pane-edit tools-pane-focus-active",r.style.opacity="1",r.textContent="×",r.title=`Remove ${C} from focus`,N.appendChild(u),N.appendChild(e),N.appendChild(a),N.appendChild(r),N.addEventListener("click",D=>{D.target.closest(".tools-pane-edit")||t.onNavigateToNode(F)}),r.addEventListener("click",D=>{D.stopPropagation(),y.nodeIds.delete(F),_(),P()}),h.appendChild(N)}const w=document.createElement("div");w.className="tools-pane-row tools-pane-clickable tools-pane-focus-clear";const g=document.createElement("span");g.className="tools-pane-name",g.style.color="var(--accent)",g.textContent=`${f.length} total`;const L=document.createElement("span");L.className="tools-pane-badge",L.textContent="clear all",w.appendChild(g),w.appendChild(L),w.addEventListener("click",()=>{y.types.clear(),y.nodeIds.clear(),_(),P()}),h.appendChild(w)}))}function R(f){const h=document.createElement("div");h.className="tools-pane-row tools-pane-clickable",E===f.name&&h.classList.add("active");const w=document.createElement("span");w.className="tools-pane-dot",w.style.backgroundColor=fe(f.name);const g=document.createElement("span");g.className="tools-pane-name",g.textContent=f.name;const L=document.createElement("span");L.className="tools-pane-count",L.textContent=String(f.count);const F=document.createElement("button");F.className="tools-pane-edit tools-pane-focus-toggle",y.types.has(f.name)&&F.classList.add("tools-pane-focus-active"),F.textContent="◎",F.title=y.types.has(f.name)?`Remove ${f.name} from focus`:`Add ${f.name} to focus`;const B=document.createElement("button");return B.className="tools-pane-edit",B.textContent="✎",B.title=`Rename all ${f.name} nodes`,h.appendChild(w),h.appendChild(g),h.appendChild(L),h.appendChild(F),h.appendChild(B),h.addEventListener("click",C=>{C.target.closest(".tools-pane-edit")||(E===f.name?(E=null,t.onFilterByType(null)):(E=f.name,t.onFilterByType(f.name)),P())}),F.addEventListener("click",C=>{C.stopPropagation(),y.types.has(f.name)?y.types.delete(f.name):y.types.add(f.name),_(),P()}),B.addEventListener("click",C=>{C.stopPropagation(),ae(h,f.name,N=>{N&&N!==f.name&&t.onRenameNodeType(f.name,N)})}),h}function G(f,h,w){const g=document.createElement("input");return g.type="text",g.className="tools-pane-search",g.placeholder=f,g.value=h,g.addEventListener("input",()=>w(g.value)),g}function W(f){if(!p)return;const h=v.toLowerCase();if(p.types.length){const g=p.types.filter(L=>!y.types.has(L.name)).filter(L=>!h||L.name.toLowerCase().includes(h));g.length>0&&f.appendChild(Q("Node Types",L=>{for(const F of g)L.appendChild(R(F))}))}const w=p.edgeTypes.filter(g=>!h||g.name.toLowerCase().includes(h));w.length&&f.appendChild(Q("Edge Types",g=>{for(const L of w){const F=document.createElement("div");F.className="tools-pane-row tools-pane-clickable";const B=document.createElement("span");B.className="tools-pane-name",B.textContent=L.name;const C=document.createElement("span");C.className="tools-pane-count",C.textContent=String(L.count);const N=document.createElement("button");N.className="tools-pane-edit",N.textContent="✎",N.title=`Rename all ${L.name} edges`,F.appendChild(B),F.appendChild(C),F.appendChild(N),N.addEventListener("click",u=>{u.stopPropagation(),ae(F,L.name,e=>{e&&e!==L.name&&t.onRenameEdgeType(L.name,e)})}),g.appendChild(F)}}))}function ne(f){if(!p)return;const h=m.toLowerCase(),w=p.mostConnected.filter(u=>!h||u.label.toLowerCase().includes(h)||u.type.toLowerCase().includes(h));w.length&&f.appendChild(Q("Most Connected",u=>{for(const e of w){const a=document.createElement("div");a.className="tools-pane-row tools-pane-clickable";const r=document.createElement("span");r.className="tools-pane-dot",r.style.backgroundColor=fe(e.type);const D=document.createElement("span");D.className="tools-pane-name",D.textContent=e.label;const T=document.createElement("span");T.className="tools-pane-count",T.textContent=`${e.connections}`;const V=document.createElement("button");V.className="tools-pane-edit tools-pane-focus-toggle",j(e.id)&&V.classList.add("tools-pane-focus-active"),V.textContent="◎",V.title=j(e.id)?`Remove ${e.label} from focus`:`Add ${e.label} to focus`,a.appendChild(r),a.appendChild(D),a.appendChild(T),a.appendChild(V),a.addEventListener("click",ee=>{ee.target.closest(".tools-pane-edit")||t.onNavigateToNode(e.id)}),V.addEventListener("click",ee=>{ee.stopPropagation(),y.nodeIds.has(e.id)?y.nodeIds.delete(e.id):y.nodeIds.add(e.id),_(),P()}),u.appendChild(a)}}));const g=p.orphans.filter(u=>!h||u.label.toLowerCase().includes(h)||u.type.toLowerCase().includes(h)),L=p.singletons.filter(u=>!h||u.name.toLowerCase().includes(h)),F=p.emptyNodes.filter(u=>!h||u.label.toLowerCase().includes(h)||u.type.toLowerCase().includes(h)),B=g.length>0,C=L.length>0,N=F.length>0;if(!B&&!C&&!N){const u=document.createElement("div");u.className="tools-pane-empty-msg",u.textContent="No issues found",f.appendChild(u);return}B&&f.appendChild(Q("Orphans",u=>{for(const e of g.slice(0,5)){const a=document.createElement("div");a.className="tools-pane-row tools-pane-clickable tools-pane-issue";const r=document.createElement("span");r.className="tools-pane-dot",r.style.backgroundColor=fe(e.type);const D=document.createElement("span");D.className="tools-pane-name",D.textContent=e.label;const T=document.createElement("span");T.className="tools-pane-badge",T.textContent="orphan";const V=document.createElement("button");V.className="tools-pane-edit tools-pane-focus-toggle",j(e.id)&&V.classList.add("tools-pane-focus-active"),V.textContent="◎",V.title=j(e.id)?`Remove ${e.label} from focus`:`Add ${e.label} to focus`,a.appendChild(r),a.appendChild(D),a.appendChild(T),a.appendChild(V),a.addEventListener("click",ee=>{ee.target.closest(".tools-pane-edit")||t.onNavigateToNode(e.id)}),V.addEventListener("click",ee=>{ee.stopPropagation(),y.nodeIds.has(e.id)?y.nodeIds.delete(e.id):y.nodeIds.add(e.id),_(),P()}),u.appendChild(a)}if(g.length>5){const e=document.createElement("div");e.className="tools-pane-more",e.textContent=`+ ${g.length-5} more orphans`,u.appendChild(e)}})),C&&f.appendChild(Q("Singletons",u=>{for(const e of L.slice(0,5)){const a=document.createElement("div");a.className="tools-pane-row tools-pane-issue";const r=document.createElement("span");r.className="tools-pane-dot",r.style.backgroundColor=fe(e.name);const D=document.createElement("span");D.className="tools-pane-name",D.textContent=e.name;const T=document.createElement("span");T.className="tools-pane-badge",T.textContent="1 node",a.appendChild(r),a.appendChild(D),a.appendChild(T),u.appendChild(a)}})),N&&f.appendChild(Q("Empty Nodes",u=>{for(const e of F.slice(0,5)){const a=document.createElement("div");a.className="tools-pane-row tools-pane-issue";const r=document.createElement("span");r.className="tools-pane-dot",r.style.backgroundColor=fe(e.type);const D=document.createElement("span");D.className="tools-pane-name",D.textContent=e.label;const T=document.createElement("span");T.className="tools-pane-badge",T.textContent="empty",a.appendChild(r),a.appendChild(D),a.appendChild(T),u.appendChild(a)}if(p.emptyNodes.length>5){const e=document.createElement("div");e.className="tools-pane-more",e.textContent=`+ ${p.emptyNodes.length-5} more empty nodes`,u.appendChild(e)}}))}function le(f){f.appendChild(Q("Display",h=>{const w=document.createElement("div");w.className="tools-pane-row tools-pane-clickable";const g=document.createElement("input");g.type="checkbox",g.checked=s,g.className="tools-pane-checkbox";const L=document.createElement("span");L.className="tools-pane-name",L.textContent="Edge labels",w.appendChild(g),w.appendChild(L),w.addEventListener("click",a=>{a.target!==g&&(g.checked=!g.checked),s=g.checked,t.onToggleEdgeLabels(s)}),h.appendChild(w);const F=document.createElement("div");F.className="tools-pane-row tools-pane-clickable";const B=document.createElement("input");B.type="checkbox",B.checked=n,B.className="tools-pane-checkbox";const C=document.createElement("span");C.className="tools-pane-name",C.textContent="Type regions",F.appendChild(B),F.appendChild(C),F.addEventListener("click",a=>{a.target!==B&&(B.checked=!B.checked),n=B.checked,t.onToggleTypeHulls(n)}),h.appendChild(F);const N=document.createElement("div");N.className="tools-pane-row tools-pane-clickable";const u=document.createElement("input");u.type="checkbox",u.checked=S,u.className="tools-pane-checkbox";const e=document.createElement("span");e.className="tools-pane-name",e.textContent="Minimap",N.appendChild(u),N.appendChild(e),N.addEventListener("click",a=>{a.target!==u&&(u.checked=!u.checked),S=u.checked,t.onToggleMinimap(S)}),h.appendChild(N)})),f.appendChild(Q("Layout",h=>{h.appendChild(me("Clustering",0,1,.02,.08,w=>{t.onLayoutChange("clusterStrength",w)})),h.appendChild(me("Spacing",.5,20,.5,1.5,w=>{t.onLayoutChange("spacing",w)})),h.appendChild(me("Pan speed",20,200,10,60,w=>{t.onPanSpeedChange(w)}))})),f.appendChild(Q("Export",h=>{const w=document.createElement("div");w.className="tools-pane-export-row";const g=document.createElement("button");g.className="tools-pane-export-btn",g.textContent="Export PNG",g.addEventListener("click",()=>t.onExport("png"));const L=document.createElement("button");L.className="tools-pane-export-btn",L.textContent="Export SVG",L.addEventListener("click",()=>t.onExport("svg")),w.appendChild(g),w.appendChild(L),h.appendChild(w)})),(t.onSnapshot||t.onRollback)&&f.appendChild(Q("Versions",h=>{const w=document.createElement("div");w.className="tools-pane-export-row";const g=document.createElement("button");if(g.className="tools-pane-export-btn",g.textContent="Save snapshot",g.addEventListener("click",()=>{nt("Save snapshot","Label (optional)").then(L=>{var F;(F=t.onSnapshot)==null||F.call(t,L||void 0)})}),w.appendChild(g),h.appendChild(w),M.length>0)for(const L of M){const F=document.createElement("div");F.className="tools-pane-row";const B=document.createElement("span");B.className="tools-pane-name";const C=Dt(L.timestamp);B.textContent=L.label?`#${L.version} ${L.label}`:`#${L.version}`,B.title=`${C} — ${L.nodeCount} nodes, ${L.edgeCount} edges`;const N=document.createElement("span");N.className="tools-pane-count",N.textContent=C;const u=document.createElement("button");u.className="tools-pane-edit",u.style.opacity="1",u.textContent="↩",u.title="Restore this snapshot",u.addEventListener("click",()=>{tt("Restore snapshot",`Restore snapshot #${L.version}? Current state will be lost unless you save a snapshot first.`).then(e=>{var a;e&&((a=t.onRollback)==null||a.call(t,L.version))})}),F.appendChild(B),F.appendChild(N),F.appendChild(u),h.appendChild(F)}else{const L=document.createElement("div");L.className="tools-pane-empty-msg",L.textContent="No snapshots yet",h.appendChild(L)}}))}function me(f,h,w,g,L,F){const B=document.createElement("div");B.className="tools-pane-slider-row";const C=document.createElement("span");C.className="tools-pane-slider-label",C.textContent=f;const N=document.createElement("input");N.type="range",N.className="tools-pane-slider",N.min=String(h),N.max=String(w),N.step=String(g),N.value=String(L);const u=document.createElement("span");return u.className="tools-pane-slider-value",u.textContent=String(L),N.addEventListener("input",()=>{const e=parseFloat(N.value);u.textContent=e%1===0?String(e):e.toFixed(2),F(e)}),B.appendChild(C),B.appendChild(N),B.appendChild(u),B}function Q(f,h){const w=document.createElement("div");w.className="tools-pane-section";const g=document.createElement("div");return g.className="tools-pane-heading",g.textContent=f,w.appendChild(g),h(w),w}function ae(f,h,w){const g=document.createElement("input");g.className="tools-pane-inline-input",g.value=h,g.type="text";const L=f.innerHTML;f.innerHTML="",f.classList.add("tools-pane-editing"),f.appendChild(g),g.focus(),g.select();function F(){const B=g.value.trim();f.classList.remove("tools-pane-editing"),B&&B!==h?w(B):f.innerHTML=L}g.addEventListener("keydown",B=>{B.key==="Enter"&&(B.preventDefault(),F()),B.key==="Escape"&&(f.innerHTML=L,f.classList.remove("tools-pane-editing"))}),g.addEventListener("blur",F)}function J(f){const h=new Map,w=new Map,g=new Map,L=new Set;for(const e of f.nodes)h.set(e.type,(h.get(e.type)??0)+1);for(const e of f.edges)w.set(e.type,(w.get(e.type)??0)+1),g.set(e.sourceId,(g.get(e.sourceId)??0)+1),g.set(e.targetId,(g.get(e.targetId)??0)+1),L.add(e.sourceId),L.add(e.targetId);const F=e=>Je(e.properties)??e.id,B=f.nodes.filter(e=>!L.has(e.id)).map(e=>({id:e.id,label:F(e),type:e.type})),C=[...h.entries()].filter(([,e])=>e===1).map(([e])=>({name:e})),N=f.nodes.filter(e=>Object.keys(e.properties).length===0).map(e=>({id:e.id,label:e.id,type:e.type})),u=f.nodes.map(e=>({id:e.id,label:F(e),type:e.type,connections:g.get(e.id)??0})).filter(e=>e.connections>0).sort((e,a)=>a.connections-e.connections).slice(0,5);return{nodeCount:f.nodes.length,edgeCount:f.edges.length,types:[...h.entries()].sort((e,a)=>a[1]-e[1]).map(([e,a])=>({name:e,count:a})),edgeTypes:[...w.entries()].sort((e,a)=>a[1]-e[1]).map(([e,a])=>({name:e,count:a})),orphans:B,singletons:C,emptyNodes:N,mostConnected:u}}return{collapse(){l=!0,Y.classList.add("hidden"),I.classList.remove("active")},addToFocusSet(f){for(const h of f)y.nodeIds.add(h);_(),P()},clearFocusSet(){y.types.clear(),y.nodeIds.clear(),_(),P()},setData(f){i=f,E=null,y.types.clear(),y.nodeIds.clear(),i&&i.nodes.length>0?(p=J(i),I.classList.remove("hidden"),P()):(p=null,I.classList.add("hidden"),Y.classList.add("hidden"))},setSnapshots(f){M=f,c==="controls"&&k()}}}function Dt(o){const t=Date.now()-new Date(o).getTime(),i=Math.floor(t/6e4);if(i<1)return"just now";if(i<60)return`${i}m ago`;const p=Math.floor(i/60);return p<24?`${p}h ago`:`${Math.floor(p/24)}d ago`}function Je(o){for(const t of Object.values(o))if(typeof t=="string")return t;return null}function Pt(o,t){const i=t.split("+"),p=i.pop(),l=i.map(S=>S.toLowerCase()),E=l.includes("ctrl")||l.includes("cmd")||l.includes("meta"),s=l.includes("shift"),n=l.includes("alt");return E!==(o.ctrlKey||o.metaKey)||!E&&(o.ctrlKey||o.metaKey)||s&&!o.shiftKey||n!==o.altKey?!1:p.toLowerCase()==="escape"?o.key==="Escape":p.toLowerCase()==="tab"?o.key==="Tab":l.length>0?o.key.toLowerCase()===p.toLowerCase():o.key===p}function Ot(){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"}}const $t=[{key:"Click",description:"Select node"},{key:"Ctrl+Click",description:"Multi-select nodes"},{key:"Drag",description:"Pan canvas"},{key:"Scroll",description:"Zoom in/out"}],zt=["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","escape"];function Ht(o){return o.split("+").map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join("+")}function Ut(o,t){const i=Ot(),p=document.createElement("div");p.className="shortcuts-overlay hidden";const l=document.createElement("div");l.className="shortcuts-modal";const E=document.createElement("h3");E.className="shortcuts-title",E.textContent="Keyboard Shortcuts";const s=document.createElement("div");s.className="shortcuts-list";for(const m of zt){const M=t[m];if(!M)continue;const y=document.createElement("div");y.className="shortcuts-row";const $=document.createElement("div");$.className="shortcuts-keys";const j=document.createElement("kbd");j.textContent=Ht(M),$.appendChild(j);const H=document.createElement("span");H.className="shortcuts-desc",H.textContent=i[m],y.appendChild($),y.appendChild(H),s.appendChild(y)}for(const m of $t){const M=document.createElement("div");M.className="shortcuts-row";const y=document.createElement("div");y.className="shortcuts-keys";const $=document.createElement("kbd");$.textContent=m.key,y.appendChild($);const j=document.createElement("span");j.className="shortcuts-desc",j.textContent=m.description,M.appendChild(y),M.appendChild(j),s.appendChild(M)}const n=document.createElement("button");n.className="shortcuts-close",n.textContent="×",l.appendChild(n),l.appendChild(E),l.appendChild(s),p.appendChild(l),o.appendChild(p);function S(){p.classList.remove("hidden")}function c(){p.classList.add("hidden")}function v(){p.classList.toggle("hidden")}return n.addEventListener("click",c),p.addEventListener("click",m=>{m.target===p&&c()}),{show:S,hide:c,toggle:v}}function jt(o){const t=document.createElement("div");return t.className="empty-state",t.innerHTML=`
5
+ <div class="empty-state-content">
6
+ <div class="empty-state-icon">
7
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
8
+ <path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 002 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0022 16z"/>
9
+ <polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
10
+ <line x1="12" y1="22.08" x2="12" y2="12"/>
11
+ </svg>
12
+ </div>
13
+ <h2 class="empty-state-title">No learning graphs yet</h2>
14
+ <p class="empty-state-desc">Connect Backpack to Claude, then start a conversation. Claude will build your first learning graph automatically.</p>
15
+ <div class="empty-state-setup">
16
+ <div class="empty-state-label">Add Backpack to Claude Code:</div>
17
+ <code class="empty-state-code">claude mcp add backpack-local -s user -- npx backpack-ontology@latest</code>
18
+ </div>
19
+ <p class="empty-state-hint">Press <kbd>?</kbd> for keyboard shortcuts</p>
20
+ </div>
21
+ `,o.appendChild(t),{show(){t.classList.remove("hidden")},hide(){t.classList.add("hidden")}}}const Yt=30;function Xt(){let o=[],t=[];return{push(i){o.push(JSON.stringify(i)),o.length>Yt&&o.shift(),t=[]},undo(i){return o.length===0?null:(t.push(JSON.stringify(i)),JSON.parse(o.pop()))},redo(i){return t.length===0?null:(o.push(JSON.stringify(i)),JSON.parse(t.pop()))},canUndo(){return o.length>0},canRedo(){return t.length>0},clear(){o=[],t=[]}}}const qt={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"},Wt={edges:!0,edgeLabels:!0,typeHulls:!0,minimap:!0,theme:"system"},_t={spacing:1.5,clustering:.08},Gt={panSpeed:60,panFastMultiplier:3,zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300},Vt={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15},Kt={maxSearchResults:8,maxQualityItems:5,maxMostConnected:5,searchDebounceMs:150},Jt={keybindings:qt,display:Wt,layout:_t,navigation:Gt,lod:Vt,limits:Kt};let ie="",b=null;async function Zt(){const o=document.getElementById("canvas-container"),t={...Jt};try{const e=await fetch("/api/config");if(e.ok){const a=await e.json();Object.assign(t.keybindings,a.keybindings??{}),Object.assign(t.display,a.display??{}),Object.assign(t.layout,a.layout??{}),Object.assign(t.navigation,a.navigation??{}),Object.assign(t.lod,a.lod??{}),Object.assign(t.limits,a.limits??{})}}catch{}const i=t.keybindings,p=window.matchMedia("(prefers-color-scheme: dark)"),l=t.display.theme==="system"?p.matches?"dark":"light":t.display.theme,s=localStorage.getItem("backpack-theme")??l;document.documentElement.setAttribute("data-theme",s);const n=document.createElement("button");n.className="theme-toggle",n.textContent=s==="light"?"☾":"☼",n.title="Toggle light/dark mode",n.addEventListener("click",()=>{const a=document.documentElement.getAttribute("data-theme")==="light"?"dark":"light";document.documentElement.setAttribute("data-theme",a),localStorage.setItem("backpack-theme",a),n.textContent=a==="light"?"☾":"☼"}),o.appendChild(n);const S=Xt();async function c(){if(!ie||!b)return;b.metadata.updatedAt=new Date().toISOString(),await He(ie,b),m.loadGraph(b),k.setLearningGraphData(b),z.setData(b);const e=await Be();ae.setSummaries(e)}async function v(e){b=e,await He(ie,b),m.loadGraph(b),k.setLearningGraphData(b),z.setData(b);const a=await Be();ae.setSummaries(a)}let m;const M=Tt(o,{onUpdateNode(e,a){if(!b)return;S.push(b);const r=b.nodes.find(D=>D.id===e);r&&(r.properties={...r.properties,...a},r.updatedAt=new Date().toISOString(),c().then(()=>M.show([e],b)))},onChangeNodeType(e,a){if(!b)return;S.push(b);const r=b.nodes.find(D=>D.id===e);r&&(r.type=a,r.updatedAt=new Date().toISOString(),c().then(()=>M.show([e],b)))},onDeleteNode(e){b&&(S.push(b),b.nodes=b.nodes.filter(a=>a.id!==e),b.edges=b.edges.filter(a=>a.sourceId!==e&&a.targetId!==e),c())},onDeleteEdge(e){var r;if(!b)return;S.push(b);const a=(r=b.edges.find(D=>D.id===e))==null?void 0:r.sourceId;b.edges=b.edges.filter(D=>D.id!==e),c().then(()=>{a&&b&&M.show([a],b)})},onAddProperty(e,a,r){if(!b)return;S.push(b);const D=b.nodes.find(T=>T.id===e);D&&(D.properties[a]=r,D.updatedAt=new Date().toISOString(),c().then(()=>M.show([e],b)))}},e=>{m.panToNode(e)},e=>{z.addToFocusSet(e)}),y=window.matchMedia("(max-width: 768px)");let $=[],j=t.display.edges,H=t.navigation.panSpeed,_=-1,I=null;function Y(e){I&&I.remove(),I=document.createElement("div"),I.className="focus-indicator";const a=document.createElement("span");a.className="focus-indicator-label",a.textContent=`Focused: ${e.totalNodes} nodes`;const r=document.createElement("span");r.className="focus-indicator-hops",r.textContent=`${e.hops}`;const D=document.createElement("button");D.className="focus-indicator-btn",D.textContent="−",D.title="Fewer hops",D.disabled=e.hops===0,D.addEventListener("click",()=>{m.enterFocus(e.seedNodeIds,Math.max(0,e.hops-1))});const T=document.createElement("button");T.className="focus-indicator-btn",T.textContent="+",T.title="More hops",T.disabled=!1,T.addEventListener("click",()=>{m.enterFocus(e.seedNodeIds,e.hops+1)});const V=document.createElement("button");V.className="focus-indicator-btn focus-indicator-exit",V.textContent="×",V.title="Exit focus (Esc)",V.addEventListener("click",()=>z.clearFocusSet()),I.appendChild(a),I.appendChild(D),I.appendChild(r),I.appendChild(T),I.appendChild(V)}function P(){I&&(I.remove(),I=null)}m=It(o,e=>{$=e??[],e&&e.length>0&&b?(M.show(e,b),y.matches&&z.collapse(),g(ie,e)):(M.hide(),ie&&g(ie))},e=>{if(e){Y(e);const a=o.querySelector(".canvas-top-left");a&&I&&a.appendChild(I),g(ie,e.seedNodeIds),M.setFocusDisabled(e.hops===0)}else P(),M.setFocusDisabled(!1),ie&&g(ie)},{lod:t.lod,navigation:t.navigation});const k=Rt(o,{maxResults:t.limits.maxSearchResults,debounceMs:t.limits.searchDebounceMs}),z=Bt(o,{onFilterByType(e){if(b)if(e===null)m.setFilteredNodeIds(null);else{const a=new Set(((b==null?void 0:b.nodes)??[]).filter(r=>r.type===e).map(r=>r.id));m.setFilteredNodeIds(a)}},onNavigateToNode(e){m.panToNode(e),b&&M.show([e],b)},onFocusChange(e){e&&e.length>0?m.enterFocus(e,0):m.isFocused()&&m.exitFocus()},onRenameNodeType(e,a){if(b){S.push(b);for(const r of b.nodes)r.type===e&&(r.type=a,r.updatedAt=new Date().toISOString());c()}},onRenameEdgeType(e,a){if(b){S.push(b);for(const r of b.edges)r.type===e&&(r.type=a);c()}},onToggleEdgeLabels(e){m.setEdgeLabels(e)},onToggleTypeHulls(e){m.setTypeHulls(e)},onToggleMinimap(e){m.setMinimap(e)},onLayoutChange(e,a){Ae({[e]:a}),m.reheat()},onPanSpeedChange(e){H=e},onExport(e){const a=m.exportImage(e);if(!a)return;const r=document.createElement("a");r.download=`${ie||"graph"}.${e}`,r.href=a,r.click()},onSnapshot:async e=>{ie&&(await ft(ie,e),await f(ie))},onRollback:async e=>{ie&&(await gt(ie,e),b=await De(ie),m.loadGraph(b),k.setLearningGraphData(b),z.setData(b),await f(ie))},onOpen(){y.matches&&M.hide()}}),R=document.createElement("div");R.className="canvas-top-bar";const G=document.createElement("div");G.className="canvas-top-left";const W=document.createElement("div");W.className="canvas-top-center";const ne=document.createElement("div");ne.className="canvas-top-right";const le=o.querySelector(".tools-pane-toggle");le&&G.appendChild(le);const me=o.querySelector(".search-overlay");me&&W.appendChild(me);const Q=o.querySelector(".zoom-controls");Q&&ne.appendChild(Q),ne.appendChild(n),R.appendChild(G),R.appendChild(W),R.appendChild(ne),o.appendChild(R),k.onFilterChange(e=>{m.setFilteredNodeIds(e)}),k.onNodeSelect(e=>{m.isFocused()&&z.clearFocusSet(),m.panToNode(e),b&&M.show([e],b)});const ae=yt(document.getElementById("sidebar"),{onSelect:e=>F(e),onRename:async(e,a)=>{await dt(e,a),ie===e&&(ie=a);const r=await Be();ae.setSummaries(r),ae.setActive(ie),ie===a&&(b=await De(a),m.loadGraph(b),k.setLearningGraphData(b),z.setData(b))},onBranchSwitch:async(e,a)=>{await ut(e,a),await J(e),b=await De(e),m.loadGraph(b),k.setLearningGraphData(b),z.setData(b),await f(e)},onBranchCreate:async(e,a)=>{await pt(e,a),await J(e)},onBranchDelete:async(e,a)=>{await mt(e,a),await J(e)}});async function J(e){const a=await rt(e),r=a.find(D=>D.active);r&&ae.setActiveBranch(e,r.name,a)}async function f(e){const a=await ht(e);z.setSnapshots(a)}const h=Ut(o,i),w=jt(o);t.display.edges||m.setEdges(!1),t.display.edgeLabels||m.setEdgeLabels(!1),t.display.typeHulls||m.setTypeHulls(!1),t.display.minimap||m.setMinimap(!1);function g(e,a){const r=[];a!=null&&a.length&&r.push("node="+a.map(encodeURIComponent).join(","));const D=m.getFocusInfo();D&&(r.push("focus="+D.seedNodeIds.map(encodeURIComponent).join(",")),r.push("hops="+D.hops));const T="#"+encodeURIComponent(e)+(r.length?"?"+r.join("&"):"");history.replaceState(null,"",T)}function L(){const e=window.location.hash.slice(1);if(!e)return{graph:null,nodes:[],focus:[],hops:1};const[a,r]=e.split("?"),D=a?decodeURIComponent(a):null;let T=[],V=[],ee=1;if(r){const he=new URLSearchParams(r),ve=he.get("node");ve&&(T=ve.split(",").map(decodeURIComponent));const oe=he.get("focus");oe&&(V=oe.split(",").map(decodeURIComponent));const we=he.get("hops");we&&(ee=Math.max(0,parseInt(we,10)||1))}return{graph:D,nodes:T,focus:V,hops:ee}}async function F(e,a,r,D){ie=e,ae.setActive(e),M.hide(),P(),k.clear(),S.clear(),b=await De(e);const T=Et(b.nodes.length);if(Ae({spacing:Math.max(t.layout.spacing,T.spacing),clusterStrength:Math.max(t.layout.clustering,T.clusterStrength)}),m.loadGraph(b),k.setLearningGraphData(b),z.setData(b),w.hide(),g(e),await J(e),await f(e),r!=null&&r.length&&b){const V=r.filter(ee=>b.nodes.some(he=>he.id===ee));if(V.length){setTimeout(()=>{m.enterFocus(V,D??1)},500);return}}if(a!=null&&a.length&&b){const V=a.filter(ee=>b.nodes.some(he=>he.id===ee));V.length&&setTimeout(()=>{m.panToNodes(V),b&&M.show(V,b),g(e,V)},500)}}const B=await Be();ae.setSummaries(B);const C=L(),N=C.graph&&B.some(e=>e.name===C.graph)?C.graph:B.length>0?B[0].name:null;N?await F(N,C.nodes.length?C.nodes:void 0,C.focus.length?C.focus:void 0,C.hops):w.show();const u={search(){k.focus()},searchAlt(){k.focus()},undo(){if(b){const e=S.undo(b);e&&v(e)}},redo(){if(b){const e=S.redo(b);e&&v(e)}},focus(){m.isFocused()?z.clearFocusSet():$.length>0&&z.addToFocusSet($)},hopsDecrease(){const e=m.getFocusInfo();e&&e.hops>0&&m.enterFocus(e.seedNodeIds,e.hops-1)},hopsIncrease(){const e=m.getFocusInfo();e&&m.enterFocus(e.seedNodeIds,e.hops+1)},nextNode(){const e=m.getNodeIds();e.length>0&&(_=(_+1)%e.length,m.panToNode(e[_]),b&&M.show([e[_]],b))},prevNode(){const e=m.getNodeIds();e.length>0&&(_=_<=0?e.length-1:_-1,m.panToNode(e[_]),b&&M.show([e[_]],b))},nextConnection(){const e=M.cycleConnection(1);e&&m.panToNode(e)},prevConnection(){const e=M.cycleConnection(-1);e&&m.panToNode(e)},historyBack(){M.goBack()},historyForward(){M.goForward()},center(){m.centerView()},toggleEdges(){j=!j,m.setEdges(j)},panLeft(){m.panBy(-H,0)},panDown(){m.panBy(0,H)},panUp(){m.panBy(0,-H)},panRight(){m.panBy(H,0)},panFastLeft(){m.panBy(-H*t.navigation.panFastMultiplier,0)},zoomOut(){m.zoomBy(1/t.navigation.zoomFactor)},zoomIn(){m.zoomBy(t.navigation.zoomFactor)},panFastRight(){m.panBy(H*t.navigation.panFastMultiplier,0)},spacingDecrease(){const e=Pe();Ae({spacing:Math.max(.5,e.spacing-.5)}),m.reheat()},spacingIncrease(){const e=Pe();Ae({spacing:Math.min(20,e.spacing+.5)}),m.reheat()},clusteringDecrease(){const e=Pe();Ae({clusterStrength:Math.max(0,e.clusterStrength-.03)}),m.reheat()},clusteringIncrease(){const e=Pe();Ae({clusterStrength:Math.min(1,e.clusterStrength+.03)}),m.reheat()},help(){h.toggle()},toggleSidebar(){ae.toggle()},escape(){m.isFocused()?z.clearFocusSet():h.hide()}};document.addEventListener("keydown",e=>{var a;if(!(e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement)){for(const[r,D]of Object.entries(i))if(Pt(e,D)){(r==="search"||r==="searchAlt"||r==="undo"||r==="redo"||r==="toggleSidebar")&&e.preventDefault(),(a=u[r])==null||a.call(u);return}}}),window.addEventListener("hashchange",()=>{const e=L();if(e.graph&&e.graph!==ie)F(e.graph,e.nodes.length?e.nodes:void 0,e.focus.length?e.focus:void 0,e.hops);else if(e.graph&&e.focus.length&&b)m.enterFocus(e.focus,e.hops);else if(e.graph&&e.nodes.length&&b){m.isFocused()&&m.exitFocus();const a=e.nodes.filter(r=>b.nodes.some(D=>D.id===r));a.length&&(m.panToNodes(a),M.show(a,b))}})}Zt();