backpack-viewer 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -1
- package/bin/serve.js +133 -3
- package/dist/app/assets/index-B3z5bBGl.css +1 -0
- package/dist/app/assets/index-CKYlU1zT.js +35 -0
- package/dist/app/index.html +2 -2
- package/dist/config.js +1 -0
- package/dist/default-config.json +4 -0
- package/dist/dialog.d.ts +13 -0
- package/dist/dialog.js +116 -0
- package/dist/main.js +13 -2
- package/dist/sidebar.d.ts +3 -2
- package/dist/sidebar.js +28 -8
- package/dist/style.css +98 -2
- package/package.json +2 -2
- package/dist/app/assets/index-DBZCyAjY.js +0 -34
- package/dist/app/assets/index-DlVz8Lz7.css +0 -1
package/README.md
CHANGED
|
@@ -128,11 +128,31 @@ Bindings are strings with optional modifier prefixes separated by `+`:
|
|
|
128
128
|
- With modifiers: `"ctrl+z"`, `"ctrl+shift+z"`, `"alt+s"`
|
|
129
129
|
- `ctrl` and `cmd`/`meta` are treated as equivalent (works on both Mac and Linux)
|
|
130
130
|
|
|
131
|
+
## Network binding
|
|
132
|
+
|
|
133
|
+
The viewer binds to **loopback only (`127.0.0.1`) by default** — its API exposes read/write access to your learning graphs, so it must never be reachable from other machines on your network without explicit opt-in. To change the bind host or port, edit `~/.config/backpack/viewer.json`:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"server": {
|
|
138
|
+
"host": "127.0.0.1",
|
|
139
|
+
"port": 5173
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Setting `host` to anything other than `127.0.0.1`, `localhost`, or `::1` prints a startup warning because the API becomes reachable from other machines on the network. Only do this for devcontainer / VM scenarios where you understand the trust boundary, and consider putting a reverse proxy with auth in front of it.
|
|
145
|
+
|
|
146
|
+
Environment variables override the config file:
|
|
147
|
+
- `BACKPACK_VIEWER_HOST` — bind host (use `0.0.0.0` for all interfaces)
|
|
148
|
+
- `PORT` — bind port
|
|
149
|
+
|
|
131
150
|
## Reference
|
|
132
151
|
|
|
133
152
|
| Variable | Effect |
|
|
134
153
|
|---|---|
|
|
135
|
-
| `PORT` | Override the default port (default: `5173`) |
|
|
154
|
+
| `PORT` | Override the default port (default: `5173` or `server.port` in config) |
|
|
155
|
+
| `BACKPACK_VIEWER_HOST` | Override the bind host (default: `127.0.0.1` or `server.host` in config) |
|
|
136
156
|
| `XDG_CONFIG_HOME` | Override config location (default: `~/.config`) |
|
|
137
157
|
| `XDG_DATA_HOME` | Override data location (default: `~/.local/share`) |
|
|
138
158
|
| `BACKPACK_DIR` | Override both config and data directories |
|
package/bin/serve.js
CHANGED
|
@@ -4,14 +4,83 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import http from "node:http";
|
|
7
|
+
import https from "node:https";
|
|
7
8
|
|
|
8
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const root = path.resolve(__dirname, "..");
|
|
10
11
|
const distDir = path.resolve(root, "dist/app");
|
|
11
|
-
const port = parseInt(process.env.PORT || "5173", 10);
|
|
12
12
|
|
|
13
13
|
const hasDistBuild = fs.existsSync(path.join(distDir, "index.html"));
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Fetch the `latest` tag for a package from the npm registry. Returns
|
|
17
|
+
* the version string on success, null on any failure (offline,
|
|
18
|
+
* timeout, parse error, etc). Never throws — caller uses a falsy
|
|
19
|
+
* check to skip the stale warning if the registry is unreachable.
|
|
20
|
+
*/
|
|
21
|
+
function fetchLatestVersion(pkgName) {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
const req = https.get(
|
|
24
|
+
`https://registry.npmjs.org/${pkgName}/latest`,
|
|
25
|
+
{ timeout: 5000, headers: { Accept: "application/json" } },
|
|
26
|
+
(res) => {
|
|
27
|
+
if (res.statusCode !== 200) {
|
|
28
|
+
res.resume();
|
|
29
|
+
resolve(null);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let data = "";
|
|
33
|
+
res.on("data", (chunk) => (data += chunk));
|
|
34
|
+
res.on("end", () => {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(data);
|
|
37
|
+
resolve(typeof parsed.version === "string" ? parsed.version : null);
|
|
38
|
+
} catch {
|
|
39
|
+
resolve(null);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
req.on("error", () => resolve(null));
|
|
45
|
+
req.on("timeout", () => {
|
|
46
|
+
req.destroy();
|
|
47
|
+
resolve(null);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// --- Version check cache (1 hour TTL) ---
|
|
53
|
+
// Used by the /api/version-check endpoint so the sidebar can render
|
|
54
|
+
// a stale-version banner without hammering the npm registry on every
|
|
55
|
+
// request.
|
|
56
|
+
const versionCheckCache = { ts: 0, latest: null, current: null };
|
|
57
|
+
const VERSION_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
58
|
+
|
|
59
|
+
async function getCachedVersionCheck(currentVersion) {
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
if (
|
|
62
|
+
versionCheckCache.latest !== null &&
|
|
63
|
+
now - versionCheckCache.ts < VERSION_CACHE_TTL_MS
|
|
64
|
+
) {
|
|
65
|
+
return {
|
|
66
|
+
current: versionCheckCache.current,
|
|
67
|
+
latest: versionCheckCache.latest,
|
|
68
|
+
stale: versionCheckCache.latest !== versionCheckCache.current,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const latest = await fetchLatestVersion("backpack-viewer");
|
|
72
|
+
if (latest) {
|
|
73
|
+
versionCheckCache.ts = now;
|
|
74
|
+
versionCheckCache.latest = latest;
|
|
75
|
+
versionCheckCache.current = currentVersion;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
current: currentVersion,
|
|
79
|
+
latest,
|
|
80
|
+
stale: latest !== null && latest !== currentVersion,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
15
84
|
if (hasDistBuild) {
|
|
16
85
|
// --- Production: static file server + API (zero native deps) ---
|
|
17
86
|
const {
|
|
@@ -26,6 +95,22 @@ if (hasDistBuild) {
|
|
|
26
95
|
} = await import("backpack-ontology");
|
|
27
96
|
const { loadViewerConfig } = await import("../dist/config.js");
|
|
28
97
|
|
|
98
|
+
// Load our own version from package.json for the stale-version check
|
|
99
|
+
const pkgJson = JSON.parse(
|
|
100
|
+
fs.readFileSync(path.join(root, "package.json"), "utf8"),
|
|
101
|
+
);
|
|
102
|
+
const currentVersion = pkgJson.version;
|
|
103
|
+
|
|
104
|
+
// Resolve host + port from the viewer config, with env vars taking
|
|
105
|
+
// precedence for quick overrides. Default is 127.0.0.1 loopback —
|
|
106
|
+
// the viewer must never bind to all interfaces by default because
|
|
107
|
+
// its API exposes read/write access to the user's learning graphs.
|
|
108
|
+
const viewerConfigForServer = loadViewerConfig();
|
|
109
|
+
const configuredHost = viewerConfigForServer?.server?.host ?? "127.0.0.1";
|
|
110
|
+
const configuredPort = viewerConfigForServer?.server?.port ?? 5173;
|
|
111
|
+
const bindHost = process.env.BACKPACK_VIEWER_HOST ?? configuredHost;
|
|
112
|
+
const port = parseInt(process.env.PORT || String(configuredPort), 10);
|
|
113
|
+
|
|
29
114
|
// Storage points at the active backpack. Wrapped in a mutable
|
|
30
115
|
// holder so a `/api/backpacks/switch` POST can swap it out in place
|
|
31
116
|
// without restarting the whole server.
|
|
@@ -80,6 +165,20 @@ if (hasDistBuild) {
|
|
|
80
165
|
return;
|
|
81
166
|
}
|
|
82
167
|
|
|
168
|
+
if (url === "/api/version-check" && req.method === "GET") {
|
|
169
|
+
try {
|
|
170
|
+
const result = await getCachedVersionCheck(currentVersion);
|
|
171
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
172
|
+
res.end(JSON.stringify(result));
|
|
173
|
+
} catch {
|
|
174
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
175
|
+
res.end(
|
|
176
|
+
JSON.stringify({ current: currentVersion, latest: null, stale: false }),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
83
182
|
// --- Remote graph routes (read-only) ---
|
|
84
183
|
if (url === "/api/remotes" && req.method === "GET") {
|
|
85
184
|
try {
|
|
@@ -529,8 +628,39 @@ if (hasDistBuild) {
|
|
|
529
628
|
}
|
|
530
629
|
});
|
|
531
630
|
|
|
532
|
-
|
|
533
|
-
|
|
631
|
+
// Bind to whatever the config + env var resolved to. The default is
|
|
632
|
+
// 127.0.0.1 because the viewer API exposes read/write access to the
|
|
633
|
+
// user's learning graphs and must never be reachable from other
|
|
634
|
+
// machines on the same network by default. Users who really need to
|
|
635
|
+
// bind to another interface (e.g. for a devcontainer scenario) can
|
|
636
|
+
// set server.host in ~/.config/backpack/viewer.json or the
|
|
637
|
+
// BACKPACK_VIEWER_HOST env var, and will see a loud warning.
|
|
638
|
+
server.listen(port, bindHost, () => {
|
|
639
|
+
const displayHost = bindHost === "0.0.0.0" || bindHost === "::" ? "localhost" : bindHost;
|
|
640
|
+
console.log(` Backpack Viewer v${currentVersion} running at http://${displayHost}:${port}/`);
|
|
641
|
+
const isLoopback =
|
|
642
|
+
bindHost === "127.0.0.1" ||
|
|
643
|
+
bindHost === "localhost" ||
|
|
644
|
+
bindHost === "::1";
|
|
645
|
+
if (!isLoopback) {
|
|
646
|
+
console.warn(
|
|
647
|
+
` WARNING: viewer is bound to ${bindHost}, not loopback. The API exposes read/write access to your learning graphs — anyone on your network can reach it. Set server.host to "127.0.0.1" in ~/.config/backpack/viewer.json to restrict to localhost.`,
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Fire-and-forget stale-version check. If the registry responds
|
|
652
|
+
// and our version is older than `latest`, print a loud warning
|
|
653
|
+
// telling the user exactly how to unblock themselves.
|
|
654
|
+
fetchLatestVersion("backpack-viewer").then((latest) => {
|
|
655
|
+
if (latest && latest !== currentVersion) {
|
|
656
|
+
console.warn("");
|
|
657
|
+
console.warn(` ⚠ Backpack Viewer ${currentVersion} is out of date — latest is ${latest}`);
|
|
658
|
+
console.warn(` To update:`);
|
|
659
|
+
console.warn(` npm cache clean --force`);
|
|
660
|
+
console.warn(` npx backpack-viewer@latest`);
|
|
661
|
+
console.warn("");
|
|
662
|
+
}
|
|
663
|
+
});
|
|
534
664
|
});
|
|
535
665
|
} else {
|
|
536
666
|
// --- Development: use Vite for HMR + TypeScript compilation ---
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*{margin:0;padding:0;box-sizing:border-box}:root{--bg: #141414;--bg-surface: #1a1a1a;--bg-hover: #222222;--bg-active: #2a2a2a;--bg-elevated: #1e1e1e;--bg-inset: #111111;--border: #2a2a2a;--text: #d4d4d4;--text-strong: #e5e5e5;--text-muted: #737373;--text-dim: #525252;--accent: #d4a27f;--accent-hover: #e8b898;--badge-text: #141414;--glass-bg: rgba(20, 20, 20, .85);--glass-border: rgba(255, 255, 255, .08);--chip-bg: rgba(42, 42, 42, .7);--chip-bg-active: rgba(42, 42, 42, .9);--chip-bg-hover: rgba(50, 50, 50, .9);--chip-border-active: rgba(255, 255, 255, .06);--shadow: rgba(0, 0, 0, .6);--shadow-strong: rgba(0, 0, 0, .5);--canvas-edge: rgba(255, 255, 255, .08);--canvas-edge-highlight: rgba(212, 162, 127, .5);--canvas-edge-dim: rgba(255, 255, 255, .03);--canvas-edge-label: rgba(255, 255, 255, .2);--canvas-edge-label-highlight: rgba(212, 162, 127, .7);--canvas-edge-label-dim: rgba(255, 255, 255, .05);--canvas-arrow: rgba(255, 255, 255, .12);--canvas-arrow-highlight: rgba(212, 162, 127, .5);--canvas-node-label: #a3a3a3;--canvas-node-label-dim: rgba(212, 212, 212, .2);--canvas-type-badge: rgba(115, 115, 115, .5);--canvas-type-badge-dim: rgba(115, 115, 115, .15);--canvas-selection-border: #d4d4d4;--canvas-node-border: rgba(255, 255, 255, .15);--canvas-walk-edge: #e8d5c4}[data-theme=light]{--bg: #f5f5f4;--bg-surface: #fafaf9;--bg-hover: #f0efee;--bg-active: #e7e5e4;--bg-elevated: #f0efee;--bg-inset: #e7e5e4;--border: #d6d3d1;--text: #292524;--text-strong: #1c1917;--text-muted: #78716c;--text-dim: #a8a29e;--accent: #c17856;--accent-hover: #b07a5e;--badge-text: #fafaf9;--glass-bg: rgba(250, 250, 249, .85);--glass-border: rgba(0, 0, 0, .08);--chip-bg: rgba(214, 211, 209, .5);--chip-bg-active: rgba(214, 211, 209, .8);--chip-bg-hover: rgba(200, 197, 195, .8);--chip-border-active: rgba(0, 0, 0, .08);--shadow: rgba(0, 0, 0, .1);--shadow-strong: rgba(0, 0, 0, .15);--canvas-edge: rgba(0, 0, 0, .1);--canvas-edge-highlight: rgba(193, 120, 86, .6);--canvas-edge-dim: rgba(0, 0, 0, .03);--canvas-edge-label: rgba(0, 0, 0, .25);--canvas-edge-label-highlight: rgba(193, 120, 86, .8);--canvas-edge-label-dim: rgba(0, 0, 0, .06);--canvas-arrow: rgba(0, 0, 0, .15);--canvas-arrow-highlight: rgba(193, 120, 86, .6);--canvas-node-label: #57534e;--canvas-node-label-dim: rgba(87, 83, 78, .2);--canvas-type-badge: rgba(87, 83, 78, .5);--canvas-type-badge-dim: rgba(87, 83, 78, .15);--canvas-selection-border: #292524;--canvas-node-border: rgba(0, 0, 0, .1);--canvas-walk-edge: #1a1a1a}body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);overflow:hidden}#app{display:flex;height:100vh;width:100vw}#sidebar{width:280px;min-width:280px;background:var(--bg-surface);border-right:1px solid var(--border);border-left:3px solid var(--backpack-color, transparent);display:flex;flex-direction:column;padding:16px;overflow-y:auto}#sidebar.sidebar-collapsed{width:0;min-width:0;padding:0;overflow:hidden;border-right:none}.sidebar-heading-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.sidebar-heading-row h2{margin-bottom:0}.sidebar-collapse-btn{background:none;border:1px solid var(--border);border-radius:6px;color:var(--text-dim);cursor:pointer;padding:4px 6px;line-height:1;transition:color .15s,border-color .15s}.sidebar-collapse-btn:hover{color:var(--text);border-color:var(--text-muted)}#sidebar h2{font-size:13px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:14px}#sidebar input{width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text);font-size:13px;outline:none;margin-bottom:12px}#sidebar input:focus{border-color:var(--accent)}#sidebar input::placeholder{color:var(--text-dim)}#ontology-list{list-style:none;display:flex;flex-direction:column;gap:2px}.ontology-item{padding:10px 12px;border-radius:6px;cursor:pointer;transition:background .15s}.ontology-item:hover{background:var(--bg-hover)}.ontology-item.active{background:var(--bg-active)}.ontology-item .name{display:block;font-size:13px;font-weight:500;color:var(--text)}.ontology-item .stats{display:block;font-size:11px;color:var(--text-dim);margin-top:2px}.sidebar-edit-btn{position:absolute;right:8px;top:10px;background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;opacity:0;transition:opacity .1s}.ontology-item{position:relative}.ontology-item:hover .sidebar-edit-btn{opacity:.7}.sidebar-section-heading{font-size:10px;font-weight:600;color:var(--text-dim);letter-spacing:.08em;margin:16px 12px 6px}.remote-list{list-style:none;padding:0;margin:0}.ontology-item-remote .name{display:inline}.remote-name-row{display:flex;align-items:center;gap:8px}.remote-badge{font-size:9px;font-weight:600;color:var(--text-dim);background:var(--bg-hover);padding:1px 6px;border-radius:4px;text-transform:uppercase;letter-spacing:.04em;border:1px solid var(--border)}.remote-source{display:block;font-size:10px;color:var(--text-dim);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-edit-btn:hover{opacity:1!important;color:var(--text)}.sidebar-rename-input{background:transparent;border:none;border-bottom:1px solid var(--accent);color:var(--text);font:inherit;font-size:13px;font-weight:500;outline:none;width:100%;padding:0}.sidebar-branch{font-size:10px;color:var(--accent);opacity:.7;display:block;margin-top:2px}.sidebar-branch:hover{opacity:1}.sidebar-stale-banner{background:#fff3cd;color:#5c3a00;border:1px solid #e6c263;border-radius:6px;padding:10px 12px;margin-bottom:10px;font-size:11px}.sidebar-stale-banner-title{font-weight:600;margin-bottom:2px}.sidebar-stale-banner-subtitle{opacity:.85;margin-bottom:6px}.sidebar-stale-banner-hint{background:#00000014;border-radius:4px;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10px;line-height:1.4;margin:0;white-space:pre-wrap;word-break:break-all}.backpack-picker-container{position:relative;margin-bottom:10px}.backpack-picker-pill{display:flex;align-items:center;gap:6px;width:100%;padding:6px 10px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;color:var(--fg);font-size:11px;font-family:inherit;cursor:pointer;text-align:left}.backpack-picker-pill:hover{border-color:var(--backpack-color, var(--accent))}.backpack-picker-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--backpack-color, var(--accent));flex-shrink:0}.backpack-picker-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.backpack-picker-caret{opacity:.6;font-size:10px}.backpack-picker-dropdown{position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;box-shadow:0 4px 16px var(--shadow);z-index:50;max-height:300px;overflow-y:auto;padding:4px}.backpack-picker-item{display:flex;align-items:center;gap:6px;width:100%;padding:6px 8px;background:transparent;border:none;border-radius:4px;color:var(--fg);font-family:inherit;font-size:11px;cursor:pointer;text-align:left}.backpack-picker-item:hover{background:var(--bg-hover)}.backpack-picker-item.active{background:var(--bg-hover);font-weight:600}.backpack-picker-item-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--backpack-color, var(--accent));flex-shrink:0}.backpack-picker-item-name{flex-shrink:0}.backpack-picker-item-path{flex:1;opacity:.55;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:9px;text-align:right}.backpack-picker-divider{height:1px;background:var(--border);margin:4px 0}.backpack-picker-add{opacity:.75;font-style:italic}.sidebar-lock-badge{font-size:10px;color:#c08c00;display:none;margin-top:2px}.sidebar-lock-badge.active{display:block}.sidebar-lock-badge.active:before{content:"● ";color:#c08c00}.branch-picker{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:4px;margin-top:4px;box-shadow:0 4px 16px var(--shadow);z-index:50}.branch-picker-item{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;font-size:12px;color:var(--text);border-radius:4px;cursor:pointer}.branch-picker-item:hover{background:var(--bg-hover)}.branch-picker-active{color:var(--accent);font-weight:600;cursor:default}.branch-picker-active:hover{background:none}.branch-picker-delete{background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:14px;padding:0 4px}.sidebar-snippets{margin-top:4px;padding-left:8px}.sidebar-snippet{display:flex;align-items:center;justify-content:space-between;padding:2px 4px;border-radius:4px;cursor:pointer}.sidebar-snippet:hover{background:var(--bg-hover)}.sidebar-snippet-label{font-size:10px;color:var(--text-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-snippet-delete{background:none;border:none;color:var(--text-dim);font-size:12px;cursor:pointer;padding:0 2px;opacity:0}.sidebar-snippet:hover .sidebar-snippet-delete{opacity:1}.branch-picker-delete:hover{color:var(--danger, #e55)}.branch-picker-create{color:var(--accent);font-size:11px;border-top:1px solid var(--border);margin-top:4px;padding-top:8px}.sidebar-footer{margin-top:auto;padding-top:16px;border-top:1px solid var(--border);text-align:center}.sidebar-footer a{display:block;font-size:12px;font-weight:500;color:var(--accent);text-decoration:none;margin-bottom:4px}.sidebar-footer a:hover{color:var(--accent-hover)}.sidebar-footer span{display:block;font-size:10px;color:var(--text-dim)}.sidebar-version{font-size:9px;color:var(--text-dim);opacity:.5;margin-top:4px}.canvas-top-bar{position:absolute;top:16px;left:16px;right:16px;z-index:30;display:flex;justify-content:space-between;align-items:flex-start;pointer-events:none}.canvas-top-left,.canvas-top-center,.canvas-top-right{pointer-events:auto;display:flex;align-items:center;gap:4px}.canvas-top-left{flex-shrink:0;min-width:var(--tools-width, 264px)}.canvas-top-center{flex:1;justify-content:center}.focus-indicator{display:flex;align-items:center;gap:2px;background:var(--bg-surface);border:1px solid rgba(212,162,127,.4);border-radius:8px;padding:4px 6px 4px 10px;box-shadow:0 2px 8px var(--shadow)}.focus-indicator-label{font-size:11px;color:var(--accent);font-weight:500;white-space:nowrap;margin-right:4px}.focus-indicator-hops{font-size:11px;color:var(--text-muted);font-family:monospace;min-width:12px;text-align:center}.focus-indicator-btn{background:none;border:none;color:var(--text-muted);font-size:14px;cursor:pointer;padding:2px 4px;line-height:1;border-radius:4px;transition:color .15s,background .15s}.focus-indicator-btn:hover:not(:disabled){color:var(--text);background:var(--bg-hover)}.focus-indicator-btn:disabled{color:var(--text-dim);cursor:default;opacity:.3}.focus-indicator-exit{font-size:16px;margin-left:2px}.focus-indicator-exit:hover{color:#ef4444!important}.info-focus-btn{font-size:14px}.theme-toggle{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;color:var(--text-muted);font-size:18px;cursor:pointer;padding:6px 10px;line-height:1;transition:color .15s,border-color .15s,background .15s;box-shadow:0 2px 8px var(--shadow)}.theme-toggle:hover{color:var(--text);border-color:var(--text-muted);background:var(--bg-hover)}.zoom-controls{display:flex;gap:4px}.zoom-btn{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;color:var(--text-muted);font-size:18px;cursor:pointer;padding:6px 10px;line-height:1;transition:color .15s,border-color .15s,background .15s;box-shadow:0 2px 8px var(--shadow)}.zoom-btn:hover{color:var(--text);border-color:var(--text-muted);background:var(--bg-hover)}.node-tooltip{position:absolute;pointer-events:none;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:12px;white-space:nowrap;z-index:20;box-shadow:0 2px 8px #00000026;opacity:.95}#canvas-container{flex:1;position:relative;overflow:hidden;touch-action:none}#graph-canvas{position:absolute;top:0;left:0;touch-action:none;width:100%;height:100%;cursor:grab}#graph-canvas:active{cursor:grabbing}.search-overlay{position:relative;display:flex;flex-direction:column;align-items:center;gap:8px;max-height:calc(100vh - 48px);pointer-events:none}.search-overlay>*{pointer-events:auto}.search-overlay.hidden{display:none}.search-input-wrap{position:relative;display:flex;align-items:center;gap:6px;width:380px;max-width:calc(100vw - 340px)}.search-input{flex:1;min-width:0;padding:10px 36px 10px 16px;border:1px solid var(--glass-border);border-radius:10px;background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);color:var(--text);font-size:14px;outline:none;transition:border-color .15s,box-shadow .15s}.search-input:focus{border-color:#d4a27f66;box-shadow:0 0 0 3px #d4a27f1a}.search-input::placeholder{color:var(--text-dim)}.search-kbd{position:absolute;right:10px;top:50%;transform:translateY(-50%);padding:2px 7px;border:1px solid var(--border);border-radius:4px;background:var(--bg-surface);color:var(--text-dim);font-size:11px;font-family:monospace;pointer-events:none}.search-kbd.hidden{display:none}.search-results{list-style:none;width:380px;max-width:calc(100vw - 340px);background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--border);border-radius:10px;overflow:hidden;box-shadow:0 8px 32px var(--shadow-strong)}.search-results.hidden{display:none}.search-result-item{display:flex;align-items:center;gap:8px;padding:8px 14px;cursor:pointer;transition:background .1s}.search-result-item:hover,.search-result-active{background:var(--bg-hover)}.search-result-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.search-result-label{font-size:13px;color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.search-result-type{font-size:11px;color:var(--text-dim);flex-shrink:0}.chip-toggle{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:1px solid var(--glass-border);border-radius:10px;background:var(--glass-bg);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);color:var(--text-dim);cursor:pointer;transition:border-color .15s,color .15s;pointer-events:auto}.chip-toggle:hover{border-color:#d4a27f4d;color:var(--text)}.chip-toggle.active{border-color:#d4a27f66;color:var(--accent)}.type-chips{display:flex;flex-wrap:wrap;gap:4px;justify-content:center;max-width:500px;max-height:200px;overflow-y:auto;padding:4px;border-radius:10px}.type-chips.hidden{display:none}.type-chip{display:flex;align-items:center;gap:4px;padding:3px 10px;border:1px solid transparent;border-radius:12px;background:var(--chip-bg);color:var(--text-dim);font-size:11px;cursor:pointer;transition:all .15s;white-space:nowrap}.type-chip.active{background:var(--chip-bg-active);color:var(--text-muted);border-color:var(--chip-border-active)}.type-chip:hover{background:var(--chip-bg-hover)}.type-chip-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.type-chip:not(.active) .type-chip-dot{opacity:.3}.info-panel{position:absolute;top:56px;right:16px;bottom:16px;width:360px;background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;overflow:hidden;display:flex;flex-direction:column;padding:0;z-index:10;box-shadow:0 8px 32px var(--shadow);transition:top .25s ease,right .25s ease,bottom .25s ease,left .25s ease,width .25s ease,max-height .25s ease,border-radius .25s ease}.info-panel-header{flex-shrink:0;padding:20px 20px 12px;position:relative}.info-panel-body{flex:1;overflow-y:auto;min-height:0;padding:0 20px 20px}.info-panel.hidden{display:none}.info-panel.info-panel-maximized{top:0;right:0;bottom:0;left:0;width:auto;max-height:none;border-radius:0;z-index:40}.info-panel-toolbar{position:absolute;top:12px;right:14px;display:flex;align-items:center;gap:2px;z-index:1}.info-toolbar-btn{background:none;border:none;color:var(--text-muted);font-size:16px;cursor:pointer;padding:4px 6px;line-height:1;border-radius:4px;transition:color .15s,background .15s}.info-toolbar-btn:hover:not(:disabled){color:var(--text);background:var(--bg-hover)}.info-toolbar-btn:disabled{color:var(--text-dim);cursor:default;opacity:.3}.info-close-btn{font-size:20px}.info-connection-link{cursor:pointer;transition:background .15s}.info-connection-link:hover{background:var(--bg-active)}.info-connection-link .info-target{color:var(--accent);text-decoration:underline;text-decoration-color:transparent;transition:text-decoration-color .15s}.info-connection-link:hover .info-target{text-decoration-color:var(--accent)}.info-header{margin-bottom:16px}.info-type-badge{display:inline-block;padding:3px 10px;border-radius:12px;font-size:11px;font-weight:600;color:var(--badge-text);margin-bottom:8px}.info-badge-row{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}.info-empty-message{font-size:12px;color:var(--text-dim)}.share-list-message{font-size:13px;color:var(--text-dim);text-align:center;padding:12px}.info-label{font-size:18px;font-weight:600;color:var(--text-strong);margin-bottom:4px;word-break:break-word}.info-id{display:block;font-size:11px;color:var(--text-dim);font-family:monospace}.info-section{margin-bottom:16px}.info-section-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border)}.info-props{display:grid;grid-template-columns:auto 1fr;gap:4px 12px}.info-props dt{font-size:12px;color:var(--text-muted);padding-top:2px}.info-props dd{font-size:12px;color:var(--text);word-break:break-word;display:flex;align-items:center;gap:4px}.info-value{white-space:pre-wrap}.info-array{display:flex;flex-wrap:wrap;gap:4px}.info-tag{display:inline-block;padding:2px 8px;background:var(--bg-hover);border-radius:4px;font-size:11px;color:var(--text-muted)}.info-json{font-size:11px;font-family:monospace;color:var(--text-muted);background:var(--bg-inset);padding:6px 8px;border-radius:4px;overflow-x:auto;white-space:pre}.info-connections{list-style:none;display:flex;flex-direction:column;gap:6px}.info-connection{display:flex;align-items:center;gap:6px;padding:6px 8px;background:var(--bg-elevated);border-radius:6px;font-size:12px;flex-wrap:wrap}.info-connection-active{outline:1.5px solid var(--accent);background:var(--bg-hover)}.info-target-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.info-arrow{color:var(--text-dim);font-size:14px;flex-shrink:0}.info-edge-type{color:var(--text-muted);font-size:11px;font-weight:500}.info-target{color:var(--text);font-weight:500}.info-edge-props{width:100%;padding-top:4px;padding-left:20px}.info-edge-prop{display:block;font-size:11px;color:var(--text-dim)}.info-editable{cursor:default;position:relative}.info-inline-edit{background:none;border:none;color:var(--badge-text);opacity:0;font-size:10px;cursor:pointer;margin-left:4px;transition:opacity .15s}.info-editable:hover .info-inline-edit{opacity:.8}.info-inline-edit:hover{opacity:1!important}.info-edit-inline-input{background:transparent;border:none;border-bottom:1px solid var(--accent);color:var(--badge-text);font:inherit;font-size:inherit;outline:none;width:100%;padding:0}.info-edit-input{background:var(--bg-inset);border:1px solid var(--border);border-radius:4px;padding:3px 6px;font-size:12px;font-family:inherit;color:var(--text);flex:1;min-width:0;resize:vertical;overflow:hidden;line-height:1.4;max-height:300px}.info-edit-input:focus{outline:none;border-color:var(--accent)}.info-delete-prop{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;padding:0 2px;flex-shrink:0;opacity:0;transition:opacity .1s,color .1s}.info-props dd:hover .info-delete-prop{opacity:1}.info-delete-prop:hover{color:#ef4444}.info-add-btn{background:none;border:1px dashed var(--border);border-radius:4px;padding:6px 10px;font-size:12px;color:var(--text-dim);cursor:pointer;width:100%;margin-top:8px;transition:border-color .15s,color .15s}.info-add-btn:hover{border-color:var(--accent);color:var(--text)}.info-add-row{display:flex;gap:4px;margin-top:6px}.info-add-save{background:var(--accent);border:none;border-radius:4px;padding:3px 10px;font-size:12px;color:var(--badge-text);cursor:pointer;flex-shrink:0}.info-add-save:hover{background:var(--accent-hover)}.info-delete-edge{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;margin-left:auto;padding:0 2px;opacity:0;transition:opacity .1s,color .1s}.info-connection:hover .info-delete-edge{opacity:1}.info-delete-edge:hover{color:#ef4444}.info-danger{margin-top:8px;padding-top:12px;border-top:1px solid var(--border)}.info-delete-node{background:none;border:1px solid rgba(239,68,68,.3);border-radius:6px;padding:6px 12px;font-size:12px;color:#ef4444;cursor:pointer;width:100%;transition:background .15s}.info-delete-node:hover{background:#ef44441a}.tools-pane-toggle{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;color:var(--text-muted);font-size:18px;cursor:pointer;padding:6px 10px;line-height:1;transition:color .15s,border-color .15s,background .15s;box-shadow:0 2px 8px var(--shadow)}.tools-pane-toggle.hidden{display:none}.tools-pane-toggle:hover{color:var(--text);border-color:var(--text-muted);background:var(--bg-hover)}.tools-pane-toggle.active{color:var(--accent);border-color:#d4a27f66}.tools-pane-content{position:absolute;top:56px;left:16px;bottom:16px;z-index:20;width:var(--tools-width, 264px);box-sizing:border-box;overflow:hidden;display:flex;flex-direction:column;background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;padding:12px;box-shadow:0 8px 32px var(--shadow)}.tools-pane-content.hidden{display:none}.tools-pane-section{margin-bottom:12px}.tools-pane-section:last-child{margin-bottom:0}.tools-pane-heading{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);margin-bottom:6px}.tools-pane-row{display:flex;align-items:center;gap:6px;padding:3px 0;font-size:12px}.tools-pane-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.tools-pane-name{flex:1;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tools-pane-count{color:var(--text-dim);font-size:11px;flex-shrink:0}.tools-pane-summary{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--text-muted);padding-bottom:10px;margin-bottom:10px;border-bottom:1px solid var(--border)}.tools-pane-sep{color:var(--text-dim)}.tools-pane-token-card{padding:8px 10px;margin-bottom:10px;border:1px solid var(--border);border-radius:6px;background:var(--bg-hover);font-size:11px}.token-card-label{font-weight:600;color:var(--text);margin-bottom:4px}.token-card-stat{color:var(--text-muted);margin-bottom:4px}.token-card-bar{height:4px;background:var(--border);border-radius:2px;margin:6px 0;overflow:hidden}.token-card-bar-fill{height:100%;background:var(--accent);border-radius:2px;transition:width .3s ease}.tools-pane-clickable{cursor:pointer;border-radius:4px;padding:3px 4px;margin:0 -4px;transition:background .1s}.tools-pane-clickable:hover{background:var(--bg-hover)}.tools-pane-clickable.active{background:var(--bg-hover);outline:1px solid var(--border)}.tools-pane-badge{font-size:9px;color:var(--accent);flex-shrink:0;opacity:.8}.tools-pane-issue .tools-pane-name{color:var(--text-muted)}.tools-pane-more{font-size:10px;color:var(--text-dim);padding:4px 0 0}.tools-pane-edit{background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;padding:0 2px;opacity:0;transition:opacity .1s,color .1s;flex-shrink:0}.tools-pane-row:hover .tools-pane-edit{opacity:1}.tools-pane-edit:hover{color:var(--accent)}.tools-pane-actions{display:flex;gap:6px;padding-top:4px}.tools-pane-action-btn{background:none;border:1px solid var(--border);color:var(--text-muted);font-size:10px;padding:2px 8px;border-radius:3px;cursor:pointer;transition:color .1s,border-color .1s}.tools-pane-action-btn:hover{color:var(--accent);border-color:var(--accent)}.tools-pane-focus-toggle{opacity:.4;font-size:11px}.tools-pane-focus-active{opacity:1!important;color:var(--accent)!important}.tools-pane-focus-clear{margin-top:4px;border-top:1px solid var(--border);padding-top:6px}.tools-pane-editing{background:none!important}.tools-pane-inline-input{width:100%;background:var(--bg);border:1px solid var(--accent);border-radius:4px;color:var(--text);font-size:12px;padding:2px 6px;outline:none}.tools-pane-slider-row{display:flex;align-items:center;gap:6px;padding:4px 0}.tools-pane-slider-label{font-size:11px;color:var(--text-muted);white-space:nowrap;min-width:56px}.tools-pane-slider{flex:1;min-width:0;height:4px;accent-color:var(--accent);cursor:pointer}.tools-pane-slider-value{font-size:10px;color:var(--text-dim);min-width:28px;text-align:right;font-family:monospace}.tools-pane-checkbox{width:14px;height:14px;accent-color:var(--accent);cursor:pointer;flex-shrink:0}.tools-pane-export-row{display:flex;gap:4px;margin-top:6px}.tools-pane-export-btn{flex:1;padding:4px 8px;font-size:11px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text-muted);cursor:pointer;transition:color .15s,border-color .15s}.tools-pane-export-btn:hover{color:var(--text);border-color:var(--text-muted)}.tools-pane-empty{font-size:11px;color:var(--text-dim);text-align:center;padding:8px 0}.tools-pane-tabs{display:flex;gap:2px;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--border)}.tools-pane-tab{flex:1;padding:4px 0;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.03em;background:none;border:1px solid transparent;border-radius:5px;color:var(--text-dim);cursor:pointer;transition:color .15s,background .15s,border-color .15s}.tools-pane-tab:hover{color:var(--text-muted);background:var(--bg-hover)}.tools-pane-tab-active{color:var(--text);background:var(--bg-hover);border-color:var(--border)}.tools-pane-tab-content{flex:1;overflow-y:auto;overflow-x:hidden;min-height:0}.tools-pane-search{width:100%;padding:4px 8px;font-size:11px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);outline:none;margin-bottom:8px;box-sizing:border-box}.tools-pane-search:focus{border-color:var(--accent)}.tools-pane-search::placeholder{color:var(--text-dim)}.tools-pane-empty-msg{font-size:11px;color:var(--text-dim);text-align:center;padding:16px 0}.empty-state{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;z-index:5;pointer-events:none;overflow:hidden}.empty-state.hidden{display:none}.empty-state-bg{position:absolute;top:0;right:0;bottom:0;left:0;overflow:hidden}.empty-state-circle{position:absolute;border-radius:50%;background:var(--accent);opacity:.07}.empty-state-circle.c1{width:80px;height:80px;left:20%;top:15%;animation:float-circle 8s ease-in-out infinite}.empty-state-circle.c2{width:50px;height:50px;right:25%;top:25%;animation:float-circle 6s ease-in-out 1s infinite}.empty-state-circle.c3{width:65px;height:65px;left:55%;bottom:20%;animation:float-circle 7s ease-in-out 2s infinite}.empty-state-circle.c4{width:40px;height:40px;left:15%;bottom:30%;animation:float-circle 9s ease-in-out .5s infinite}.empty-state-circle.c5{width:55px;height:55px;right:15%;bottom:35%;animation:float-circle 7.5s ease-in-out 1.5s infinite}.empty-state-lines{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;color:var(--text-dim);animation:float-circle 10s ease-in-out infinite}@keyframes float-circle{0%,to{transform:translateY(0)}50%{transform:translateY(-12px)}}.empty-state-content{text-align:center;max-width:420px;padding:40px 24px;position:relative;z-index:1}.empty-state-icon{color:var(--text-dim);margin-bottom:16px}.empty-state-title{font-size:18px;font-weight:600;color:var(--text);margin-bottom:8px}.empty-state-desc{font-size:13px;color:var(--text-muted);line-height:1.5;margin-bottom:20px}.empty-state-setup{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:12px 16px;margin-bottom:16px}.empty-state-label{font-size:11px;color:var(--text-dim);margin-bottom:8px}.empty-state-code{display:block;font-size:12px;color:var(--accent);font-family:monospace;word-break:break-all}.empty-state-hint{font-size:11px;color:var(--text-dim)}.empty-state-hint kbd{padding:1px 5px;border:1px solid var(--border);border-radius:3px;background:var(--bg-surface);font-family:monospace;font-size:11px}.shortcuts-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:100}.shortcuts-overlay.hidden{display:none}.shortcuts-modal{background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;padding:24px;min-width:300px;max-width:400px;box-shadow:0 16px 48px var(--shadow);position:relative}.shortcuts-close{position:absolute;top:12px;right:14px;background:none;border:none;color:var(--text-dim);font-size:20px;cursor:pointer;padding:0 4px;line-height:1}.shortcuts-close:hover{color:var(--text)}.shortcuts-title{font-size:15px;font-weight:600;color:var(--text);margin-bottom:16px}.shortcuts-list{display:flex;flex-direction:column;gap:8px}.shortcuts-row{display:flex;align-items:center;justify-content:space-between;gap:12px}.shortcuts-keys{display:flex;align-items:center;gap:4px}.shortcuts-keys kbd{padding:2px 7px;border:1px solid var(--border);border-radius:4px;background:var(--bg);color:var(--text);font-size:11px;font-family:monospace}.shortcuts-or{font-size:10px;color:var(--text-dim)}.shortcuts-desc{font-size:12px;color:var(--text-muted)}@media(max-width:768px){#app{flex-direction:column}#sidebar{width:100%;min-width:0;max-height:35vh;border-right:none;border-bottom:1px solid var(--border)}.info-panel{top:auto;bottom:72px;right:8px;left:8px;width:auto;max-height:calc(100% - 200px);overflow-y:auto}.info-panel.info-panel-maximized{bottom:0;left:0;right:0}.canvas-top-bar{top:8px;left:8px;right:8px}.tools-pane-content{top:48px;left:8px;bottom:80px;width:160px;max-width:calc(100vw - 24px)}.tools-pane-edit{opacity:.6}}.bp-dialog-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.bp-dialog{background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;padding:20px;min-width:280px;max-width:400px;box-shadow:0 16px 48px #0000004d}.bp-dialog-title{font-size:14px;font-weight:600;color:var(--text);margin-bottom:12px}.bp-dialog-message{font-size:13px;color:var(--text-muted);margin-bottom:16px;line-height:1.5}.bp-dialog-input{width:100%;padding:8px 12px;font-size:13px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);outline:none;margin-bottom:16px}.bp-dialog-input:focus{border-color:var(--accent)}.bp-dialog-label{display:block;font-size:11px;color:var(--text-muted);margin-bottom:4px;margin-top:8px;text-transform:uppercase;letter-spacing:.04em}.bp-dialog-path-row{display:flex;gap:8px;margin-bottom:4px}.bp-dialog-path-input{flex:1;margin-bottom:0}.bp-dialog-browse-btn{flex-shrink:0;padding:8px 14px;font-size:12px}.bp-dialog-browse-btn:disabled{opacity:.4;cursor:not-allowed}.bp-dialog-path-input.bp-dialog-drag-over{border-color:var(--accent);background:var(--bg-hover)}.bp-dialog-picker-hint{font-size:11px;color:var(--text-muted);min-height:1em;margin-bottom:8px}.bp-dialog-activate-row{display:flex;align-items:center;gap:8px;margin-top:8px;margin-bottom:12px;font-size:12px;color:var(--text-muted)}.bp-dialog-activate-row input[type=checkbox]{margin:0}.bp-dialog-activate-row label{cursor:pointer}.bp-dialog-buttons{display:flex;justify-content:flex-end;gap:8px}.bp-dialog-btn{padding:6px 16px;font-size:12px;border-radius:6px;border:1px solid var(--border);background:var(--bg);color:var(--text-muted);cursor:pointer;transition:all .15s}.bp-dialog-btn:hover{background:var(--bg-hover);color:var(--text)}.bp-dialog-btn-accent{background:var(--accent);color:#fff;border-color:var(--accent)}.bp-dialog-btn-accent:hover{opacity:.9;color:#fff;background:var(--accent)}.bp-dialog-btn-danger{background:#e55;color:#fff;border-color:#e55}.bp-dialog-btn-danger:hover{opacity:.9;color:#fff;background:#e55}.bp-toast{position:fixed;bottom:24px;left:50%;transform:translate(-50%) translateY(20px);background:var(--bg-surface);border:1px solid var(--border);color:var(--text);padding:8px 20px;border-radius:8px;font-size:12px;z-index:1001;opacity:0;transition:opacity .3s,transform .3s;box-shadow:0 4px 16px var(--shadow)}.bp-toast-visible{opacity:1;transform:translate(-50%) translateY(0)}.context-menu{position:absolute;z-index:100;background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:4px;min-width:180px;box-shadow:0 8px 24px var(--shadow)}.context-menu-item{padding:6px 12px;font-size:12px;color:var(--text);border-radius:4px;cursor:pointer;white-space:nowrap}.context-menu-item:hover{background:var(--bg-hover)}.context-menu-separator{height:1px;background:var(--border);margin:4px 8px}.path-bar{position:absolute;bottom:16px;left:50%;transform:translate(-50%);z-index:25;display:flex;align-items:center;gap:4px;background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:6px 12px;box-shadow:0 4px 16px var(--shadow);max-width:80%;overflow-x:auto}.path-bar.hidden{display:none}.path-bar-node{font-size:11px;color:var(--text);padding:2px 8px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0}.path-bar-node:hover{background:var(--bg-hover)}.path-bar-edge{font-size:9px;color:var(--text-dim);white-space:nowrap;flex-shrink:0}.path-bar-close{background:none;border:none;color:var(--text-dim);font-size:14px;cursor:pointer;padding:0 4px;margin-left:8px;flex-shrink:0}.path-bar-close:hover{color:var(--text)}.walk-trail-edge{font-size:9px;color:var(--text-dim);padding:1px 0 1px 24px;opacity:.7}.walk-indicator{font-size:10px;color:var(--accent);padding:2px 8px;border:1px solid rgba(212,162,127,.4);border-radius:4px;cursor:pointer;opacity:.4}.walk-indicator.active{opacity:1;background:#d4a27f26;animation:walk-strobe 2s ease-in-out infinite}@keyframes walk-strobe{0%,to{opacity:.6;border-color:#d4a27f4d}50%{opacity:1;border-color:#d4a27fcc}}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
var En=Object.defineProperty;var wn=(t,e,s)=>e in t?En(t,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[e]=s;var mt=(t,e,s)=>wn(t,typeof e!="symbol"?e+"":e,s);(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))d(l);new MutationObserver(l=>{for(const C of l)if(C.type==="childList")for(const E of C.addedNodes)E.tagName==="LINK"&&E.rel==="modulepreload"&&d(E)}).observe(document,{childList:!0,subtree:!0});function s(l){const C={};return l.integrity&&(C.integrity=l.integrity),l.referrerPolicy&&(C.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?C.credentials="include":l.crossOrigin==="anonymous"?C.credentials="omit":C.credentials="same-origin",C}function d(l){if(l.ep)return;l.ep=!0;const C=s(l);fetch(l.href,C)}})();async function at(){const t=await fetch("/api/ontologies");return t.ok?t.json():[]}async function ht(t){const e=await fetch(`/api/ontologies/${encodeURIComponent(t)}`);if(!e.ok)throw new Error(`Failed to load ontology: ${t}`);return e.json()}async function kn(){const t=await fetch("/api/remotes");return t.ok?t.json():[]}async function Nn(t){const e=await fetch(`/api/remotes/${encodeURIComponent(t)}`);if(!e.ok)throw new Error(`Failed to load remote graph: ${t}`);return e.json()}async function wt(t,e){if(!(await fetch(`/api/ontologies/${encodeURIComponent(t)}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).ok)throw new Error(`Failed to save ontology: ${t}`)}async function Sn(t,e){if(!(await fetch(`/api/ontologies/${encodeURIComponent(t)}/rename`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e})})).ok)throw new Error(`Failed to rename ontology: ${t}`)}async function Ln(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches`);return e.ok?e.json():[]}async function _t(t,e,s){const d=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,from:s})});if(!d.ok){const l=await d.json().catch(()=>({}));throw new Error(l.error||"Failed to create branch")}}async function Gt(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches/switch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e})});if(!s.ok){const d=await s.json().catch(()=>({}));throw new Error(d.error||"Failed to switch branch")}}async function In(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/branches/${encodeURIComponent(e)}`,{method:"DELETE"});if(!s.ok){const d=await s.json().catch(()=>({}));throw new Error(d.error||"Failed to delete branch")}}async function Mn(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/snapshots`);return e.ok?e.json():[]}async function Tn(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/snapshots`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:e})});if(!s.ok){const d=await s.json().catch(()=>({}));throw new Error(d.error||"Failed to create snapshot")}}async function An(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/rollback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({version:e})});if(!s.ok){const d=await s.json().catch(()=>({}));throw new Error(d.error||"Failed to rollback")}}async function Bn(t){const e=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets`);return e.ok?e.json():[]}async function Vt(t,e,s,d,l){const C=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({label:e,description:l,nodeIds:s,edgeIds:d})});if(!C.ok)throw new Error("Failed to save snippet");return(await C.json()).id}async function Pn(t,e){const s=await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets/${encodeURIComponent(e)}`);if(!s.ok)throw new Error("Snippet not found");return s.json()}async function Rn(t,e){if(!(await fetch(`/api/graphs/${encodeURIComponent(t)}/snippets/${encodeURIComponent(e)}`,{method:"DELETE"})).ok)throw new Error("Failed to delete snippet")}const Jt="bp-dialog-overlay";function Bt(){var e;(e=document.querySelector(`.${Jt}`))==null||e.remove();const t=document.createElement("div");return t.className=Jt,document.body.appendChild(t),t}function Pt(t,e){const s=document.createElement("div");s.className="bp-dialog";const d=document.createElement("h4");return d.className="bp-dialog-title",d.textContent=e,s.appendChild(d),t.appendChild(s),t.addEventListener("click",l=>{l.target===t&&t.remove()}),s}function Rt(t,e){const s=document.createElement("div");s.className="bp-dialog-buttons";for(const d of e){const l=document.createElement("button");l.className="bp-dialog-btn",d.accent&&l.classList.add("bp-dialog-btn-accent"),d.danger&&l.classList.add("bp-dialog-btn-danger"),l.textContent=d.label,l.addEventListener("click",d.onClick),s.appendChild(l)}t.appendChild(s)}function hn(t,e){return new Promise(s=>{var E;const d=Bt(),l=Pt(d,t),C=document.createElement("p");C.className="bp-dialog-message",C.textContent=e,l.appendChild(C),Rt(l,[{label:"Cancel",onClick:()=>{d.remove(),s(!1)}},{label:"Confirm",accent:!0,onClick:()=>{d.remove(),s(!0)}}]),(E=l.querySelector(".bp-dialog-btn-accent"))==null||E.focus()})}function gt(t,e,s){return new Promise(d=>{const l=Bt(),C=Pt(l,t),E=document.createElement("input");E.type="text",E.className="bp-dialog-input",E.placeholder=e??"",E.value="",C.appendChild(E);const c=()=>{const o=E.value.trim();l.remove(),d(o||null)};E.addEventListener("keydown",o=>{o.key==="Enter"&&c(),o.key==="Escape"&&(l.remove(),d(null))}),Rt(C,[{label:"Cancel",onClick:()=>{l.remove(),d(null)}},{label:"OK",accent:!0,onClick:c}]),E.focus(),E.select()})}function Fn(){return new Promise(t=>{const e=Bt(),s=Pt(e,"Add Backpack"),d=document.createElement("p");d.className="bp-dialog-message",d.textContent="Enter the absolute path to a directory that should become a backpack. It will be shown in the sidebar using the last segment of the path as its display name.",s.appendChild(d);const l=document.createElement("label");l.className="bp-dialog-label",l.textContent="Path",s.appendChild(l);const C=document.createElement("div");C.className="bp-dialog-path-row",s.appendChild(C);const E=document.createElement("input");E.type="text",E.className="bp-dialog-input bp-dialog-path-input",E.placeholder="/Users/you/OneDrive/work",C.appendChild(E);const c=document.createElement("button");c.type="button",c.className="bp-dialog-btn bp-dialog-browse-btn",c.textContent="Browse...",C.appendChild(c),typeof window.showDirectoryPicker=="function"||(c.disabled=!0,c.title="Browser doesn't support native folder picker — paste the path manually"),c.addEventListener("click",async y=>{y.preventDefault();try{const z=await window.showDirectoryPicker({mode:"read"});R.textContent=`Picked "${z.name}" — paste the absolute path to it below.`,E.focus()}catch{}});const R=document.createElement("div");R.className="bp-dialog-picker-hint",s.appendChild(R);const i=document.createElement("div");i.className="bp-dialog-activate-row";const a=document.createElement("input");a.type="checkbox",a.id="bp-dialog-activate",a.checked=!0;const I=document.createElement("label");I.htmlFor="bp-dialog-activate",I.textContent="Switch to this backpack after registering",i.appendChild(a),i.appendChild(I),s.appendChild(i),E.addEventListener("dragover",y=>{y.preventDefault(),E.classList.add("bp-dialog-drag-over")}),E.addEventListener("dragleave",()=>{E.classList.remove("bp-dialog-drag-over")}),E.addEventListener("drop",y=>{var ce,O,Q;y.preventDefault(),E.classList.remove("bp-dialog-drag-over");const z=(ce=y.dataTransfer)==null?void 0:ce.items;if(!z||z.length===0)return;const j=(Q=(O=z[0]).webkitGetAsEntry)==null?void 0:Q.call(O);j!=null&&j.isDirectory&&(R.textContent=`Dropped "${j.name}" — paste the absolute path to it below.`)});const $=()=>{const y=E.value.trim();y&&(e.remove(),t({path:y,activate:a.checked}))};E.addEventListener("keydown",y=>{y.key==="Enter"&&$(),y.key==="Escape"&&(e.remove(),t(null))}),Rt(s,[{label:"Cancel",onClick:()=>{e.remove(),t(null)}},{label:"Register",accent:!0,onClick:$}]),E.focus()})}function Kt(t){return t>=1e3?`${(t/1e3).toFixed(1)}k tokens`:`${t} tokens`}function Zt(t,e){return t*50+e*25+50}function $n(t,e){const s=typeof e=="function"?{onSelect:e}:e,d=document.createElement("h2");d.textContent="Backpack Viewer";const l=document.createElement("input");l.type="text",l.placeholder="Filter...",l.id="filter";const C=document.createElement("ul");C.id="ontology-list";const E=document.createElement("h3");E.className="sidebar-section-heading",E.textContent="REMOTE GRAPHS",E.hidden=!0;const c=document.createElement("ul");c.id="remote-list",c.className="remote-list",c.hidden=!0;const o=document.createElement("div");o.className="sidebar-footer",o.innerHTML='<a href="mailto:support@backpackontology.com">support@backpackontology.com</a><span>Feedback & support</span><span class="sidebar-version">v0.5.1</span>';const R=document.createElement("button");R.className="sidebar-collapse-btn",R.title="Toggle sidebar (Tab)",R.innerHTML='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>';let i=!1;function a(){i=!i,t.classList.toggle("sidebar-collapsed",i),ae.classList.toggle("hidden",!i)}R.addEventListener("click",a);const I=document.createElement("div");I.className="sidebar-heading-row",I.appendChild(d),I.appendChild(R),t.appendChild(I);const $=document.createElement("div");$.className="sidebar-stale-banner",$.hidden=!0,t.appendChild($);const y=document.createElement("button");y.className="backpack-picker-pill",y.type="button",y.setAttribute("aria-haspopup","listbox"),y.setAttribute("aria-expanded","false");const z=document.createElement("span");z.className="backpack-picker-dot";const j=document.createElement("span");j.className="backpack-picker-name",j.textContent="...";const ce=document.createElement("span");ce.className="backpack-picker-caret",ce.textContent="▾",y.appendChild(z),y.appendChild(j),y.appendChild(ce);const O=document.createElement("div");O.className="backpack-picker-dropdown",O.hidden=!0,O.setAttribute("role","listbox");const Q=document.createElement("div");Q.className="backpack-picker-container",Q.appendChild(y),Q.appendChild(O),t.appendChild(Q);let V=!1;function T(){V=!1,O.hidden=!0,y.setAttribute("aria-expanded","false")}function te(){V=!0,O.hidden=!1,y.setAttribute("aria-expanded","true")}y.addEventListener("click",W=>{W.stopPropagation(),V?T():te()}),document.addEventListener("click",W=>{Q.contains(W.target)||T()});let G=[],ne=null;function J(){O.replaceChildren();for(const x of G){const r=document.createElement("button");r.className="backpack-picker-item",r.type="button",r.setAttribute("role","option"),x.active&&r.classList.add("active");const u=document.createElement("span");u.className="backpack-picker-item-dot",u.style.setProperty("--backpack-color",x.color);const b=document.createElement("span");b.className="backpack-picker-item-name",b.textContent=x.name;const g=document.createElement("span");g.className="backpack-picker-item-path",g.textContent=x.path,r.appendChild(u),r.appendChild(b),r.appendChild(g),r.addEventListener("click",k=>{k.stopPropagation(),T(),!x.active&&s.onBackpackSwitch&&s.onBackpackSwitch(x.name)}),O.appendChild(r)}const W=document.createElement("div");W.className="backpack-picker-divider",O.appendChild(W);const Y=document.createElement("button");Y.className="backpack-picker-item backpack-picker-add",Y.type="button",Y.textContent="+ Add new backpack…",Y.addEventListener("click",async x=>{if(x.stopPropagation(),T(),!s.onBackpackRegister)return;const r=await Fn();r&&s.onBackpackRegister(r.path,r.activate)}),O.appendChild(Y)}const ae=document.createElement("button");ae.className="tools-pane-toggle hidden",ae.title="Show sidebar (Tab)",ae.innerHTML='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 7 18 12 13 17"/><polyline points="6 7 11 12 6 17"/></svg>',ae.addEventListener("click",a),t.appendChild(l),t.appendChild(C),t.appendChild(E),t.appendChild(c),t.appendChild(o);let Z=[],ye=[],Ce="";return l.addEventListener("input",()=>{const W=l.value.toLowerCase();for(const Y of Z){const x=Y.dataset.name??"";Y.style.display=x.includes(W)?"":"none"}for(const Y of ye){const x=Y.dataset.name??"";Y.style.display=x.includes(W)?"":"none"}}),{setStaleVersionBanner(W,Y){$.replaceChildren();const x=document.createElement("div");x.className="sidebar-stale-banner-title",x.textContent=`Viewer ${W} is out of date`;const r=document.createElement("div");r.className="sidebar-stale-banner-subtitle",r.textContent=`Latest is ${Y}. Your version is stuck because of an npx cache.`;const u=document.createElement("pre");u.className="sidebar-stale-banner-hint",u.textContent=`npm cache clean --force
|
|
2
|
+
npx backpack-viewer@latest`,$.appendChild(x),$.appendChild(r),$.appendChild(u),$.hidden=!1},setBackpacks(W){G=W.slice();const Y=W.find(x=>x.active)??null;ne=Y,Y&&(j.textContent=Y.name,z.style.setProperty("--backpack-color",Y.color),t.style.setProperty("--backpack-color",Y.color)),J()},setActiveBackpack(W){ne=W,G=G.map(Y=>({...Y,active:Y.name===W.name})),G.some(Y=>Y.name===W.name)||G.push({...W,active:!0}),j.textContent=W.name,z.style.setProperty("--backpack-color",W.color),t.style.setProperty("--backpack-color",W.color),J()},getActiveBackpack(){return ne},setSummaries(W){C.innerHTML="";const Y=fetch("/api/locks").then(x=>x.json()).catch(()=>({}));Z=W.map(x=>{const r=document.createElement("li");r.className="ontology-item",r.dataset.name=x.name;const u=document.createElement("span");u.className="name",u.textContent=x.name;const b=document.createElement("span");b.className="stats";const g=Zt(x.nodeCount,x.edgeCount);b.textContent=`${x.nodeCount} nodes, ${x.edgeCount} edges · ~${Kt(g)}`;const k=document.createElement("span");k.className="sidebar-branch",k.dataset.graph=x.name;const f=document.createElement("span");if(f.className="sidebar-lock-badge",f.dataset.graph=x.name,Y.then(v=>{if(!f.isConnected)return;const M=v[x.name];M&&typeof M=="object"&&M.author&&(f.textContent=`editing: ${M.author}`,f.title=`Last activity: ${M.lastActivity??""}`,f.classList.add("active"))}),r.appendChild(u),r.appendChild(b),r.appendChild(f),r.appendChild(k),s.onRename){const v=document.createElement("button");v.className="sidebar-edit-btn",v.textContent="✎",v.title="Rename";const M=s.onRename;v.addEventListener("click",P=>{P.stopPropagation();const F=document.createElement("input");F.type="text",F.className="sidebar-rename-input",F.value=x.name,u.textContent="",u.appendChild(F),v.style.display="none",F.focus(),F.select();const L=()=>{const m=F.value.trim();m&&m!==x.name?M(x.name,m):(u.textContent=x.name,v.style.display="")};F.addEventListener("blur",L),F.addEventListener("keydown",m=>{m.key==="Enter"&&F.blur(),m.key==="Escape"&&(F.value=x.name,F.blur())})}),r.appendChild(v)}return r.addEventListener("click",()=>s.onSelect(x.name)),C.appendChild(r),r}),Ce&&this.setActive(Ce)},setActive(W){Ce=W;for(const Y of Z)Y.classList.toggle("active",Y.dataset.name===W);for(const Y of ye)Y.classList.toggle("active",Y.dataset.name===W)},setRemotes(W){c.replaceChildren(),ye=W.map(x=>{const r=document.createElement("li");r.className="ontology-item ontology-item-remote",r.dataset.name=x.name;const u=document.createElement("div");u.className="remote-name-row";const b=document.createElement("span");b.className="name",b.textContent=x.name;const g=document.createElement("span");g.className="remote-badge",g.textContent=x.pinned?"remote · pinned":"remote",g.title=`Source: ${x.source??x.url}`,u.appendChild(b),u.appendChild(g);const k=document.createElement("span");k.className="stats";const f=Zt(x.nodeCount,x.edgeCount);k.textContent=`${x.nodeCount} nodes, ${x.edgeCount} edges · ~${Kt(f)}`;const v=document.createElement("span");return v.className="remote-source",v.textContent=x.source??new URL(x.url).hostname,v.title=x.url,r.appendChild(u),r.appendChild(k),r.appendChild(v),r.addEventListener("click",()=>s.onSelect(x.name)),c.appendChild(r),r});const Y=W.length>0;E.hidden=!Y,c.hidden=!Y,Ce&&this.setActive(Ce)},setActiveBranch(W,Y,x){const r=C.querySelectorAll(`.sidebar-branch[data-graph="${W}"]`);for(const u of r){u.textContent=`/ ${Y}`,u.title="Click to switch branch",u.style.cursor="pointer";const b=u.cloneNode(!0);u.replaceWith(b),b.addEventListener("click",g=>{g.stopPropagation(),we(W,b,x??[])})}},setSnippets(W,Y){var u;const x=Z.find(b=>b.dataset.name===W);if(!x||((u=x.querySelector(".sidebar-snippets"))==null||u.remove(),Y.length===0))return;const r=document.createElement("div");r.className="sidebar-snippets";for(const b of Y){const g=document.createElement("div");g.className="sidebar-snippet";const k=document.createElement("span");k.className="sidebar-snippet-label",k.textContent=`◆ ${b.label}`,k.title=`${b.nodeCount} nodes — click to load`;const f=document.createElement("button");f.className="sidebar-snippet-delete",f.textContent="×",f.title="Delete snippet",f.addEventListener("click",v=>{var M;v.stopPropagation(),(M=s.onSnippetDelete)==null||M.call(s,W,b.id)}),g.appendChild(k),g.appendChild(f),g.addEventListener("click",v=>{var M;v.stopPropagation(),(M=s.onSnippetLoad)==null||M.call(s,W,b.id)}),r.appendChild(g)}x.appendChild(r)},toggle:a,expandBtn:ae};function we(W,Y,x){const r=t.querySelector(".branch-picker");r&&r.remove();const u=document.createElement("div");u.className="branch-picker";for(const g of x){const k=document.createElement("div");k.className="branch-picker-item",g.active&&k.classList.add("branch-picker-active");const f=document.createElement("span");if(f.textContent=g.name,k.appendChild(f),!g.active&&s.onBranchDelete){const v=document.createElement("button");v.className="branch-picker-delete",v.textContent="×",v.title=`Delete ${g.name}`,v.addEventListener("click",M=>{M.stopPropagation(),hn("Delete branch",`Delete branch "${g.name}"?`).then(P=>{P&&(s.onBranchDelete(W,g.name),u.remove())})}),k.appendChild(v)}g.active||k.addEventListener("click",()=>{var v;(v=s.onBranchSwitch)==null||v.call(s,W,g.name),u.remove()}),u.appendChild(k)}if(s.onBranchCreate){const g=document.createElement("div");g.className="branch-picker-item branch-picker-create",g.textContent="+ New branch",g.addEventListener("click",()=>{gt("New branch","Branch name").then(k=>{k&&(s.onBranchCreate(W,k),u.remove())})}),u.appendChild(g)}Y.after(u);const b=g=>{u.contains(g.target)||(u.remove(),document.removeEventListener("click",b))};setTimeout(()=>document.addEventListener("click",b),0)}}function Tt(t,e,s,d){return{x0:t,y0:e,x1:s,y1:d,cx:0,cy:0,mass:0,children:[null,null,null,null],body:null}}function Qt(t,e,s){const d=(t.x0+t.x1)/2,l=(t.y0+t.y1)/2;return(e<d?0:1)+(s<l?0:2)}function en(t,e){const s=(t.x0+t.x1)/2,d=(t.y0+t.y1)/2;switch(e){case 0:return[t.x0,t.y0,s,d];case 1:return[s,t.y0,t.x1,d];case 2:return[t.x0,d,s,t.y1];default:return[s,d,t.x1,t.y1]}}function At(t,e){if(t.mass===0&&t.body===null){t.body=e,t.cx=e.x,t.cy=e.y,t.mass=1;return}if(t.body!==null){const l=t.body;t.body=null,l.x===e.x&&l.y===e.y&&(e.x+=(Math.random()-.5)*.1,e.y+=(Math.random()-.5)*.1);const C=Qt(t,l.x,l.y);if(t.children[C]===null){const[E,c,o,R]=en(t,C);t.children[C]=Tt(E,c,o,R)}At(t.children[C],l)}const s=Qt(t,e.x,e.y);if(t.children[s]===null){const[l,C,E,c]=en(t,s);t.children[s]=Tt(l,C,E,c)}At(t.children[s],e);const d=t.mass+1;t.cx=(t.cx*t.mass+e.x)/d,t.cy=(t.cy*t.mass+e.y)/d,t.mass=d}function Dn(t){if(t.length===0)return null;let e=1/0,s=1/0,d=-1/0,l=-1/0;for(const i of t)i.x<e&&(e=i.x),i.y<s&&(s=i.y),i.x>d&&(d=i.x),i.y>l&&(l=i.y);const C=Math.max(d-e,l-s)*.1+50,E=(e+d)/2,c=(s+l)/2,o=Math.max(d-e,l-s)/2+C,R=Tt(E-o,c-o,E+o,c+o);for(const i of t)At(R,i);return R}function On(t,e,s,d,l,C){fn(t,e,s,d,l,C)}function fn(t,e,s,d,l,C){if(t.mass===0)return;const E=t.cx-e.x,c=t.cy-e.y,o=E*E+c*c;if(t.body!==null){if(t.body!==e){let i=Math.sqrt(o);i<C&&(i=C);const a=d*l/(i*i),I=E/i*a,$=c/i*a;e.vx-=I,e.vy-=$}return}const R=t.x1-t.x0;if(R*R/o<s*s){let i=Math.sqrt(o);i<C&&(i=C);const a=d*t.mass*l/(i*i),I=E/i*a,$=c/i*a;e.vx-=I,e.vy-=$;return}for(let i=0;i<4;i++)t.children[i]!==null&&fn(t.children[i],e,s,d,l,C)}const gn={clusterStrength:.08,spacing:1.5},tn=6e3,Wn=12e3,Hn=.004,yn=140,Cn=350,nn=.9,on=.01,ct=30,kt=50;let Xe={...gn};function Qe(t){t.clusterStrength!==void 0&&(Xe.clusterStrength=t.clusterStrength),t.spacing!==void 0&&(Xe.spacing=t.spacing)}function rt(){return{...Xe}}function zn(t){if(t<=30)return{...gn};const e=Math.log2(t/30);return{clusterStrength:Math.min(.5,.08+.06*e),spacing:Math.min(15,1.5+1.2*e)}}function jn(t,e){for(const s of Object.values(t))if(typeof s=="string")return s;return e}function sn(t,e,s){const d=new Set(e);let l=new Set(e);for(let C=0;C<s;C++){const E=new Set;for(const c of t.edges)l.has(c.sourceId)&&!d.has(c.targetId)&&E.add(c.targetId),l.has(c.targetId)&&!d.has(c.sourceId)&&E.add(c.sourceId);for(const c of E)d.add(c);if(l=E,E.size===0)break}return{nodes:t.nodes.filter(C=>d.has(C.id)),edges:t.edges.filter(C=>d.has(C.sourceId)&&d.has(C.targetId)),metadata:t.metadata}}function Nt(t){const e=new Map,s=[...new Set(t.nodes.map(o=>o.type))],d=Math.sqrt(s.length)*Cn*.6*Math.max(1,Xe.spacing),l=new Map,C=new Map;for(const o of t.nodes)C.set(o.type,(C.get(o.type)??0)+1);const E=t.nodes.map(o=>{const R=s.indexOf(o.type),i=2*Math.PI*R/Math.max(s.length,1),a=Math.cos(i)*d,I=Math.sin(i)*d,$=l.get(o.type)??0;l.set(o.type,$+1);const y=C.get(o.type)??1,z=2*Math.PI*$/y,j=yn*.6,ce={id:o.id,x:a+Math.cos(z)*j,y:I+Math.sin(z)*j,vx:0,vy:0,label:jn(o.properties,o.id),type:o.type};return e.set(o.id,ce),ce}),c=t.edges.map(o=>({sourceId:o.sourceId,targetId:o.targetId,type:o.type}));return{nodes:E,edges:c,nodeMap:e}}const Un=.7,qn=80;function Yn(t,e){const{nodes:s,edges:d,nodeMap:l}=t,C=Wn*Xe.spacing;if(s.length>=qn){const c=Dn(s);if(c)for(const R of s)On(c,R,Un,C,e,ct);const o=C-tn;if(o>0){const R=new Map;for(const i of s){let a=R.get(i.type);a||(a=[],R.set(i.type,a)),a.push(i)}for(const i of R.values())for(let a=0;a<i.length;a++)for(let I=a+1;I<i.length;I++){const $=i[a],y=i[I];let z=y.x-$.x,j=y.y-$.y,ce=Math.sqrt(z*z+j*j);ce<ct&&(ce=ct);const O=o*e/(ce*ce),Q=z/ce*O,V=j/ce*O;$.vx+=Q,$.vy+=V,y.vx-=Q,y.vy-=V}}}else for(let c=0;c<s.length;c++)for(let o=c+1;o<s.length;o++){const R=s[c],i=s[o];let a=i.x-R.x,I=i.y-R.y,$=Math.sqrt(a*a+I*I);$<ct&&($=ct);const z=(R.type===i.type?tn:C)*e/($*$),j=a/$*z,ce=I/$*z;R.vx-=j,R.vy-=ce,i.vx+=j,i.vy+=ce}for(const c of d){const o=l.get(c.sourceId),R=l.get(c.targetId);if(!o||!R)continue;const i=R.x-o.x,a=R.y-o.y,I=Math.sqrt(i*i+a*a);if(I===0)continue;const $=o.type===R.type?yn*Xe.spacing:Cn*Xe.spacing,y=Hn*(I-$)*e,z=i/I*y,j=a/I*y;o.vx+=z,o.vy+=j,R.vx-=z,R.vy-=j}for(const c of s)c.vx-=c.x*on*e,c.vy-=c.y*on*e;const E=new Map;for(const c of s){const o=E.get(c.type)??{x:0,y:0,count:0};o.x+=c.x,o.y+=c.y,o.count++,E.set(c.type,o)}for(const c of E.values())c.x/=c.count,c.y/=c.count;for(const c of s){const o=E.get(c.type);c.vx+=(o.x-c.x)*Xe.clusterStrength*e,c.vy+=(o.y-c.y)*Xe.clusterStrength*e}for(const c of s){c.vx*=nn,c.vy*=nn;const o=Math.sqrt(c.vx*c.vx+c.vy*c.vy);o>kt&&(c.vx=c.vx/o*kt,c.vy=c.vy/o*kt),c.x+=c.vx,c.y+=c.vy}return e*.995}const an=["#d4a27f","#c17856","#b07a5e","#d4956b","#a67c5a","#cc9e7c","#c4866a","#cb8e6c","#b8956e","#a88a70","#d9b08c","#c4a882","#e8b898","#b5927a","#a8886e","#d1a990"],cn=new Map;function Ne(t){const e=cn.get(t);if(e)return e;let s=0;for(let l=0;l<t.length;l++)s=(s<<5)-s+t.charCodeAt(l)|0;const d=an[Math.abs(s)%an.length];return cn.set(t,d),d}class Xn{constructor(e){mt(this,"cells",new Map);mt(this,"cellSize");mt(this,"invCell");this.cellSize=e,this.invCell=1/e}key(e,s){const d=e+32768|0,l=s+32768|0;return d*73856093^l*19349663}clear(){this.cells.clear()}insert(e){const s=Math.floor(e.x*this.invCell),d=Math.floor(e.y*this.invCell),l=this.key(s,d),C=this.cells.get(l);C?C.push(e):this.cells.set(l,[e])}rebuild(e){this.cells.clear();for(const s of e)this.insert(s)}query(e,s,d){const l=d*d,C=Math.floor((e-d)*this.invCell),E=Math.floor((e+d)*this.invCell),c=Math.floor((s-d)*this.invCell),o=Math.floor((s+d)*this.invCell);let R=null,i=l;for(let a=C;a<=E;a++)for(let I=c;I<=o;I++){const $=this.cells.get(this.key(a,I));if($)for(const y of $){const z=y.x-e,j=y.y-s,ce=z*z+j*j;ce<=i&&(i=ce,R=y)}}return R}}const et=new Map,_n=2e3;function Gn(t,e,s){return`${t}|${e}|${s}`}const Vn=new OffscreenCanvas(1,1),ln=Vn.getContext("2d");function Jn(t,e,s){ln.font=e;const d=ln.measureText(t),l=Math.ceil(d.width)+2,C=Math.ceil(d.actualBoundingBoxAscent+d.actualBoundingBoxDescent)+4,E=new OffscreenCanvas(l,C),c=E.getContext("2d");return c.font=e,c.fillStyle=s,c.textAlign="left",c.textBaseline="top",c.fillText(t,1,1),{canvas:E,width:l,height:C}}function rn(t,e,s,d,l,C,E){const c=Gn(e,l,C);let o=et.get(c);if(!o){if(et.size>=_n){const a=et.keys().next().value;a!==void 0&&et.delete(a)}o=Jn(e,l,C),et.set(c,o)}const R=s-o.width/2,i=E==="top"?d:d-o.height;t.drawImage(o.canvas,R,i)}function Kn(){et.clear()}function ge(t){return getComputedStyle(document.documentElement).getPropertyValue(t).trim()}const Fe=20,St=.001,Zn={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15,dotNodes:.1,hullsOnly:.05},Qn={zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300};function ft(t,e,s,d,l,C=100){const E=(t-s.x)*s.scale,c=(e-s.y)*s.scale;return E>=-C&&E<=d+C&&c>=-C&&c<=l+C}function eo(t,e,s,d){const l={...Zn,...(d==null?void 0:d.lod)??{}},C={...Qn,...(d==null?void 0:d.navigation)??{}},E={pulseSpeed:.02,...(d==null?void 0:d.walk)??{}},c=t.querySelector("canvas"),o=c.getContext("2d"),R=window.devicePixelRatio||1;let i={x:0,y:0,scale:1},a=null,I=1,$=0,y=new Set,z=null,j=!0,ce=!0,O=!0,Q=!0,V=null,T=null,te=1,G=null,ne=null,J=null,ae=!1,Z=[],ye=0,Ce=0;const we=400,W=new Xn(Fe*2);let Y=0;function x(){P(),Y||(Y=requestAnimationFrame(()=>{Y=0,p()}))}const r=150;let u=null,b=!1;function g(){if(!u)try{u=new Worker(new URL("/assets/layout-worker-BZXiBoiC.js",import.meta.url),{type:"module"}),u.onmessage=k,u.onerror=()=>{b=!1,u=null,xe()}}catch{return b=!1,null}return u}function k(h){const w=h.data;if(w.type==="tick"&&a){const B=w.positions,D=a.nodes;for(let H=0;H<D.length;H++)D[H].x=B[H*4],D[H].y=B[H*4+1],D[H].vx=B[H*4+2],D[H].vy=B[H*4+3];I=w.alpha,W.rebuild(D),p()}w.type==="settled"&&(I=0,ae&&Z.length>0&&!ie&&(ie=requestAnimationFrame(fe)))}let f=null,v=null,M=!0;function P(){M=!0}let F=null,L=null;const m=C.panAnimationMs;function N(){c.width=c.clientWidth*R,c.height=c.clientHeight*R,P(),x()}const U=new ResizeObserver(N);U.observe(t),N();function oe(h,w){return[h/i.scale+i.x,w/i.scale+i.y]}function de(h,w){if(!a)return null;const[B,D]=oe(h,w);return W.query(B,D,Fe)}function n(){if(!a)return;ye+=E.pulseSpeed;const h=new Set(Z);o.save(),o.setTransform(R,0,0,R,0,0),f&&(o.clearRect(0,0,c.clientWidth,c.clientHeight),o.drawImage(f,0,0,c.clientWidth,c.clientHeight)),o.save(),o.translate(-i.x*i.scale,-i.y*i.scale),o.scale(i.scale,i.scale);const w=ge("--canvas-walk-edge")||"#1a1a1a",B=[];for(const X of a.edges){if(!h.has(X.sourceId)||!h.has(X.targetId)||X.sourceId===X.targetId)continue;const q=a.nodeMap.get(X.sourceId),K=a.nodeMap.get(X.targetId);!q||!K||B.push(q.x,q.y,K.x,K.y)}if(B.length>0){o.beginPath();for(let X=0;X<B.length;X+=4)o.moveTo(B[X],B[X+1]),o.lineTo(B[X+2],B[X+3]);o.strokeStyle=w,o.lineWidth=3,o.globalAlpha=.5+.5*Math.sin(ye),o.stroke(),o.globalAlpha=1}const D=i.scale<l.smallNodes?Fe*.5:Fe,H=ge("--accent")||"#d4a27f";for(const X of Z){const q=a.nodeMap.get(X);if(!q||!ft(q.x,q.y,i,c.clientWidth,c.clientHeight))continue;const K=X===Z[Z.length-1],pe=.5+.5*Math.sin(ye);o.strokeStyle=H,o.lineWidth=K?3:2,o.globalAlpha=K?.5+.5*pe:.3+.4*pe,o.beginPath(),o.arc(q.x,q.y,D+(K?6:4),0,Math.PI*2),o.stroke()}o.globalAlpha=1,o.restore(),Q&&a.nodes.length>1&&A(),o.restore()}function p(){var zt;if(!a){o.clearRect(0,0,c.width,c.height);return}if(!M&&f&&ae&&Z.length>0&&I<St){n();return}const h=I<St&&ae&&Z.length>0;ae&&Z.length>0&&(ye+=E.pulseSpeed);const w=ae&&!h?new Set(Z):null,B=ge("--canvas-edge"),D=ge("--canvas-edge-highlight"),H=ge("--canvas-edge-dim"),X=ge("--canvas-edge-label"),q=ge("--canvas-edge-label-highlight"),K=ge("--canvas-edge-label-dim"),pe=ge("--canvas-arrow"),me=ge("--canvas-arrow-highlight"),he=ge("--canvas-node-label"),be=ge("--canvas-node-label-dim"),Oe=ge("--canvas-type-badge"),Me=ge("--canvas-type-badge-dim"),Ie=ge("--canvas-selection-border"),je=ge("--canvas-node-border");if(o.save(),o.setTransform(R,0,0,R,0,0),o.clearRect(0,0,c.clientWidth,c.clientHeight),o.save(),o.translate(-i.x*i.scale,-i.y*i.scale),o.scale(i.scale,i.scale),O&&i.scale>=l.smallNodes){const ee=new Map;for(const Se of a.nodes){if(z!==null&&!z.has(Se.id))continue;const Be=ee.get(Se.type)??[];Be.push(Se),ee.set(Se.type,Be)}for(const[Se,Be]of ee){if(Be.length<2)continue;const Ve=Ne(Se),_e=Fe*2.5;let We=1/0,ze=1/0,Pe=-1/0,Ye=-1/0;for(const Re of Be)Re.x<We&&(We=Re.x),Re.y<ze&&(ze=Re.y),Re.x>Pe&&(Pe=Re.x),Re.y>Ye&&(Ye=Re.y);o.beginPath();const se=(Pe-We)/2+_e,ke=(Ye-ze)/2+_e,ve=(We+Pe)/2,$e=(ze+Ye)/2;o.ellipse(ve,$e,se,ke,0,0,Math.PI*2),o.fillStyle=Ve,o.globalAlpha=.05,o.fill(),o.strokeStyle=Ve,o.globalAlpha=.12,o.lineWidth=1,o.setLineDash([4,4]),o.stroke(),o.setLineDash([]),o.globalAlpha=1}}let Te=null;if(y.size>0){Te=new Set;for(const ee of a.edges)y.has(ee.sourceId)&&Te.add(ee.targetId),y.has(ee.targetId)&&Te.add(ee.sourceId)}const vt=ge("--accent")||"#d4a27f",xt=ge("--canvas-walk-edge")||"#1a1a1a",Ze=i.scale>=l.hideArrows,pt=ce&&i.scale>=l.hideEdgeLabels;if(j){const ee=[],Se=[],Be=[],Ve=[],_e=[],We=[];for(const se of a.edges){const ke=a.nodeMap.get(se.sourceId),ve=a.nodeMap.get(se.targetId);if(!ke||!ve||!ft(ke.x,ke.y,i,c.clientWidth,c.clientHeight,200)&&!ft(ve.x,ve.y,i,c.clientWidth,c.clientHeight,200))continue;const $e=z===null||z.has(se.sourceId),Re=z===null||z.has(se.targetId),jt=$e&ℜif(z!==null&&!$e&&!Re)continue;const Et=y.size>0&&(y.has(se.sourceId)||y.has(se.targetId))||z!==null&&jt,Ut=z!==null&&!jt,qt=w!==null&&w.has(se.sourceId)&&w.has(se.targetId),Yt=J?V==null?void 0:V.edges.find(ut=>ut.sourceId===se.sourceId&&ut.targetId===se.targetId||ut.targetId===se.sourceId&&ut.sourceId===se.targetId):null,Xt=!!(J&&Yt&&J.edgeIds.has(Yt.id));if(se.sourceId===se.targetId){le(ke,se.type,Et,B,D,X,q);continue}(Xt?_e:qt?Ve:Et?Se:Ut?Be:ee).push(ke.x,ke.y,ve.x,ve.y),(Ze||pt)&&We.push({sx:ke.x,sy:ke.y,tx:ve.x,ty:ve.y,type:se.type,highlighted:Et,edgeDimmed:Ut,isPathEdge:Xt,isWalkEdge:qt})}const ze=Ze?1.5:1,Ye=[{lines:ee,color:B,width:ze,alpha:1},{lines:Be,color:H,width:ze,alpha:1},{lines:Se,color:D,width:Ze?2.5:1,alpha:1},{lines:_e,color:vt,width:3,alpha:1},{lines:Ve,color:xt,width:3,alpha:.5+.5*Math.sin(ye)}];for(const se of Ye)if(se.lines.length!==0){o.beginPath();for(let ke=0;ke<se.lines.length;ke+=4)o.moveTo(se.lines[ke],se.lines[ke+1]),o.lineTo(se.lines[ke+2],se.lines[ke+3]);o.strokeStyle=se.color,o.lineWidth=se.width,o.globalAlpha=se.alpha,o.stroke()}o.globalAlpha=1;for(const se of We)if(Ze&&_(se.sx,se.sy,se.tx,se.ty,se.highlighted||se.isPathEdge,pe,me),pt){const ke=(se.sx+se.tx)/2,ve=(se.sy+se.ty)/2;o.fillStyle=se.highlighted?q:se.edgeDimmed?K:X,o.font="9px system-ui, sans-serif",o.textAlign="center",o.textBaseline="bottom",o.fillText(se.type,ke,ve-4)}}const bt=performance.now()-Ce,Ee=Math.min(1,bt/we),Ue=1-(1-Ee)*(1-Ee),qe=Ee<1,Ht=i.scale<l.hullsOnly,bn=!Ht&&i.scale<l.dotNodes;if(!Ht)for(const ee of a.nodes){if(!ft(ee.x,ee.y,i,c.clientWidth,c.clientHeight))continue;const Se=Ne(ee.type);if(bn){const ve=z!==null&&!z.has(ee.id);o.fillStyle=Se;const $e=ve?.1:.8;o.globalAlpha=qe?$e*Ue:$e,o.fillRect(ee.x-2,ee.y-2,4,4);continue}const Be=y.has(ee.id),Ve=Te!==null&&Te.has(ee.id),_e=z!==null&&!z.has(ee.id),We=_e||y.size>0&&!Be&&!Ve,ze=i.scale<l.smallNodes?Fe*.5:Fe,Pe=qe?ze*Ue:ze;if(w!=null&&w.has(ee.id)){const ve=Z[Z.length-1]===ee.id,$e=.5+.5*Math.sin(ye),Re=ge("--accent")||"#d4a27f";o.save(),o.strokeStyle=Re,o.lineWidth=ve?3:2,o.globalAlpha=ve?.5+.5*$e:.3+.4*$e,o.beginPath(),o.arc(ee.x,ee.y,Pe+(ve?6:4),0,Math.PI*2),o.stroke(),o.restore()}Be&&(o.save(),o.shadowColor=Se,o.shadowBlur=20,o.beginPath(),o.arc(ee.x,ee.y,Pe+3,0,Math.PI*2),o.fillStyle=Se,o.globalAlpha=.3,o.fill(),o.restore()),o.beginPath(),o.arc(ee.x,ee.y,Pe,0,Math.PI*2),o.fillStyle=Se;const Ye=_e?.1:We?.3:1;o.globalAlpha=qe?Ye*Ue:Ye,o.fill(),o.strokeStyle=Be?Ie:je,o.lineWidth=Be?3:1.5,o.stroke(),o.globalAlpha=1,J&&J.nodeIds.has(ee.id)&&!Be&&(o.save(),o.shadowColor=ge("--accent")||"#d4a27f",o.shadowBlur=15,o.beginPath(),o.arc(ee.x,ee.y,Pe+2,0,Math.PI*2),o.strokeStyle=ge("--accent")||"#d4a27f",o.globalAlpha=.5,o.lineWidth=2,o.stroke(),o.restore());const se=V==null?void 0:V.nodes.find(ve=>ve.id===ee.id);if(((zt=se==null?void 0:se.properties)==null?void 0:zt._starred)===!0&&(o.fillStyle="#ffd700",o.font="10px system-ui, sans-serif",o.textAlign="left",o.textBaseline="bottom",o.fillText("★",ee.x+Pe-2,ee.y-Pe+2)),i.scale>=l.hideLabels){const ve=ee.label.length>24?ee.label.slice(0,22)+"...":ee.label,$e=We?be:he;rn(o,ve,ee.x,ee.y+Pe+4,"11px system-ui, sans-serif",$e,"top")}if(i.scale>=l.hideBadges){const ve=We?Me:Oe;rn(o,ee.type,ee.x,ee.y-Pe-3,"9px system-ui, sans-serif",ve,"bottom")}o.globalAlpha=1}if(o.restore(),o.restore(),h){const ee=c.width,Se=c.height;(!f||f.width!==ee||f.height!==Se)&&(f=new OffscreenCanvas(ee,Se),v=f.getContext("2d")),v&&(v.clearRect(0,0,ee,Se),v.drawImage(c,0,0),M=!1),n();return}Q&&a.nodes.length>1&&A()}function A(){if(!a)return;const h=140,w=100,B=8,D=c.clientWidth-h-16,H=c.clientHeight-w-16;let X=1/0,q=1/0,K=-1/0,pe=-1/0;for(const Ee of a.nodes)Ee.x<X&&(X=Ee.x),Ee.y<q&&(q=Ee.y),Ee.x>K&&(K=Ee.x),Ee.y>pe&&(pe=Ee.y);const me=K-X||1,he=pe-q||1,be=Math.min((h-B*2)/me,(w-B*2)/he),Oe=D+B+(h-B*2-me*be)/2,Me=H+B+(w-B*2-he*be)/2;o.save(),o.setTransform(R,0,0,R,0,0),o.fillStyle=ge("--bg-surface")||"#1a1a1a",o.globalAlpha=.85,o.beginPath(),o.roundRect(D,H,h,w,8),o.fill(),o.strokeStyle=ge("--border")||"#2a2a2a",o.globalAlpha=1,o.lineWidth=1,o.stroke(),o.globalAlpha=.15,o.strokeStyle=ge("--canvas-edge")||"#555",o.lineWidth=.5;for(const Ee of a.edges){const Ue=a.nodeMap.get(Ee.sourceId),qe=a.nodeMap.get(Ee.targetId);!Ue||!qe||Ee.sourceId===Ee.targetId||(o.beginPath(),o.moveTo(Oe+(Ue.x-X)*be,Me+(Ue.y-q)*be),o.lineTo(Oe+(qe.x-X)*be,Me+(qe.y-q)*be),o.stroke())}o.globalAlpha=.8;for(const Ee of a.nodes){const Ue=Oe+(Ee.x-X)*be,qe=Me+(Ee.y-q)*be;o.beginPath(),o.arc(Ue,qe,2,0,Math.PI*2),o.fillStyle=Ne(Ee.type),o.fill()}const Ie=i.x,je=i.y,Te=i.x+c.clientWidth/i.scale,vt=i.y+c.clientHeight/i.scale,xt=Oe+(Ie-X)*be,Ze=Me+(je-q)*be,pt=(Te-Ie)*be,bt=(vt-je)*be;o.globalAlpha=.3,o.strokeStyle=ge("--accent")||"#d4a27f",o.lineWidth=1.5,o.strokeRect(Math.max(D,Math.min(xt,D+h)),Math.max(H,Math.min(Ze,H+w)),Math.min(pt,h),Math.min(bt,w)),o.globalAlpha=1,o.restore()}function _(h,w,B,D,H,X,q){const K=Math.atan2(D-w,B-h),pe=B-Math.cos(K)*Fe,me=D-Math.sin(K)*Fe,he=8;o.beginPath(),o.moveTo(pe,me),o.lineTo(pe-he*Math.cos(K-.4),me-he*Math.sin(K-.4)),o.lineTo(pe-he*Math.cos(K+.4),me-he*Math.sin(K+.4)),o.closePath(),o.fillStyle=H?q:X,o.fill()}function le(h,w,B,D,H,X,q){const K=h.x+Fe+15,pe=h.y-Fe-15;o.beginPath(),o.arc(K,pe,15,0,Math.PI*2),o.strokeStyle=B?H:D,o.lineWidth=B?2.5:1.5,o.stroke(),ce&&(o.fillStyle=B?q:X,o.font="9px system-ui, sans-serif",o.textAlign="center",o.fillText(w,K,pe-18))}function ue(){if(!F||!L)return;const h=performance.now()-L.time,w=Math.min(h/m,1),B=1-Math.pow(1-w,3);i.x=L.x+(F.x-L.x)*B,i.y=L.y+(F.y-L.y)*B,P(),p(),w<1?requestAnimationFrame(ue):(F=null,L=null)}let ie=0;function fe(){if(!ae||Z.length===0){ie=0;return}p(),ie=requestAnimationFrame(fe)}function Le(){if(!a||a.nodes.length===0)return;let h=1/0,w=1/0,B=-1/0,D=-1/0;for(const me of a.nodes)me.x<h&&(h=me.x),me.y<w&&(w=me.y),me.x>B&&(B=me.x),me.y>D&&(D=me.y);const H=Fe*4,X=B-h+H*2,q=D-w+H*2,K=c.clientWidth/Math.max(X,1),pe=c.clientHeight/Math.max(q,1);i.scale=Math.min(K,pe,2),i.x=(h+B)/2-c.clientWidth/(2*i.scale),i.y=(w+D)/2-c.clientHeight/(2*i.scale),x()}function xe(){if(!a||I<St){ae&&Z.length>0&&!ie&&(ie=requestAnimationFrame(fe));return}I=Yn(a,I),W.rebuild(a.nodes),p(),$=requestAnimationFrame(xe)}let He=!1,yt=!1,Je=0,Ke=0;c.addEventListener("mousedown",h=>{He=!0,yt=!1,Je=h.clientX,Ke=h.clientY}),c.addEventListener("mousemove",h=>{if(!He)return;const w=h.clientX-Je,B=h.clientY-Ke;(Math.abs(w)>5||Math.abs(B)>5)&&(yt=!0),i.x-=w/i.scale,i.y-=B/i.scale,Je=h.clientX,Ke=h.clientY,x()}),c.addEventListener("mouseup",h=>{if(He=!1,yt)return;const w=c.getBoundingClientRect(),B=h.clientX-w.left,D=h.clientY-w.top,H=de(B,D),X=h.ctrlKey||h.metaKey;if(ae&&T&&H&&a){const q=Z.length>0?Z[Z.length-1]:T[0],K=new Set([q]),pe=[{id:q,path:[q]}];let me=null;for(;pe.length>0;){const{id:Me,path:Ie}=pe.shift();if(Me===H.id){me=Ie;break}for(const je of a.edges){let Te=null;je.sourceId===Me?Te=je.targetId:je.targetId===Me&&(Te=je.sourceId),Te&&!K.has(Te)&&(K.add(Te),pe.push({id:Te,path:[...Ie,Te]}))}}if(!me)return;for(const Me of me.slice(1))Z.includes(Me)||Z.push(Me);T=[H.id];const he=Math.max(1,te);te=he;const be=sn(V,[H.id],he);cancelAnimationFrame($),u&&u.postMessage({type:"stop"}),a=Nt(be),W.rebuild(a.nodes),I=1,y=new Set([H.id]),z=null,i={x:0,y:0,scale:1},b=be.nodes.length>=r;const Oe=b?g():null;Oe?Oe.postMessage({type:"start",data:be}):(b=!1,xe()),setTimeout(()=>{a&&Le()},300),s==null||s({seedNodeIds:[H.id],hops:he,totalNodes:be.nodes.length}),e==null||e([H.id]);return}if(H){X?y.has(H.id)?y.delete(H.id):y.add(H.id):y.size===1&&y.has(H.id)?y.clear():(y.clear(),y.add(H.id));const q=[...y];e==null||e(q.length>0?q:null)}else y.clear(),e==null||e(null);x()}),c.addEventListener("mouseleave",()=>{He=!1}),c.addEventListener("wheel",h=>{h.preventDefault();const w=c.getBoundingClientRect(),B=h.clientX-w.left,D=h.clientY-w.top,[H,X]=oe(B,D),q=h.ctrlKey?1-h.deltaY*.01:h.deltaY>0?.9:1.1;i.scale=Math.max(C.zoomMin,Math.min(C.zoomMax,i.scale*q)),i.x=H-B/i.scale,i.y=X-D/i.scale,x()},{passive:!1});let De=[],Ft=0,$t=1,Dt=0,Ot=0,Ct=!1;c.addEventListener("touchstart",h=>{h.preventDefault(),De=Array.from(h.touches),De.length===2?(Ft=Wt(De[0],De[1]),$t=i.scale):De.length===1&&(Je=De[0].clientX,Ke=De[0].clientY,Dt=De[0].clientX,Ot=De[0].clientY,Ct=!1)},{passive:!1}),c.addEventListener("touchmove",h=>{h.preventDefault();const w=Array.from(h.touches);if(w.length===2&&De.length===2){const D=Wt(w[0],w[1])/Ft;i.scale=Math.max(C.zoomMin,Math.min(C.zoomMax,$t*D)),x()}else if(w.length===1){const B=w[0].clientX-Je,D=w[0].clientY-Ke;(Math.abs(w[0].clientX-Dt)>10||Math.abs(w[0].clientY-Ot)>10)&&(Ct=!0),i.x-=B/i.scale,i.y-=D/i.scale,Je=w[0].clientX,Ke=w[0].clientY,x()}De=w},{passive:!1}),c.addEventListener("touchend",h=>{if(h.preventDefault(),Ct||h.changedTouches.length!==1)return;const w=h.changedTouches[0],B=c.getBoundingClientRect(),D=w.clientX-B.left,H=w.clientY-B.top,X=de(D,H);if(X){y.size===1&&y.has(X.id)?y.clear():(y.clear(),y.add(X.id));const q=[...y];e==null||e(q.length>0?q:null)}else y.clear(),e==null||e(null);x()},{passive:!1}),c.addEventListener("gesturestart",h=>h.preventDefault()),c.addEventListener("gesturechange",h=>h.preventDefault());function Wt(h,w){const B=h.clientX-w.clientX,D=h.clientY-w.clientY;return Math.sqrt(B*B+D*D)}const tt=document.createElement("div");tt.className="zoom-controls";const nt=document.createElement("button");nt.className="zoom-btn",nt.textContent="+",nt.title="Zoom in",nt.addEventListener("click",()=>{const h=c.clientWidth/2,w=c.clientHeight/2,[B,D]=oe(h,w);i.scale=Math.min(C.zoomMax,i.scale*C.zoomFactor),i.x=B-h/i.scale,i.y=D-w/i.scale,x()});const ot=document.createElement("button");ot.className="zoom-btn",ot.textContent="−",ot.title="Zoom out",ot.addEventListener("click",()=>{const h=c.clientWidth/2,w=c.clientHeight/2,[B,D]=oe(h,w);i.scale=Math.max(C.zoomMin,i.scale/C.zoomFactor),i.x=B-h/i.scale,i.y=D-w/i.scale,x()});const st=document.createElement("button");st.className="zoom-btn",st.textContent="○",st.title="Reset zoom",st.addEventListener("click",()=>{if(a){if(i={x:0,y:0,scale:1},a.nodes.length>0){let h=1/0,w=1/0,B=-1/0,D=-1/0;for(const q of a.nodes)q.x<h&&(h=q.x),q.y<w&&(w=q.y),q.x>B&&(B=q.x),q.y>D&&(D=q.y);const H=(h+B)/2,X=(w+D)/2;i.x=H-c.clientWidth/2,i.y=X-c.clientHeight/2}x()}}),tt.appendChild(nt),tt.appendChild(st),tt.appendChild(ot),t.appendChild(tt);const Ae=document.createElement("div");Ae.className="node-tooltip",Ae.style.display="none",t.appendChild(Ae);let dt=null,Ge=null;return c.addEventListener("mousemove",h=>{if(He){Ae.style.display!=="none"&&(Ae.style.display="none",dt=null);return}const w=c.getBoundingClientRect(),B=h.clientX-w.left,D=h.clientY-w.top,H=de(B,D),X=(H==null?void 0:H.id)??null;X!==dt?(dt=X,Ae.style.display="none",Ge&&clearTimeout(Ge),Ge=null,X&&H&&(Ge=setTimeout(()=>{if(!a||!V)return;const q=a.edges.filter(K=>K.sourceId===X||K.targetId===X).length;Ae.textContent=`${H.label} · ${H.type} · ${q} edge${q!==1?"s":""}`,Ae.style.left=`${h.clientX-w.left+12}px`,Ae.style.top=`${h.clientY-w.top-8}px`,Ae.style.display="block"},200))):X&&Ae.style.display==="block"&&(Ae.style.left=`${h.clientX-w.left+12}px`,Ae.style.top=`${h.clientY-w.top-8}px`)}),c.addEventListener("mouseleave",()=>{Ae.style.display="none",dt=null,Ge&&clearTimeout(Ge),Ge=null}),{loadGraph(h){if(cancelAnimationFrame($),u&&u.postMessage({type:"stop"}),Kn(),V=h,T=null,G=null,ne=null,Ce=performance.now(),a=Nt(h),W.rebuild(a.nodes),I=1,y=new Set,z=null,i={x:0,y:0,scale:1},a.nodes.length>0){let B=1/0,D=1/0,H=-1/0,X=-1/0;for(const he of a.nodes)he.x<B&&(B=he.x),he.y<D&&(D=he.y),he.x>H&&(H=he.x),he.y>X&&(X=he.y);const q=(B+H)/2,K=(D+X)/2,pe=c.clientWidth,me=c.clientHeight;i.x=q-pe/2,i.y=K-me/2}b=h.nodes.length>=r;const w=b?g():null;w?w.postMessage({type:"start",data:h}):(b=!1,xe())},setFilteredNodeIds(h){z=h,x()},panToNode(h){this.panToNodes([h])},panToNodes(h){if(!a||h.length===0)return;const w=h.map(H=>a.nodeMap.get(H)).filter(Boolean);if(w.length===0)return;y=new Set(h),e==null||e(h);const B=c.clientWidth,D=c.clientHeight;if(w.length===1)L={x:i.x,y:i.y,time:performance.now()},F={x:w[0].x-B/(2*i.scale),y:w[0].y-D/(2*i.scale)};else{let H=1/0,X=1/0,q=-1/0,K=-1/0;for(const Ie of w)Ie.x<H&&(H=Ie.x),Ie.y<X&&(X=Ie.y),Ie.x>q&&(q=Ie.x),Ie.y>K&&(K=Ie.y);const pe=Fe*4,me=q-H+pe*2,he=K-X+pe*2,be=Math.min(B/me,D/he,i.scale);i.scale=be;const Oe=(H+q)/2,Me=(X+K)/2;L={x:i.x,y:i.y,time:performance.now()},F={x:Oe-B/(2*i.scale),y:Me-D/(2*i.scale)}}ue()},setEdges(h){j=h,x()},setEdgeLabels(h){ce=h,x()},setTypeHulls(h){O=h,x()},setMinimap(h){Q=h,x()},centerView(){Le()},panBy(h,w){i.x+=h/i.scale,i.y+=w/i.scale,x()},zoomBy(h){const w=c.clientWidth/2,B=c.clientHeight/2,[D,H]=oe(w,B);i.scale=Math.max(C.zoomMin,Math.min(C.zoomMax,i.scale*h)),i.x=D-w/i.scale,i.y=H-B/i.scale,x()},reheat(){b&&u?u.postMessage({type:"params",params:rt()}):(I=.5,cancelAnimationFrame($),xe())},exportImage(h){if(!a)return"";const w=c.width,B=c.height;if(h==="png"){const q=document.createElement("canvas");q.width=w,q.height=B;const K=q.getContext("2d");return K.fillStyle=ge("--bg")||"#141414",K.fillRect(0,0,w,B),K.drawImage(c,0,0),xn(K,w,B),q.toDataURL("image/png")}const D=c.toDataURL("image/png"),H=Math.max(16,Math.round(w/80)),X=`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${B}">
|
|
3
|
+
<image href="${D}" width="${w}" height="${B}"/>
|
|
4
|
+
<text x="${w-20}" y="${B-16}" text-anchor="end" font-family="system-ui, sans-serif" font-size="${H}" fill="#ffffff" opacity="0.4">backpackontology.com</text>
|
|
5
|
+
</svg>`;return"data:image/svg+xml;charset=utf-8,"+encodeURIComponent(X)},enterFocus(h,w){if(!V||!a)return;T||(G=a,ne={...i}),T=h,te=w;const B=sn(V,h,w);cancelAnimationFrame($),u&&u.postMessage({type:"stop"}),a=Nt(B),W.rebuild(a.nodes),I=1,y=new Set(h),z=null,i={x:0,y:0,scale:1},b=B.nodes.length>=r;const D=b?g():null;D?D.postMessage({type:"start",data:B}):(b=!1,xe()),setTimeout(()=>{!a||!T||Le()},300),s==null||s({seedNodeIds:h,hops:w,totalNodes:B.nodes.length})},exitFocus(){!T||!G||(cancelAnimationFrame($),u&&u.postMessage({type:"stop"}),a=G,W.rebuild(a.nodes),i=ne??{x:0,y:0,scale:1},T=null,G=null,ne=null,y=new Set,z=null,x(),s==null||s(null))},isFocused(){return T!==null},getFocusInfo(){return!T||!a?null:{seedNodeIds:T,hops:te,totalNodes:a.nodes.length}},findPath(h,w){if(!a)return null;const B=new Set([h]),D=[{nodeId:h,path:[h],edges:[]}];for(;D.length>0;){const{nodeId:H,path:X,edges:q}=D.shift();if(H===w)return{nodeIds:X,edgeIds:q};for(const K of a.edges){let pe=null;if(K.sourceId===H?pe=K.targetId:K.targetId===H&&(pe=K.sourceId),pe&&!B.has(pe)){B.add(pe);const me=V==null?void 0:V.edges.find(he=>he.sourceId===K.sourceId&&he.targetId===K.targetId||he.targetId===K.sourceId&&he.sourceId===K.targetId);D.push({nodeId:pe,path:[...X,pe],edges:[...q,(me==null?void 0:me.id)??""]})}}}return null},setHighlightedPath(h,w){h&&w?J={nodeIds:new Set(h),edgeIds:new Set(w)}:J=null,x()},clearHighlightedPath(){J=null,x()},setWalkMode(h){ae=h,h?(Z=T?[...T]:[...y],ie||(ie=requestAnimationFrame(fe))):(Z=[],ie&&(cancelAnimationFrame(ie),ie=0)),x()},getWalkMode(){return ae},getWalkTrail(){return[...Z]},getFilteredNodeIds(){return z},removeFromWalkTrail(h){Z=Z.filter(w=>w!==h),x()},nodeAtScreen(h,w){return de(h,w)},getNodeIds(){if(!a)return[];if(T){const h=new Set(T),w=a.nodes.filter(D=>h.has(D.id)).map(D=>D.id),B=a.nodes.filter(D=>!h.has(D.id)).map(D=>D.id);return[...w,...B]}return a.nodes.map(h=>h.id)},destroy(){cancelAnimationFrame($),Y&&(cancelAnimationFrame(Y),Y=0),ie&&(cancelAnimationFrame(ie),ie=0),u&&(u.terminate(),u=null),f=null,v=null,U.disconnect()}};function xn(h,w,B){const D=Math.max(16,Math.round(w/80));h.save(),h.font=`${D}px system-ui, sans-serif`,h.fillStyle="rgba(255, 255, 255, 0.4)",h.textAlign="right",h.textBaseline="bottom",h.fillText("backpackontology.com",w-20,B-16),h.restore()}}function it(t){for(const e of Object.values(t.properties))if(typeof e=="string")return e;return t.id}const to="✎";function no(t,e,s,d){const l=document.createElement("div");l.id="info-panel",l.className="info-panel hidden",t.appendChild(l);let C=!1,E=[],c=-1,o=!1,R=null,i=[],a=!1,I=[],$=-1;function y(){l.classList.add("hidden"),l.classList.remove("info-panel-maximized"),l.innerHTML="",C=!1,E=[],c=-1}function z(T){!R||!s||(c<E.length-1&&(E=E.slice(0,c+1)),E.push(T),c=E.length-1,o=!0,s(T),o=!1)}function j(){if(c<=0||!R)return;c--,o=!0;const T=E[c];s==null||s(T),Q(T,R),o=!1}function ce(){if(c>=E.length-1||!R)return;c++,o=!0;const T=E[c];s==null||s(T),Q(T,R),o=!1}function O(){const T=document.createElement("div");T.className="info-panel-toolbar";const te=document.createElement("button");te.className="info-toolbar-btn",te.textContent="←",te.title="Back",te.disabled=c<=0,te.addEventListener("click",j),T.appendChild(te);const G=document.createElement("button");if(G.className="info-toolbar-btn",G.textContent="→",G.title="Forward",G.disabled=c>=E.length-1,G.addEventListener("click",ce),T.appendChild(G),d&&i.length>0){const ae=document.createElement("button");ae.className="info-toolbar-btn info-focus-btn",ae.textContent="◎",ae.title="Focus on neighborhood (F)",ae.disabled=a,a&&(ae.style.opacity="0.3"),ae.addEventListener("click",()=>{a||d(i)}),T.appendChild(ae)}const ne=document.createElement("button");ne.className="info-toolbar-btn",ne.textContent=C?"⎘":"⛶",ne.title=C?"Restore":"Maximize",ne.addEventListener("click",()=>{C=!C,l.classList.toggle("info-panel-maximized",C),ne.textContent=C?"⎘":"⛶",ne.title=C?"Restore":"Maximize"}),T.appendChild(ne);const J=document.createElement("button");return J.className="info-toolbar-btn info-close-btn",J.textContent="×",J.title="Close",J.addEventListener("click",y),T.appendChild(J),T}function Q(T,te){const G=te.nodes.find(f=>f.id===T);if(!G)return;const ne=te.edges.filter(f=>f.sourceId===T||f.targetId===T);I=ne.map(f=>f.sourceId===T?f.targetId:f.sourceId),$=-1,l.innerHTML="",l.classList.remove("hidden"),C&&l.classList.add("info-panel-maximized");const J=document.createElement("div");J.className="info-panel-header",J.appendChild(O());const ae=document.createElement("div");ae.className="info-header";const Z=document.createElement("span");if(Z.className="info-type-badge",Z.textContent=G.type,Z.style.backgroundColor=Ne(G.type),e){Z.classList.add("info-editable");const f=document.createElement("button");f.className="info-inline-edit",f.textContent=to,f.addEventListener("click",v=>{v.stopPropagation();const M=document.createElement("input");M.type="text",M.className="info-edit-inline-input",M.value=G.type,Z.textContent="",Z.appendChild(M),M.focus(),M.select();const P=()=>{const F=M.value.trim();F&&F!==G.type?e.onChangeNodeType(T,F):(Z.textContent=G.type,Z.appendChild(f))};M.addEventListener("blur",P),M.addEventListener("keydown",F=>{F.key==="Enter"&&M.blur(),F.key==="Escape"&&(M.value=G.type,M.blur())})}),Z.appendChild(f)}const ye=document.createElement("h3");ye.className="info-label",ye.textContent=it(G);const Ce=document.createElement("span");Ce.className="info-id",Ce.textContent=G.id,ae.appendChild(Z),ae.appendChild(ye),ae.appendChild(Ce),J.appendChild(ae),l.appendChild(J);const we=document.createElement("div");we.className="info-panel-body";const W=Object.keys(G.properties),Y=lt("Properties");if(W.length>0){const f=document.createElement("dl");f.className="info-props";for(const v of W){const M=document.createElement("dt");M.textContent=v;const P=document.createElement("dd");if(e){const F=Lt(G.properties[v]),L=document.createElement("textarea");L.className="info-edit-input",L.value=F,L.rows=1,L.addEventListener("input",()=>dn(L)),L.addEventListener("keydown",N=>{N.key==="Enter"&&!N.shiftKey&&(N.preventDefault(),L.blur())}),L.addEventListener("blur",()=>{const N=L.value;N!==F&&e.onUpdateNode(T,{[v]:so(N)})}),P.appendChild(L),requestAnimationFrame(()=>dn(L));const m=document.createElement("button");m.className="info-delete-prop",m.textContent="×",m.title=`Remove ${v}`,m.addEventListener("click",()=>{const N={...G.properties};delete N[v],e.onUpdateNode(T,N)}),P.appendChild(m)}else P.appendChild(oo(G.properties[v]));f.appendChild(M),f.appendChild(P)}Y.appendChild(f)}if(e){const f=document.createElement("button");f.className="info-add-btn",f.textContent="+ Add property",f.addEventListener("click",()=>{const v=document.createElement("div");v.className="info-add-row";const M=document.createElement("input");M.type="text",M.className="info-edit-input",M.placeholder="key";const P=document.createElement("input");P.type="text",P.className="info-edit-input",P.placeholder="value";const F=document.createElement("button");F.className="info-add-save",F.textContent="Add",F.addEventListener("click",()=>{M.value&&e.onAddProperty(T,M.value,P.value)}),v.appendChild(M),v.appendChild(P),v.appendChild(F),Y.appendChild(v),M.focus()}),Y.appendChild(f)}if(we.appendChild(Y),ne.length>0){const f=lt(`Connections (${ne.length})`),v=document.createElement("ul");v.className="info-connections";for(const M of ne){const P=M.sourceId===T,F=P?M.targetId:M.sourceId,L=te.nodes.find(p=>p.id===F),m=L?it(L):F,N=document.createElement("li");if(N.className="info-connection",s&&L&&(N.classList.add("info-connection-link"),N.addEventListener("click",p=>{p.target.closest(".info-delete-edge")||z(F)})),L){const p=document.createElement("span");p.className="info-target-dot",p.style.backgroundColor=Ne(L.type),N.appendChild(p)}const U=document.createElement("span");U.className="info-arrow",U.textContent=P?"→":"←";const oe=document.createElement("span");oe.className="info-edge-type",oe.textContent=M.type;const de=document.createElement("span");de.className="info-target",de.textContent=m,N.appendChild(U),N.appendChild(oe),N.appendChild(de);const n=Object.keys(M.properties);if(n.length>0){const p=document.createElement("div");p.className="info-edge-props";for(const A of n){const _=document.createElement("span");_.className="info-edge-prop",_.textContent=`${A}: ${Lt(M.properties[A])}`,p.appendChild(_)}N.appendChild(p)}if(e){const p=document.createElement("button");p.className="info-delete-edge",p.textContent="×",p.title="Remove connection",p.addEventListener("click",A=>{A.stopPropagation(),e.onDeleteEdge(M.id)}),N.appendChild(p)}v.appendChild(N)}f.appendChild(v),we.appendChild(f)}const x=lt("Timestamps"),r=document.createElement("dl");r.className="info-props";const u=document.createElement("dt");u.textContent="created";const b=document.createElement("dd");b.textContent=pn(G.createdAt);const g=document.createElement("dt");g.textContent="updated";const k=document.createElement("dd");if(k.textContent=pn(G.updatedAt),r.appendChild(u),r.appendChild(b),r.appendChild(g),r.appendChild(k),x.appendChild(r),we.appendChild(x),e){const f=document.createElement("div");f.className="info-section info-danger";const v=document.createElement("button");v.className="info-delete-node",v.textContent="Delete node",v.addEventListener("click",()=>{e.onDeleteNode(T),y()}),f.appendChild(v),we.appendChild(f)}l.appendChild(we)}function V(T,te){const G=new Set(T),ne=te.nodes.filter(x=>G.has(x.id));if(ne.length===0)return;const J=te.edges.filter(x=>G.has(x.sourceId)&&G.has(x.targetId));l.innerHTML="",l.classList.remove("hidden"),C&&l.classList.add("info-panel-maximized"),l.appendChild(O());const ae=document.createElement("div");ae.className="info-header";const Z=document.createElement("h3");Z.className="info-label",Z.textContent=`${ne.length} nodes selected`,ae.appendChild(Z);const ye=document.createElement("div");ye.className="info-badge-row";const Ce=new Map;for(const x of ne)Ce.set(x.type,(Ce.get(x.type)??0)+1);for(const[x,r]of Ce){const u=document.createElement("span");u.className="info-type-badge",u.style.backgroundColor=Ne(x),u.textContent=r>1?`${x} (${r})`:x,ye.appendChild(u)}ae.appendChild(ye),l.appendChild(ae);const we=lt("Selected Nodes"),W=document.createElement("ul");W.className="info-connections";for(const x of ne){const r=document.createElement("li");r.className="info-connection",s&&(r.classList.add("info-connection-link"),r.addEventListener("click",()=>{z(x.id)}));const u=document.createElement("span");u.className="info-target-dot",u.style.backgroundColor=Ne(x.type);const b=document.createElement("span");b.className="info-target",b.textContent=it(x);const g=document.createElement("span");g.className="info-edge-type",g.textContent=x.type,r.appendChild(u),r.appendChild(b),r.appendChild(g),W.appendChild(r)}we.appendChild(W),l.appendChild(we);const Y=lt(J.length>0?`Connections Between Selected (${J.length})`:"Connections Between Selected");if(J.length===0){const x=document.createElement("p");x.className="info-empty-message",x.textContent="No direct connections between selected nodes",Y.appendChild(x)}else{const x=document.createElement("ul");x.className="info-connections";for(const r of J){const u=te.nodes.find(N=>N.id===r.sourceId),b=te.nodes.find(N=>N.id===r.targetId),g=u?it(u):r.sourceId,k=b?it(b):r.targetId,f=document.createElement("li");if(f.className="info-connection",u){const N=document.createElement("span");N.className="info-target-dot",N.style.backgroundColor=Ne(u.type),f.appendChild(N)}const v=document.createElement("span");v.className="info-target",v.textContent=g;const M=document.createElement("span");M.className="info-arrow",M.textContent="→";const P=document.createElement("span");P.className="info-edge-type",P.textContent=r.type;const F=document.createElement("span");if(F.className="info-arrow",F.textContent="→",f.appendChild(v),f.appendChild(M),f.appendChild(P),f.appendChild(F),b){const N=document.createElement("span");N.className="info-target-dot",N.style.backgroundColor=Ne(b.type),f.appendChild(N)}const L=document.createElement("span");L.className="info-target",L.textContent=k,f.appendChild(L);const m=Object.keys(r.properties);if(m.length>0){const N=document.createElement("div");N.className="info-edge-props";for(const U of m){const oe=document.createElement("span");oe.className="info-edge-prop",oe.textContent=`${U}: ${Lt(r.properties[U])}`,N.appendChild(oe)}f.appendChild(N)}x.appendChild(f)}Y.appendChild(x)}l.appendChild(Y)}return{show(T,te){if(R=te,i=T,T.length===1&&!o){const G=T[0];E[c]!==G&&(c<E.length-1&&(E=E.slice(0,c+1)),E.push(G),c=E.length-1)}T.length===1?Q(T[0],te):T.length>1&&V(T,te)},hide:y,goBack:j,goForward:ce,cycleConnection(T){if(I.length===0)return null;$===-1?$=T===1?0:I.length-1:($+=T,$>=I.length&&($=0),$<0&&($=I.length-1));const te=l.querySelectorAll(".info-connection");return te.forEach((G,ne)=>{G.classList.toggle("info-connection-active",ne===$)}),$>=0&&te[$]&&te[$].scrollIntoView({block:"nearest"}),I[$]??null},setFocusDisabled(T){a=T;const te=l.querySelector(".info-focus-btn");te&&(te.disabled=T,te.style.opacity=T?"0.3":"")},get visible(){return!l.classList.contains("hidden")}}}function lt(t){const e=document.createElement("div");e.className="info-section";const s=document.createElement("h4");return s.className="info-section-title",s.textContent=t,e.appendChild(s),e}function oo(t){if(Array.isArray(t)){const s=document.createElement("div");s.className="info-array";for(const d of t){const l=document.createElement("span");l.className="info-tag",l.textContent=String(d),s.appendChild(l)}return s}if(t!==null&&typeof t=="object"){const s=document.createElement("pre");return s.className="info-json",s.textContent=JSON.stringify(t,null,2),s}const e=document.createElement("span");return e.className="info-value",e.textContent=String(t??""),e}function Lt(t){return Array.isArray(t)?t.map(String).join(", "):t!==null&&typeof t=="object"?JSON.stringify(t):String(t??"")}function so(t){const e=t.trim();if(e==="true")return!0;if(e==="false")return!1;if(e!==""&&!isNaN(Number(e)))return Number(e);if(e.startsWith("[")&&e.endsWith("]")||e.startsWith("{")&&e.endsWith("}"))try{return JSON.parse(e)}catch{return t}return t}function dn(t){t.style.height="auto",t.style.height=t.scrollHeight+"px"}function pn(t){try{return new Date(t).toLocaleString()}catch{return t}}function vn(t){for(const e of Object.values(t.properties))if(typeof e=="string")return e;return t.id}function un(t,e){const s=e.toLowerCase();if(vn(t).toLowerCase().includes(s)||t.type.toLowerCase().includes(s))return!0;for(const d of Object.values(t.properties))if(typeof d=="string"&&d.toLowerCase().includes(s))return!0;return!1}function ao(t,e){const s=(e==null?void 0:e.maxResults)??8,d=(e==null?void 0:e.debounceMs)??150;let l=null,C=null,E=null,c=null;const o=document.createElement("div");o.className="search-overlay hidden";const R=document.createElement("div");R.className="search-input-wrap";const i=document.createElement("input");i.className="search-input",i.type="text",i.placeholder="Search nodes...",i.setAttribute("autocomplete","off"),i.setAttribute("spellcheck","false");const a=document.createElement("kbd");a.className="search-kbd",a.textContent="/",R.appendChild(i),R.appendChild(a);const I=document.createElement("ul");I.className="search-results hidden",o.appendChild(R),o.appendChild(I),t.appendChild(o);function $(){if(!l)return null;const O=i.value.trim();if(O.length===0)return null;const Q=new Set;for(const V of l.nodes)un(V,O)&&Q.add(V.id);return Q}function y(){const O=$();C==null||C(O),z()}function z(){I.innerHTML="",j=-1;const O=i.value.trim();if(!l||O.length===0){I.classList.add("hidden");return}const Q=[];for(const V of l.nodes)if(un(V,O)&&(Q.push(V),Q.length>=s))break;if(Q.length===0){I.classList.add("hidden");return}for(const V of Q){const T=document.createElement("li");T.className="search-result-item";const te=document.createElement("span");te.className="search-result-dot",te.style.backgroundColor=Ne(V.type);const G=document.createElement("span");G.className="search-result-label";const ne=vn(V);G.textContent=ne.length>36?ne.slice(0,34)+"...":ne;const J=document.createElement("span");J.className="search-result-type",J.textContent=V.type,T.appendChild(te),T.appendChild(G),T.appendChild(J),T.addEventListener("click",()=>{E==null||E(V.id),i.value="",I.classList.add("hidden"),y()}),I.appendChild(T)}I.classList.remove("hidden")}i.addEventListener("input",()=>{c&&clearTimeout(c),c=setTimeout(y,d)});let j=-1;function ce(){const O=I.querySelectorAll(".search-result-item");O.forEach((Q,V)=>{Q.classList.toggle("search-result-active",V===j)}),j>=0&&O[j]&&O[j].scrollIntoView({block:"nearest"})}return i.addEventListener("keydown",O=>{const Q=I.querySelectorAll(".search-result-item");O.key==="ArrowDown"?(O.preventDefault(),Q.length>0&&(j=Math.min(j+1,Q.length-1),ce())):O.key==="ArrowUp"?(O.preventDefault(),Q.length>0&&(j=Math.max(j-1,0),ce())):O.key==="Enter"?(O.preventDefault(),j>=0&&Q[j]?Q[j].click():Q.length>0&&Q[0].click(),i.blur()):O.key==="Escape"&&(i.value="",i.blur(),I.classList.add("hidden"),j=-1,y())}),document.addEventListener("click",O=>{o.contains(O.target)||I.classList.add("hidden")}),i.addEventListener("focus",()=>a.classList.add("hidden")),i.addEventListener("blur",()=>{i.value.length===0&&a.classList.remove("hidden")}),{setLearningGraphData(O){l=O,i.value="",I.classList.add("hidden"),l&&l.nodes.length>0?o.classList.remove("hidden"):o.classList.add("hidden")},onFilterChange(O){C=O},onNodeSelect(O){E=O},clear(){i.value="",I.classList.add("hidden"),C==null||C(null)},focus(){i.focus()}}}function co(t,e){let s=null,d=null,l=!0,C=null,E=!0,c=!0,o=!0,R="types",i="",a="",I=[],$=[];const y={types:new Set,nodeIds:new Set};function z(){if(!s)return[];const r=new Set;for(const u of s.nodes)y.types.has(u.type)&&r.add(u.id);for(const u of y.nodeIds)r.add(u);return[...r]}function j(r){if(y.nodeIds.has(r))return!0;const u=s==null?void 0:s.nodes.find(b=>b.id===r);return u?y.types.has(u.type):!1}function ce(){return y.types.size===0&&y.nodeIds.size===0}function O(){const r=z();e.onFocusChange(r.length>0?r:null)}const Q=document.createElement("button");Q.className="tools-pane-toggle hidden",Q.title="Graph Inspector",Q.innerHTML='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16"/><path d="M4 12h16"/><path d="M4 17h10"/></svg>';const V=document.createElement("div");V.className="tools-pane-content hidden",t.appendChild(Q),t.appendChild(V),Q.addEventListener("click",()=>{var r;l=!l,V.classList.toggle("hidden",l),Q.classList.toggle("active",!l),l||(r=e.onOpen)==null||r.call(e)});function T(){if(V.innerHTML="",!d)return;const r=document.createElement("div");if(r.className="tools-pane-summary",r.innerHTML=`<span>${d.nodeCount} nodes</span><span class="tools-pane-sep">·</span><span>${d.edgeCount} edges</span><span class="tools-pane-sep">·</span><span>${d.types.length} types</span>`,V.appendChild(r),s&&d.nodeCount>0){const k=Math.ceil(JSON.stringify(s).length/4),f=Math.round(k/d.nodeCount),v=Math.max(10,Math.round(f*.3)*Math.min(5,d.nodeCount)),M=k>v?Math.round((1-v/k)*100):0,P=document.createElement("div");P.className="tools-pane-token-card";const F=Math.min(100,M),L=document.createElement("div");L.className="token-card-label",L.textContent="Token Efficiency",P.appendChild(L);const m=document.createElement("div");m.className="token-card-stat",m.textContent=`~${k.toLocaleString()} tokens stored`,P.appendChild(m);const N=document.createElement("div");N.className="token-card-bar";const U=document.createElement("div");U.className="token-card-bar-fill",U.style.width=`${F}%`,N.appendChild(U),P.appendChild(N);const oe=document.createElement("div");oe.className="token-card-stat",oe.textContent=`A search returns ~${v} tokens instead of ~${k.toLocaleString()} (${M}% reduction)`,P.appendChild(oe),V.appendChild(P)}const u=document.createElement("div");u.className="tools-pane-tabs";const b=[{id:"types",label:"Types"},{id:"insights",label:"Insights"},{id:"controls",label:"Controls"}];for(const k of b){const f=document.createElement("button");f.className="tools-pane-tab",R===k.id&&f.classList.add("tools-pane-tab-active"),f.textContent=k.label,f.addEventListener("click",()=>{R=k.id,T()}),u.appendChild(f)}V.appendChild(u),ce()||ne(),$.length>0&&G(),R==="types"&&d.types.length>5?V.appendChild(ae("Filter types...",i,k=>{i=k,te()})):R==="insights"&&d.orphans.length+d.singletons.length+d.emptyNodes.length>5&&V.appendChild(ae("Filter issues...",a,f=>{a=f,te()}));const g=document.createElement("div");g.className="tools-pane-tab-content",V.appendChild(g),te()}function te(){const r=V.querySelector(".tools-pane-tab-content");r&&(r.innerHTML="",R==="types"?Z(r):R==="insights"?ye(r):R==="controls"&&Ce(r))}function G(){V.appendChild(W(`Walk Trail (${$.length})`,r=>{for(let b=0;b<$.length;b++){const g=$[b],k=b===$.length-1;if(g.edgeType){const m=document.createElement("div");m.className="walk-trail-edge",m.textContent=`↓ ${g.edgeType}`,r.appendChild(m)}const f=document.createElement("div");f.className="tools-pane-row tools-pane-clickable",k&&(f.style.fontWeight="600");const v=document.createElement("span");v.className="tools-pane-count",v.style.minWidth="18px",v.textContent=`${b+1}`;const M=document.createElement("span");M.className="tools-pane-dot",M.style.backgroundColor=Ne(g.type);const P=document.createElement("span");P.className="tools-pane-name",P.textContent=g.label;const F=document.createElement("span");F.className="tools-pane-count",F.textContent=g.type;const L=document.createElement("button");L.className="tools-pane-edit",L.style.opacity="1",L.textContent="×",L.title="Remove from trail",L.addEventListener("click",m=>{var N;m.stopPropagation(),(N=e.onWalkTrailRemove)==null||N.call(e,g.id)}),f.appendChild(v),f.appendChild(M),f.appendChild(P),f.appendChild(F),f.appendChild(L),f.addEventListener("click",()=>{e.onNavigateToNode(g.id)}),r.appendChild(f)}const u=document.createElement("div");if(u.className="tools-pane-export-row",e.onWalkIsolate){const b=document.createElement("button");b.className="tools-pane-export-btn",b.textContent="Isolate (I)",b.addEventListener("click",()=>e.onWalkIsolate()),u.appendChild(b)}if(e.onWalkSaveSnippet&&$.length>=2){const b=document.createElement("button");b.className="tools-pane-export-btn",b.textContent="Save snippet",b.addEventListener("click",()=>{gt("Save snippet","Name for this snippet").then(g=>{g&&e.onWalkSaveSnippet(g)})}),u.appendChild(b)}r.appendChild(u)}))}function ne(){if(!d||!s)return;const r=z();V.appendChild(W("Focused",u=>{for(const f of y.types){const v=d.types.find(N=>N.name===f);if(!v)continue;const M=document.createElement("div");M.className="tools-pane-row tools-pane-clickable";const P=document.createElement("span");P.className="tools-pane-dot",P.style.backgroundColor=Ne(v.name);const F=document.createElement("span");F.className="tools-pane-name",F.textContent=v.name;const L=document.createElement("span");L.className="tools-pane-count",L.textContent=`${v.count} nodes`;const m=document.createElement("button");m.className="tools-pane-edit tools-pane-focus-active",m.style.opacity="1",m.textContent="×",m.title=`Remove ${v.name} from focus`,M.appendChild(P),M.appendChild(F),M.appendChild(L),M.appendChild(m),m.addEventListener("click",N=>{N.stopPropagation(),y.types.delete(v.name),O(),T()}),u.appendChild(M)}for(const f of y.nodeIds){const v=s.nodes.find(U=>U.id===f);if(!v)continue;const M=mn(v.properties)??v.id,P=document.createElement("div");P.className="tools-pane-row tools-pane-clickable";const F=document.createElement("span");F.className="tools-pane-dot",F.style.backgroundColor=Ne(v.type);const L=document.createElement("span");L.className="tools-pane-name",L.textContent=M;const m=document.createElement("span");m.className="tools-pane-count",m.textContent=v.type;const N=document.createElement("button");N.className="tools-pane-edit tools-pane-focus-active",N.style.opacity="1",N.textContent="×",N.title=`Remove ${M} from focus`,P.appendChild(F),P.appendChild(L),P.appendChild(m),P.appendChild(N),P.addEventListener("click",U=>{U.target.closest(".tools-pane-edit")||e.onNavigateToNode(f)}),N.addEventListener("click",U=>{U.stopPropagation(),y.nodeIds.delete(f),O(),T()}),u.appendChild(P)}const b=document.createElement("div");b.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=`${r.length} total`;const k=document.createElement("span");k.className="tools-pane-badge",k.textContent="clear all",b.appendChild(g),b.appendChild(k),b.addEventListener("click",()=>{y.types.clear(),y.nodeIds.clear(),O(),T()}),u.appendChild(b)}))}function J(r){const u=document.createElement("div");u.className="tools-pane-row tools-pane-clickable",C===r.name&&u.classList.add("active");const b=document.createElement("span");b.className="tools-pane-dot",b.style.backgroundColor=Ne(r.name);const g=document.createElement("span");g.className="tools-pane-name",g.textContent=r.name;const k=document.createElement("span");k.className="tools-pane-count",k.textContent=String(r.count);const f=document.createElement("button");f.className="tools-pane-edit tools-pane-focus-toggle",y.types.has(r.name)&&f.classList.add("tools-pane-focus-active"),f.textContent="◎",f.title=y.types.has(r.name)?`Remove ${r.name} from focus`:`Add ${r.name} to focus`;const v=document.createElement("button");return v.className="tools-pane-edit",v.textContent="✎",v.title=`Rename all ${r.name} nodes`,u.appendChild(b),u.appendChild(g),u.appendChild(k),u.appendChild(f),u.appendChild(v),u.addEventListener("click",M=>{M.target.closest(".tools-pane-edit")||(C===r.name?(C=null,e.onFilterByType(null)):(C=r.name,e.onFilterByType(r.name)),T())}),f.addEventListener("click",M=>{M.stopPropagation(),y.types.has(r.name)?y.types.delete(r.name):y.types.add(r.name),O(),T()}),v.addEventListener("click",M=>{M.stopPropagation(),Y(u,r.name,P=>{P&&P!==r.name&&e.onRenameNodeType(r.name,P)})}),u}function ae(r,u,b){const g=document.createElement("input");return g.type="text",g.className="tools-pane-search",g.placeholder=r,g.value=u,g.addEventListener("input",()=>b(g.value)),g}function Z(r){if(!d)return;const u=i.toLowerCase();if(d.types.length){const g=d.types.filter(k=>!y.types.has(k.name)).filter(k=>!u||k.name.toLowerCase().includes(u));g.length>0&&r.appendChild(W("Node Types",k=>{for(const f of g)k.appendChild(J(f))}))}const b=d.edgeTypes.filter(g=>!u||g.name.toLowerCase().includes(u));b.length&&r.appendChild(W("Edge Types",g=>{for(const k of b){const f=document.createElement("div");f.className="tools-pane-row tools-pane-clickable";const v=document.createElement("span");v.className="tools-pane-name",v.textContent=k.name;const M=document.createElement("span");M.className="tools-pane-count",M.textContent=String(k.count);const P=document.createElement("button");P.className="tools-pane-edit",P.textContent="✎",P.title=`Rename all ${k.name} edges`,f.appendChild(v),f.appendChild(M),f.appendChild(P),P.addEventListener("click",F=>{F.stopPropagation(),Y(f,k.name,L=>{L&&L!==k.name&&e.onRenameEdgeType(k.name,L)})}),g.appendChild(f)}}))}function ye(r){if(!d)return;const u=a.toLowerCase(),b=d.starred.filter(L=>!u||L.label.toLowerCase().includes(u)||L.type.toLowerCase().includes(u));b.length&&r.appendChild(W("★ Starred",L=>{for(const U of b){const oe=document.createElement("div");oe.className="tools-pane-row tools-pane-clickable";const de=document.createElement("span");de.className="tools-pane-dot",de.style.backgroundColor=Ne(U.type);const n=document.createElement("span");n.className="tools-pane-name",n.textContent=U.label;const p=document.createElement("button");p.className="tools-pane-edit tools-pane-focus-toggle",j(U.id)&&p.classList.add("tools-pane-focus-active"),p.textContent="◎",p.title=j(U.id)?`Remove ${U.label} from focus`:`Add ${U.label} to focus`,oe.appendChild(de),oe.appendChild(n),oe.appendChild(p),oe.addEventListener("click",A=>{A.target.closest(".tools-pane-edit")||e.onNavigateToNode(U.id)}),p.addEventListener("click",A=>{A.stopPropagation(),y.nodeIds.has(U.id)?y.nodeIds.delete(U.id):y.nodeIds.add(U.id),O(),T()}),L.appendChild(oe)}const m=document.createElement("div");m.className="tools-pane-row tools-pane-actions";const N=document.createElement("button");if(N.className="tools-pane-action-btn",N.textContent="Focus all",N.title="Enter focus mode with all starred nodes",N.addEventListener("click",()=>{y.nodeIds.clear(),y.types.clear();for(const U of d.starred)y.nodeIds.add(U.id);O(),T()}),m.appendChild(N),e.onStarredSaveSnippet){const U=document.createElement("button");U.className="tools-pane-action-btn",U.textContent="Save as snippet",U.title="Save starred nodes as a reusable snippet",U.addEventListener("click",async()=>{const oe=await gt("Snippet name","starred");oe&&e.onStarredSaveSnippet(oe,d.starred.map(de=>de.id))}),m.appendChild(U)}L.appendChild(m)}));const g=d.mostConnected.filter(L=>!u||L.label.toLowerCase().includes(u)||L.type.toLowerCase().includes(u));g.length&&r.appendChild(W("Most Connected",L=>{for(const m of g){const 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=Ne(m.type);const oe=document.createElement("span");oe.className="tools-pane-name",oe.textContent=m.label;const de=document.createElement("span");de.className="tools-pane-count",de.textContent=`${m.connections}`;const n=document.createElement("button");n.className="tools-pane-edit tools-pane-focus-toggle",j(m.id)&&n.classList.add("tools-pane-focus-active"),n.textContent="◎",n.title=j(m.id)?`Remove ${m.label} from focus`:`Add ${m.label} to focus`,N.appendChild(U),N.appendChild(oe),N.appendChild(de),N.appendChild(n),N.addEventListener("click",p=>{p.target.closest(".tools-pane-edit")||e.onNavigateToNode(m.id)}),n.addEventListener("click",p=>{p.stopPropagation(),y.nodeIds.has(m.id)?y.nodeIds.delete(m.id):y.nodeIds.add(m.id),O(),T()}),L.appendChild(N)}}));const k=d.orphans.filter(L=>!u||L.label.toLowerCase().includes(u)||L.type.toLowerCase().includes(u)),f=d.singletons.filter(L=>!u||L.name.toLowerCase().includes(u)),v=d.emptyNodes.filter(L=>!u||L.label.toLowerCase().includes(u)||L.type.toLowerCase().includes(u)),M=k.length>0,P=f.length>0,F=v.length>0;if(!M&&!P&&!F){const L=document.createElement("div");L.className="tools-pane-empty-msg",L.textContent="No issues found",r.appendChild(L);return}M&&r.appendChild(W("Orphans",L=>{for(const m of k.slice(0,5)){const N=document.createElement("div");N.className="tools-pane-row tools-pane-clickable tools-pane-issue";const U=document.createElement("span");U.className="tools-pane-dot",U.style.backgroundColor=Ne(m.type);const oe=document.createElement("span");oe.className="tools-pane-name",oe.textContent=m.label;const de=document.createElement("span");de.className="tools-pane-badge",de.textContent="orphan";const n=document.createElement("button");n.className="tools-pane-edit tools-pane-focus-toggle",j(m.id)&&n.classList.add("tools-pane-focus-active"),n.textContent="◎",n.title=j(m.id)?`Remove ${m.label} from focus`:`Add ${m.label} to focus`,N.appendChild(U),N.appendChild(oe),N.appendChild(de),N.appendChild(n),N.addEventListener("click",p=>{p.target.closest(".tools-pane-edit")||e.onNavigateToNode(m.id)}),n.addEventListener("click",p=>{p.stopPropagation(),y.nodeIds.has(m.id)?y.nodeIds.delete(m.id):y.nodeIds.add(m.id),O(),T()}),L.appendChild(N)}if(k.length>5){const m=document.createElement("div");m.className="tools-pane-more",m.textContent=`+ ${k.length-5} more orphans`,L.appendChild(m)}})),P&&r.appendChild(W("Singletons",L=>{for(const m of f.slice(0,5)){const N=document.createElement("div");N.className="tools-pane-row tools-pane-issue";const U=document.createElement("span");U.className="tools-pane-dot",U.style.backgroundColor=Ne(m.name);const oe=document.createElement("span");oe.className="tools-pane-name",oe.textContent=m.name;const de=document.createElement("span");de.className="tools-pane-badge",de.textContent="1 node",N.appendChild(U),N.appendChild(oe),N.appendChild(de),L.appendChild(N)}})),F&&r.appendChild(W("Empty Nodes",L=>{for(const m of v.slice(0,5)){const N=document.createElement("div");N.className="tools-pane-row tools-pane-issue";const U=document.createElement("span");U.className="tools-pane-dot",U.style.backgroundColor=Ne(m.type);const oe=document.createElement("span");oe.className="tools-pane-name",oe.textContent=m.label;const de=document.createElement("span");de.className="tools-pane-badge",de.textContent="empty",N.appendChild(U),N.appendChild(oe),N.appendChild(de),L.appendChild(N)}if(d.emptyNodes.length>5){const m=document.createElement("div");m.className="tools-pane-more",m.textContent=`+ ${d.emptyNodes.length-5} more empty nodes`,L.appendChild(m)}}))}function Ce(r){r.appendChild(W("Display",u=>{const b=document.createElement("div");b.className="tools-pane-row tools-pane-clickable";const g=document.createElement("input");g.type="checkbox",g.checked=E,g.className="tools-pane-checkbox";const k=document.createElement("span");k.className="tools-pane-name",k.textContent="Edge labels",b.appendChild(g),b.appendChild(k),b.addEventListener("click",m=>{m.target!==g&&(g.checked=!g.checked),E=g.checked,e.onToggleEdgeLabels(E)}),u.appendChild(b);const f=document.createElement("div");f.className="tools-pane-row tools-pane-clickable";const v=document.createElement("input");v.type="checkbox",v.checked=c,v.className="tools-pane-checkbox";const M=document.createElement("span");M.className="tools-pane-name",M.textContent="Type regions",f.appendChild(v),f.appendChild(M),f.addEventListener("click",m=>{m.target!==v&&(v.checked=!v.checked),c=v.checked,e.onToggleTypeHulls(c)}),u.appendChild(f);const P=document.createElement("div");P.className="tools-pane-row tools-pane-clickable";const F=document.createElement("input");F.type="checkbox",F.checked=o,F.className="tools-pane-checkbox";const L=document.createElement("span");L.className="tools-pane-name",L.textContent="Minimap",P.appendChild(F),P.appendChild(L),P.addEventListener("click",m=>{m.target!==F&&(F.checked=!F.checked),o=F.checked,e.onToggleMinimap(o)}),u.appendChild(P)})),r.appendChild(W("Layout",u=>{u.appendChild(we("Clustering",0,1,.02,.08,b=>{e.onLayoutChange("clusterStrength",b)})),u.appendChild(we("Spacing",.5,20,.5,1.5,b=>{e.onLayoutChange("spacing",b)})),u.appendChild(we("Pan speed",20,200,10,60,b=>{e.onPanSpeedChange(b)}))})),r.appendChild(W("Export",u=>{const b=document.createElement("div");b.className="tools-pane-export-row";const g=document.createElement("button");g.className="tools-pane-export-btn",g.textContent="Export PNG",g.addEventListener("click",()=>e.onExport("png"));const k=document.createElement("button");k.className="tools-pane-export-btn",k.textContent="Export SVG",k.addEventListener("click",()=>e.onExport("svg")),b.appendChild(g),b.appendChild(k),u.appendChild(b)})),(e.onSnapshot||e.onRollback)&&r.appendChild(W("Versions",u=>{const b=document.createElement("div");b.className="tools-pane-export-row";const g=document.createElement("button");if(g.className="tools-pane-export-btn",g.textContent="Save snapshot",g.addEventListener("click",()=>{gt("Save snapshot","Label (optional)").then(k=>{var f;(f=e.onSnapshot)==null||f.call(e,k||void 0)})}),b.appendChild(g),u.appendChild(b),I.length>0)for(const k of I){const f=document.createElement("div");f.className="tools-pane-row";const v=document.createElement("span");v.className="tools-pane-name";const M=io(k.timestamp);v.textContent=k.label?`#${k.version} ${k.label}`:`#${k.version}`,v.title=`${M} — ${k.nodeCount} nodes, ${k.edgeCount} edges`;const P=document.createElement("span");P.className="tools-pane-count",P.textContent=M;const F=document.createElement("button");F.className="tools-pane-edit",F.style.opacity="1",F.textContent="↩",F.title="Restore this snapshot",F.addEventListener("click",()=>{hn("Restore snapshot",`Restore snapshot #${k.version}? Current state will be lost unless you save a snapshot first.`).then(L=>{var m;L&&((m=e.onRollback)==null||m.call(e,k.version))})}),f.appendChild(v),f.appendChild(P),f.appendChild(F),u.appendChild(f)}else{const k=document.createElement("div");k.className="tools-pane-empty-msg",k.textContent="No snapshots yet",u.appendChild(k)}}))}function we(r,u,b,g,k,f){const v=document.createElement("div");v.className="tools-pane-slider-row";const M=document.createElement("span");M.className="tools-pane-slider-label",M.textContent=r;const P=document.createElement("input");P.type="range",P.className="tools-pane-slider",P.min=String(u),P.max=String(b),P.step=String(g),P.value=String(k);const F=document.createElement("span");return F.className="tools-pane-slider-value",F.textContent=String(k),P.addEventListener("input",()=>{const L=parseFloat(P.value);F.textContent=L%1===0?String(L):L.toFixed(2),f(L)}),v.appendChild(M),v.appendChild(P),v.appendChild(F),v}function W(r,u){const b=document.createElement("div");b.className="tools-pane-section";const g=document.createElement("div");return g.className="tools-pane-heading",g.textContent=r,b.appendChild(g),u(b),b}function Y(r,u,b){const g=document.createElement("input");g.className="tools-pane-inline-input",g.value=u,g.type="text";const k=r.innerHTML;r.innerHTML="",r.classList.add("tools-pane-editing"),r.appendChild(g),g.focus(),g.select();function f(){const v=g.value.trim();r.classList.remove("tools-pane-editing"),v&&v!==u?b(v):r.innerHTML=k}g.addEventListener("keydown",v=>{v.key==="Enter"&&(v.preventDefault(),f()),v.key==="Escape"&&(r.innerHTML=k,r.classList.remove("tools-pane-editing"))}),g.addEventListener("blur",f)}function x(r){const u=new Map,b=new Map,g=new Map,k=new Set;for(const m of r.nodes)u.set(m.type,(u.get(m.type)??0)+1);for(const m of r.edges)b.set(m.type,(b.get(m.type)??0)+1),g.set(m.sourceId,(g.get(m.sourceId)??0)+1),g.set(m.targetId,(g.get(m.targetId)??0)+1),k.add(m.sourceId),k.add(m.targetId);const f=m=>mn(m.properties)??m.id,v=r.nodes.filter(m=>m.properties._starred===!0).map(m=>({id:m.id,label:f(m),type:m.type})),M=r.nodes.filter(m=>!k.has(m.id)).map(m=>({id:m.id,label:f(m),type:m.type})),P=[...u.entries()].filter(([,m])=>m===1).map(([m])=>({name:m})),F=r.nodes.filter(m=>Object.keys(m.properties).length===0).map(m=>({id:m.id,label:m.id,type:m.type})),L=r.nodes.map(m=>({id:m.id,label:f(m),type:m.type,connections:g.get(m.id)??0})).filter(m=>m.connections>0).sort((m,N)=>N.connections-m.connections).slice(0,5);return{nodeCount:r.nodes.length,edgeCount:r.edges.length,types:[...u.entries()].sort((m,N)=>N[1]-m[1]).map(([m,N])=>({name:m,count:N})),edgeTypes:[...b.entries()].sort((m,N)=>N[1]-m[1]).map(([m,N])=>({name:m,count:N})),starred:v,orphans:M,singletons:P,emptyNodes:F,mostConnected:L}}return{collapse(){l=!0,V.classList.add("hidden"),Q.classList.remove("active")},addToFocusSet(r){for(const u of r)y.nodeIds.add(u);O(),T()},clearFocusSet(){y.types.clear(),y.nodeIds.clear(),O(),T()},setData(r){s=r,C=null,y.types.clear(),y.nodeIds.clear(),s&&s.nodes.length>0?(d=x(s),Q.classList.remove("hidden"),T()):(d=null,Q.classList.add("hidden"),V.classList.add("hidden"))},setSnapshots(r){I=r,R==="controls"&&te()},setWalkTrail(r){$=r,T()}}}function io(t){const e=Date.now()-new Date(t).getTime(),s=Math.floor(e/6e4);if(s<1)return"just now";if(s<60)return`${s}m ago`;const d=Math.floor(s/60);return d<24?`${d}h ago`:`${Math.floor(d/24)}d ago`}function mn(t){for(const e of Object.values(t))if(typeof e=="string")return e;return null}function lo(t,e){const s=e.split("+"),d=s.pop(),l=s.map(o=>o.toLowerCase()),C=l.includes("ctrl")||l.includes("cmd")||l.includes("meta"),E=l.includes("shift"),c=l.includes("alt");return C!==(t.ctrlKey||t.metaKey)||!C&&(t.ctrlKey||t.metaKey)||E&&!t.shiftKey||c!==t.altKey?!1:d.toLowerCase()==="escape"?t.key==="Escape":d.toLowerCase()==="tab"?t.key==="Tab":l.length>0?t.key.toLowerCase()===d.toLowerCase():t.key===d}function ro(){return{search:"Focus search",searchAlt:"Focus search (alt)",undo:"Undo",redo:"Redo",help:"Toggle help",escape:"Exit focus / close panel",focus:"Focus on selected / exit focus",toggleEdges:"Toggle edges on/off",center:"Center view on graph",nextNode:"Next node in view",prevNode:"Previous node in view",nextConnection:"Next connection",prevConnection:"Previous connection",historyBack:"Node history back",historyForward:"Node history forward",hopsIncrease:"Increase hops",hopsDecrease:"Decrease hops",panLeft:"Pan left",panDown:"Pan down",panUp:"Pan up",panRight:"Pan right",panFastLeft:"Pan fast left",zoomOut:"Zoom out",zoomIn:"Zoom in",panFastRight:"Pan fast right",spacingDecrease:"Decrease spacing",spacingIncrease:"Increase spacing",clusteringDecrease:"Decrease clustering",clusteringIncrease:"Increase clustering",toggleSidebar:"Toggle sidebar",walkMode:"Toggle walk mode (in focus)",walkIsolate:"Isolate walk trail nodes"}}const po=[{key:"Click",description:"Select node"},{key:"Ctrl+Click",description:"Multi-select nodes"},{key:"Drag",description:"Pan canvas"},{key:"Scroll",description:"Zoom in/out"}],uo=["search","searchAlt","undo","redo","help","focus","toggleEdges","center","nextNode","prevNode","nextConnection","prevConnection","historyBack","historyForward","hopsIncrease","hopsDecrease","panLeft","panDown","panUp","panRight","panFastLeft","panFastRight","zoomIn","zoomOut","spacingDecrease","spacingIncrease","clusteringDecrease","clusteringIncrease","toggleSidebar","walkMode","walkIsolate","escape"];function mo(t){return t.split("+").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("+")}function ho(t,e){const s=ro(),d=document.createElement("div");d.className="shortcuts-overlay hidden";const l=document.createElement("div");l.className="shortcuts-modal";const C=document.createElement("h3");C.className="shortcuts-title",C.textContent="Keyboard Shortcuts";const E=document.createElement("div");E.className="shortcuts-list";for(const a of uo){const I=e[a];if(!I)continue;const $=document.createElement("div");$.className="shortcuts-row";const y=document.createElement("div");y.className="shortcuts-keys";const z=document.createElement("kbd");z.textContent=mo(I),y.appendChild(z);const j=document.createElement("span");j.className="shortcuts-desc",j.textContent=s[a],$.appendChild(y),$.appendChild(j),E.appendChild($)}for(const a of po){const I=document.createElement("div");I.className="shortcuts-row";const $=document.createElement("div");$.className="shortcuts-keys";const y=document.createElement("kbd");y.textContent=a.key,$.appendChild(y);const z=document.createElement("span");z.className="shortcuts-desc",z.textContent=a.description,I.appendChild($),I.appendChild(z),E.appendChild(I)}const c=document.createElement("button");c.className="shortcuts-close",c.textContent="×",l.appendChild(c),l.appendChild(C),l.appendChild(E),d.appendChild(l),t.appendChild(d);function o(){d.classList.remove("hidden")}function R(){d.classList.add("hidden")}function i(){d.classList.toggle("hidden")}return c.addEventListener("click",R),d.addEventListener("click",a=>{a.target===d&&R()}),{show:o,hide:R,toggle:i}}function fo(t){const e=document.createElement("div");return e.className="empty-state",e.innerHTML=`
|
|
6
|
+
<div class="empty-state-bg">
|
|
7
|
+
<div class="empty-state-circle c1"></div>
|
|
8
|
+
<div class="empty-state-circle c2"></div>
|
|
9
|
+
<div class="empty-state-circle c3"></div>
|
|
10
|
+
<div class="empty-state-circle c4"></div>
|
|
11
|
+
<div class="empty-state-circle c5"></div>
|
|
12
|
+
<svg class="empty-state-lines" viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
|
|
13
|
+
<line x1="80" y1="60" x2="220" y2="140" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
|
|
14
|
+
<line x1="220" y1="140" x2="320" y2="80" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
|
|
15
|
+
<line x1="220" y1="140" x2="160" y2="240" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
|
|
16
|
+
<line x1="160" y1="240" x2="300" y2="220" stroke="currentColor" stroke-width="0.5" opacity="0.15"/>
|
|
17
|
+
</svg>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="empty-state-content">
|
|
20
|
+
<div class="empty-state-icon">
|
|
21
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
22
|
+
<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"/>
|
|
23
|
+
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
|
|
24
|
+
<line x1="12" y1="22.08" x2="12" y2="12"/>
|
|
25
|
+
</svg>
|
|
26
|
+
</div>
|
|
27
|
+
<h2 class="empty-state-title">No learning graphs yet</h2>
|
|
28
|
+
<p class="empty-state-desc">Connect Backpack to Claude, then start a conversation. Claude will build your first learning graph automatically.</p>
|
|
29
|
+
<div class="empty-state-setup">
|
|
30
|
+
<div class="empty-state-label">Add Backpack to Claude Code:</div>
|
|
31
|
+
<code class="empty-state-code">claude mcp add backpack-local -s user -- npx backpack-ontology@latest</code>
|
|
32
|
+
</div>
|
|
33
|
+
<p class="empty-state-hint">Press <kbd>?</kbd> for keyboard shortcuts</p>
|
|
34
|
+
</div>
|
|
35
|
+
`,t.appendChild(e),{show(){e.classList.remove("hidden")},hide(){e.classList.add("hidden")}}}const go=30;function yo(){let t=[],e=[];return{push(s){t.push(JSON.stringify(s)),t.length>go&&t.shift(),e=[]},undo(s){return t.length===0?null:(e.push(JSON.stringify(s)),JSON.parse(t.pop()))},redo(s){return e.length===0?null:(t.push(JSON.stringify(s)),JSON.parse(e.pop()))},canUndo(){return t.length>0},canRedo(){return e.length>0},clear(){t=[],e=[]}}}function Co(t,e){let s=null;function d(E,c,o,R,i){l(),s=document.createElement("div"),s.className="context-menu",s.style.left=`${R}px`,s.style.top=`${i}px`;const a=[{label:o?"★ Unstar":"☆ Star",action:()=>e.onStar(E),premium:!1},{label:"◎ Focus on node",action:()=>e.onFocusNode(E),premium:!1},{label:"⑂ Explore in branch",action:()=>e.onExploreInBranch(E),premium:!1},{label:"⎘ Copy ID",action:()=>e.onCopyId(E),premium:!1}];e.onExpand&&a.push({label:"⊕ Expand node",action:()=>e.onExpand(E),premium:!0}),e.onExplainPath&&a.push({label:"↔ Explain path to…",action:()=>e.onExplainPath(E),premium:!0}),e.onEnrich&&a.push({label:"≡ Enrich from web",action:()=>e.onEnrich(E),premium:!0});let I=!1;for(const y of a){if(!I&&y.premium){const j=document.createElement("div");j.className="context-menu-separator",s.appendChild(j),I=!0}const z=document.createElement("div");z.className="context-menu-item",z.textContent=y.label,z.addEventListener("click",()=>{y.action(),l()}),s.appendChild(z)}t.appendChild(s);const $=s.getBoundingClientRect();$.right>window.innerWidth&&(s.style.left=`${R-$.width}px`),$.bottom>window.innerHeight&&(s.style.top=`${i-$.height}px`),setTimeout(()=>document.addEventListener("click",l),0),document.addEventListener("keydown",C)}function l(){s&&(s.remove(),s=null),document.removeEventListener("click",l),document.removeEventListener("keydown",C)}function C(E){E.key==="Escape"&&l()}return{show:d,hide:l}}const vo={search:"/",searchAlt:"ctrl+k",undo:"ctrl+z",redo:"ctrl+shift+z",help:"?",escape:"Escape",focus:"f",toggleEdges:"e",center:"c",nextNode:".",prevNode:",",nextConnection:">",prevConnection:"<",historyBack:"(",historyForward:")",hopsIncrease:"=",hopsDecrease:"-",panLeft:"h",panDown:"j",panUp:"k",panRight:"l",panFastLeft:"H",zoomOut:"J",zoomIn:"K",panFastRight:"L",spacingDecrease:"[",spacingIncrease:"]",clusteringDecrease:"{",clusteringIncrease:"}",toggleSidebar:"Tab",walkMode:"w",walkIsolate:"i"},xo={host:"127.0.0.1",port:5173},bo={edges:!0,edgeLabels:!0,typeHulls:!0,minimap:!0,theme:"system"},Eo={spacing:1.5,clustering:.08},wo={panSpeed:60,panFastMultiplier:3,zoomFactor:1.3,zoomMin:.05,zoomMax:10,panAnimationMs:300},ko={hideBadges:.4,hideLabels:.25,hideEdgeLabels:.35,smallNodes:.2,hideArrows:.15},No={pulseSpeed:.02},So={maxSearchResults:8,maxQualityItems:5,maxMostConnected:5,searchDebounceMs:150},Lo={keybindings:vo,server:xo,display:bo,layout:Eo,navigation:wo,lod:ko,walk:No,limits:So};let re="",S=null,It=new Set,Mt=!1;async function Io(){const t=document.getElementById("canvas-container"),e={...Lo};try{const n=await fetch("/api/config");if(n.ok){const p=await n.json();Object.assign(e.keybindings,p.keybindings??{}),Object.assign(e.display,p.display??{}),Object.assign(e.layout,p.layout??{}),Object.assign(e.navigation,p.navigation??{}),Object.assign(e.lod,p.lod??{}),Object.assign(e.limits,p.limits??{})}}catch{}const s=e.keybindings,d=window.matchMedia("(prefers-color-scheme: dark)"),l=e.display.theme==="system"?d.matches?"dark":"light":e.display.theme,E=localStorage.getItem("backpack-theme")??l;document.documentElement.setAttribute("data-theme",E);const c=document.createElement("button");c.className="theme-toggle",c.textContent=E==="light"?"☾":"☼",c.title="Toggle light/dark mode",c.addEventListener("click",()=>{const p=document.documentElement.getAttribute("data-theme")==="light"?"dark":"light";document.documentElement.setAttribute("data-theme",p),localStorage.setItem("backpack-theme",p),c.textContent=p==="light"?"☾":"☼"}),t.appendChild(c);const o=yo();async function R(){if(!re||!S)return;S.metadata.updatedAt=new Date().toISOString(),await wt(re,S),a.loadGraph(S),ne.setLearningGraphData(S),J.setData(S);const n=await at();x.setSummaries(n)}async function i(n){S=n,await wt(re,S),a.loadGraph(S),ne.setLearningGraphData(S),J.setData(S);const p=await at();x.setSummaries(p)}let a;const I=no(t,{onUpdateNode(n,p){if(!S)return;o.push(S);const A=S.nodes.find(_=>_.id===n);A&&(A.properties={...A.properties,...p},A.updatedAt=new Date().toISOString(),R().then(()=>I.show([n],S)))},onChangeNodeType(n,p){if(!S)return;o.push(S);const A=S.nodes.find(_=>_.id===n);A&&(A.type=p,A.updatedAt=new Date().toISOString(),R().then(()=>I.show([n],S)))},onDeleteNode(n){S&&(o.push(S),S.nodes=S.nodes.filter(p=>p.id!==n),S.edges=S.edges.filter(p=>p.sourceId!==n&&p.targetId!==n),R())},onDeleteEdge(n){var A;if(!S)return;o.push(S);const p=(A=S.edges.find(_=>_.id===n))==null?void 0:A.sourceId;S.edges=S.edges.filter(_=>_.id!==n),R().then(()=>{p&&S&&I.show([p],S)})},onAddProperty(n,p,A){if(!S)return;o.push(S);const _=S.nodes.find(le=>le.id===n);_&&(_.properties[p]=A,_.updatedAt=new Date().toISOString(),R().then(()=>I.show([n],S)))}},n=>{a.panToNode(n)},n=>{J.addToFocusSet(n)}),$=window.matchMedia("(max-width: 768px)");let y=[],z=e.display.edges,j=e.navigation.panSpeed,ce=-1,O=null;function Q(n){O&&O.remove(),O=document.createElement("div"),O.className="focus-indicator";const p=document.createElement("span");p.className="focus-indicator-label",p.textContent=`Focused: ${n.totalNodes} nodes`;const A=document.createElement("span");A.className="focus-indicator-hops",A.textContent=`${n.hops}`;const _=document.createElement("button");_.className="focus-indicator-btn",_.textContent="−",_.title="Fewer hops",_.disabled=n.hops===0,_.addEventListener("click",()=>{a.enterFocus(n.seedNodeIds,Math.max(0,n.hops-1))});const le=document.createElement("button");le.className="focus-indicator-btn",le.textContent="+",le.title="More hops",le.disabled=!1,le.addEventListener("click",()=>{a.enterFocus(n.seedNodeIds,n.hops+1)});const ue=document.createElement("button");ue.className="focus-indicator-btn focus-indicator-exit",ue.textContent="×",ue.title="Exit focus (Esc)",ue.addEventListener("click",()=>J.clearFocusSet());const ie=document.createElement("button");ie.className="walk-indicator",a.getWalkMode()&&ie.classList.add("active"),ie.textContent="Walk",ie.title="Toggle walk mode (W) — click nodes to traverse",ie.addEventListener("click",()=>{a.setWalkMode(!a.getWalkMode()),ie.classList.toggle("active",a.getWalkMode())}),O.appendChild(p),O.appendChild(_),O.appendChild(A),O.appendChild(le),O.appendChild(ie),O.appendChild(ue)}function V(){O&&(O.remove(),O=null)}const T=document.createElement("div");T.className="path-bar hidden",t.appendChild(T);function te(n){if(T.innerHTML="",!S)return;for(let A=0;A<n.nodeIds.length;A++){const _=n.nodeIds[A],le=S.nodes.find(fe=>fe.id===_);if(!le)continue;const ue=Object.values(le.properties).find(fe=>typeof fe=="string")??le.id;if(A>0){const fe=n.edgeIds[A-1],Le=S.edges.find(He=>He.id===fe),xe=document.createElement("span");xe.className="path-bar-edge",xe.textContent=Le?`→ ${Le.type} →`:"→",T.appendChild(xe)}const ie=document.createElement("span");ie.className="path-bar-node",ie.textContent=ue,ie.addEventListener("click",()=>a.panToNode(_)),T.appendChild(ie)}const p=document.createElement("button");p.className="path-bar-close",p.textContent="×",p.addEventListener("click",G),T.appendChild(p),T.classList.remove("hidden")}function G(){T.classList.add("hidden"),T.innerHTML="",a.clearHighlightedPath()}a=eo(t,n=>{if(y=n??[],!a.getWalkMode())if(n&&n.length===2){const p=a.findPath(n[0],n[1]);p&&p.nodeIds.length>0?(a.setHighlightedPath(p.nodeIds,p.edgeIds),te(p)):G()}else G();n&&n.length>0&&S?(I.show(n,S),$.matches&&J.collapse(),P(re,n)):(I.hide(),re&&P(re))},n=>{if(n){Q(n);const p=t.querySelector(".canvas-top-left");p&&O&&p.appendChild(O),P(re,n.seedNodeIds),I.setFocusDisabled(n.hops===0),u()}else V(),I.setFocusDisabled(!1),re&&P(re),u()},{lod:e.lod,navigation:e.navigation,walk:e.walk});const ne=ao(t,{maxResults:e.limits.maxSearchResults,debounceMs:e.limits.searchDebounceMs}),J=co(t,{onFilterByType(n){if(S)if(n===null)a.setFilteredNodeIds(null);else{const p=new Set(((S==null?void 0:S.nodes)??[]).filter(A=>A.type===n).map(A=>A.id));a.setFilteredNodeIds(p)}},onNavigateToNode(n){a.panToNode(n),S&&I.show([n],S)},onWalkTrailRemove(n){a.removeFromWalkTrail(n),u()},onWalkIsolate(){if(!S)return;const n=a.getWalkTrail();n.length!==0&&a.enterFocus(n,0)},async onWalkSaveSnippet(n){if(!re||!S)return;const p=a.getWalkTrail();if(p.length<2)return;const A=new Set(p),_=S.edges.filter(le=>A.has(le.sourceId)&&A.has(le.targetId)).map(le=>le.id);await Vt(re,n,p,_),await k(re)},async onStarredSaveSnippet(n,p){if(!re||!S)return;const A=new Set(p),_=S.edges.filter(le=>A.has(le.sourceId)&&A.has(le.targetId)).map(le=>le.id);await Vt(re,n,p,_),await k(re)},onFocusChange(n){n&&n.length>0?a.enterFocus(n,0):a.isFocused()&&a.exitFocus()},onRenameNodeType(n,p){if(S){o.push(S);for(const A of S.nodes)A.type===n&&(A.type=p,A.updatedAt=new Date().toISOString());R()}},onRenameEdgeType(n,p){if(S){o.push(S);for(const A of S.edges)A.type===n&&(A.type=p);R()}},onToggleEdgeLabels(n){a.setEdgeLabels(n)},onToggleTypeHulls(n){a.setTypeHulls(n)},onToggleMinimap(n){a.setMinimap(n)},onLayoutChange(n,p){Qe({[n]:p}),a.reheat()},onPanSpeedChange(n){j=n},onExport(n){const p=a.exportImage(n);if(!p)return;const A=document.createElement("a");A.download=`${re||"graph"}.${n}`,A.href=p,A.click()},onSnapshot:async n=>{re&&(await Tn(re,n),await g(re))},onRollback:async n=>{re&&(await An(re,n),S=await ht(re),a.loadGraph(S),ne.setLearningGraphData(S),J.setData(S),await g(re))},onOpen(){$.matches&&I.hide()}}),ae=document.createElement("div");ae.className="canvas-top-bar";const Z=document.createElement("div");Z.className="canvas-top-left";const ye=document.createElement("div");ye.className="canvas-top-center";const Ce=document.createElement("div");Ce.className="canvas-top-right";const we=t.querySelector(".tools-pane-toggle");we&&Z.appendChild(we);const W=t.querySelector(".search-overlay");W&&ye.appendChild(W);const Y=t.querySelector(".zoom-controls");Y&&Ce.appendChild(Y),Ce.appendChild(c),ae.appendChild(Z),ae.appendChild(ye),ae.appendChild(Ce),t.appendChild(ae),ne.onFilterChange(n=>{a.setFilteredNodeIds(n)}),ne.onNodeSelect(n=>{a.isFocused()&&J.clearFocusSet(),a.panToNode(n),S&&I.show([n],S)});const x=$n(document.getElementById("sidebar"),{onSelect:n=>L(n),onRename:async(n,p)=>{await Sn(n,p),re===n&&(re=p);const A=await at();x.setSummaries(A),x.setActive(re),re===p&&(S=await ht(p),a.loadGraph(S),ne.setLearningGraphData(S),J.setData(S))},onBranchSwitch:async(n,p)=>{await Gt(n,p),await b(n),S=await ht(n),a.loadGraph(S),ne.setLearningGraphData(S),J.setData(S),await g(n)},onBranchCreate:async(n,p)=>{await _t(n,p),await b(n)},onBranchDelete:async(n,p)=>{await In(n,p),await b(n)},onSnippetLoad:async(n,p)=>{var _;const A=await Pn(n,p);((_=A==null?void 0:A.nodeIds)==null?void 0:_.length)>0&&a.enterFocus(A.nodeIds,0)},onSnippetDelete:async(n,p)=>{await Rn(n,p),await k(n)},onBackpackSwitch:async n=>{await fetch("/api/backpacks/switch",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n})}),await r()},onBackpackRegister:async(n,p)=>{await fetch("/api/backpacks",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:n,activate:p})}),await r()}});async function r(){try{const p=await(await fetch("/api/backpacks")).json();x.setBackpacks(p)}catch{}try{const n=await at();x.setSummaries(n),re&&!n.some(p=>p.name===re)&&(re="",S=null,a.loadGraph({metadata:{name:"",description:"",createdAt:"",updatedAt:""},nodes:[],edges:[]}))}catch{}}function u(){const n=a.getWalkTrail();if(!S||n.length===0){J.setWalkTrail([]),G();return}const p=[],A=n.map((_,le)=>{const ue=S.nodes.find(fe=>fe.id===_);let ie;if(le>0){const fe=n[le-1],Le=S.edges.find(xe=>xe.sourceId===fe&&xe.targetId===_||xe.targetId===fe&&xe.sourceId===_);ie=Le==null?void 0:Le.type,Le&&p.push(Le.id)}return{id:_,label:ue?Object.values(ue.properties).find(fe=>typeof fe=="string")??ue.id:_,type:(ue==null?void 0:ue.type)??"?",edgeType:ie}});J.setWalkTrail(A),n.length>=2?(a.setHighlightedPath(n,p),te({nodeIds:n,edgeIds:p})):G()}async function b(n){const p=await Ln(n),A=p.find(_=>_.active);A&&x.setActiveBranch(n,A.name,p)}async function g(n){const p=await Mn(n);J.setSnapshots(p)}async function k(n){const p=await Bn(n);x.setSnippets(n,p)}Z.insertBefore(x.expandBtn,Z.firstChild);const f=ho(t,s),v=fo(t),M=Co(t,{onStar(n){if(!S)return;const p=S.nodes.find(_=>_.id===n);if(!p)return;const A=p.properties._starred===!0;p.properties._starred=!A,wt(re,S),a.loadGraph(S)},onFocusNode(n){J.addToFocusSet([n])},onExploreInBranch(n){if(re){const p=`explore-${n.slice(0,8)}`;_t(re,p).then(()=>{Gt(re,p).then(()=>{a.enterFocus([n],1)})})}},onCopyId(n){navigator.clipboard.writeText(n)}});t.addEventListener("contextmenu",n=>{n.preventDefault();const p=t.querySelector("canvas");if(!p||!S)return;const A=p.getBoundingClientRect(),_=n.clientX-A.left,le=n.clientY-A.top,ue=a.nodeAtScreen(_,le);if(!ue)return;const ie=S.nodes.find(xe=>xe.id===ue.id);if(!ie)return;const fe=Object.values(ie.properties).find(xe=>typeof xe=="string")??ie.id,Le=ie.properties._starred===!0;M.show(ie.id,fe,Le,n.clientX-A.left,n.clientY-A.top)}),e.display.edges||a.setEdges(!1),e.display.edgeLabels||a.setEdgeLabels(!1),e.display.typeHulls||a.setTypeHulls(!1),e.display.minimap||a.setMinimap(!1);function P(n,p){const A=[];p!=null&&p.length&&A.push("node="+p.map(encodeURIComponent).join(","));const _=a.getFocusInfo();_&&(A.push("focus="+_.seedNodeIds.map(encodeURIComponent).join(",")),A.push("hops="+_.hops));const le="#"+encodeURIComponent(n)+(A.length?"?"+A.join("&"):"");history.replaceState(null,"",le)}function F(){const n=window.location.hash.slice(1);if(!n)return{graph:null,nodes:[],focus:[],hops:1};const[p,A]=n.split("?"),_=p?decodeURIComponent(p):null;let le=[],ue=[],ie=1;if(A){const fe=new URLSearchParams(A),Le=fe.get("node");Le&&(le=Le.split(",").map(decodeURIComponent));const xe=fe.get("focus");xe&&(ue=xe.split(",").map(decodeURIComponent));const He=fe.get("hops");He&&(ie=Math.max(0,parseInt(He,10)||1))}return{graph:_,nodes:le,focus:ue,hops:ie}}async function L(n,p,A,_){re=n,Mt=It.has(n),x.setActive(n),I.hide(),V(),ne.clear(),o.clear(),S=Mt?await Nn(n):await ht(n);const le=zn(S.nodes.length);if(Qe({spacing:Math.max(e.layout.spacing,le.spacing),clusterStrength:Math.max(e.layout.clustering,le.clusterStrength)}),a.loadGraph(S),ne.setLearningGraphData(S),J.setData(S),v.hide(),P(n),Mt||(await b(n),await g(n),await k(n)),A!=null&&A.length&&S){const ue=A.filter(ie=>S.nodes.some(fe=>fe.id===ie));if(ue.length){setTimeout(()=>{a.enterFocus(ue,_??1)},500);return}}if(p!=null&&p.length&&S){const ue=p.filter(ie=>S.nodes.some(fe=>fe.id===ie));ue.length&&setTimeout(()=>{a.panToNodes(ue),S&&I.show(ue,S),P(n,ue)},500)}}try{const p=await(await fetch("/api/backpacks")).json();x.setBackpacks(p)}catch{}fetch("/api/version-check").then(n=>n.json()).then(n=>{n.stale&&n.latest&&x.setStaleVersionBanner(n.current,n.latest)}).catch(()=>{});const[m,N]=await Promise.all([at(),kn().catch(()=>[])]);x.setSummaries(m),x.setRemotes(N),It=new Set(N.map(n=>n.name));const U=F(),oe=U.graph&&m.some(n=>n.name===U.graph)||U.graph&&It.has(U.graph)?U.graph:m.length>0?m[0].name:N.length>0?N[0].name:null;oe?await L(oe,U.nodes.length?U.nodes:void 0,U.focus.length?U.focus:void 0,U.hops):v.show();const de={search(){ne.focus()},searchAlt(){ne.focus()},undo(){if(S){const n=o.undo(S);n&&i(n)}},redo(){if(S){const n=o.redo(S);n&&i(n)}},focus(){a.isFocused()?J.clearFocusSet():y.length>0&&J.addToFocusSet(y)},hopsDecrease(){const n=a.getFocusInfo();n&&n.hops>0&&a.enterFocus(n.seedNodeIds,n.hops-1)},hopsIncrease(){const n=a.getFocusInfo();n&&a.enterFocus(n.seedNodeIds,n.hops+1)},nextNode(){const n=a.getNodeIds();n.length>0&&(ce=(ce+1)%n.length,a.panToNode(n[ce]),S&&I.show([n[ce]],S))},prevNode(){const n=a.getNodeIds();n.length>0&&(ce=ce<=0?n.length-1:ce-1,a.panToNode(n[ce]),S&&I.show([n[ce]],S))},nextConnection(){const n=I.cycleConnection(1);n&&a.panToNode(n)},prevConnection(){const n=I.cycleConnection(-1);n&&a.panToNode(n)},historyBack(){I.goBack()},historyForward(){I.goForward()},center(){a.centerView()},toggleEdges(){z=!z,a.setEdges(z)},panLeft(){a.panBy(-j,0)},panDown(){a.panBy(0,j)},panUp(){a.panBy(0,-j)},panRight(){a.panBy(j,0)},panFastLeft(){a.panBy(-j*e.navigation.panFastMultiplier,0)},zoomOut(){a.zoomBy(1/e.navigation.zoomFactor)},zoomIn(){a.zoomBy(e.navigation.zoomFactor)},panFastRight(){a.panBy(j*e.navigation.panFastMultiplier,0)},spacingDecrease(){const n=rt();Qe({spacing:Math.max(.5,n.spacing-.5)}),a.reheat()},spacingIncrease(){const n=rt();Qe({spacing:Math.min(20,n.spacing+.5)}),a.reheat()},clusteringDecrease(){const n=rt();Qe({clusterStrength:Math.max(0,n.clusterStrength-.03)}),a.reheat()},clusteringIncrease(){const n=rt();Qe({clusterStrength:Math.min(1,n.clusterStrength+.03)}),a.reheat()},help(){f.toggle()},toggleSidebar(){x.toggle()},walkIsolate(){if(!S)return;const n=a.getWalkTrail();n.length!==0&&a.enterFocus(n,0)},walkMode(){!a.isFocused()&&y.length>0&&J.addToFocusSet(y),a.setWalkMode(!a.getWalkMode());const n=t.querySelector(".walk-indicator");n&&n.classList.toggle("active",a.getWalkMode()),u()},escape(){a.isFocused()?J.clearFocusSet():f.hide()}};document.addEventListener("keydown",n=>{var p;if(!(n.target instanceof HTMLInputElement||n.target instanceof HTMLTextAreaElement)){for(const[A,_]of Object.entries(s))if(lo(n,_)){(A==="search"||A==="searchAlt"||A==="undo"||A==="redo"||A==="toggleSidebar")&&n.preventDefault(),(p=de[A])==null||p.call(de);return}}}),window.addEventListener("hashchange",()=>{const n=F();if(n.graph&&n.graph!==re)L(n.graph,n.nodes.length?n.nodes:void 0,n.focus.length?n.focus:void 0,n.hops);else if(n.graph&&n.focus.length&&S)a.enterFocus(n.focus,n.hops);else if(n.graph&&n.nodes.length&&S){a.isFocused()&&a.exitFocus();const p=n.nodes.filter(A=>S.nodes.some(_=>_.id===A));p.length&&(a.panToNodes(p),I.show(p,S))}})}Io();
|
package/dist/app/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Backpack Viewer</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-CKYlU1zT.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B3z5bBGl.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="app">
|