@swarmvaultai/viewer 0.1.28 → 0.1.30

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.
@@ -0,0 +1 @@
1
+ :root{--c-bg-base: #020617;--c-bg-surface: #0c1222;--c-bg-elevated: rgba(15, 23, 42, .88);--c-bg-inset: rgba(2, 6, 23, .82);--c-bg-input: #070d1a;--c-border: rgba(148, 163, 184, .1);--c-border-subtle: rgba(148, 163, 184, .06);--c-border-focus: rgba(125, 211, 252, .28);--c-border-danger: rgba(248, 113, 113, .24);--c-border-warning: rgba(251, 191, 36, .2);--c-text-primary: #e2e8f0;--c-text-secondary: #94a3b8;--c-text-muted: #64748b;--c-text-accent: #7dd3fc;--c-text-error: #f87171;--c-text-warning: #fbbf24;--c-accent-bg: rgba(14, 165, 233, .08);--c-danger-bg: rgba(127, 29, 29, .18);--font-sans: "Inter", "Avenir Next", "Segoe UI", system-ui, sans-serif;--font-mono: "IBM Plex Mono", "SF Mono", "Fira Code", monospace;--text-2xs: .6875rem;--text-xs: .75rem;--text-sm: .8125rem;--text-base: .9375rem;--text-lg: 1.0625rem;--text-xl: 1.25rem;--sp-1: 4px;--sp-2: 8px;--sp-3: 12px;--sp-4: 16px;--sp-5: 20px;--sp-6: 24px;--radius-sm: 4px;--radius-md: 6px;--radius-lg: 10px;--sidebar-width: 220px;--rail-width: 400px;--bar-height: 42px;color-scheme:dark;font-family:var(--font-sans);background:var(--c-bg-base);color:var(--c-text-primary)}*,*:before,*:after{box-sizing:border-box}body{margin:0;height:100vh;overflow:hidden;font-size:var(--text-sm);line-height:1.5;-webkit-font-smoothing:antialiased}#root{height:100vh;overflow:hidden}.app-shell{display:grid;grid-template-columns:var(--sidebar-width) minmax(0,1fr) var(--rail-width);grid-template-rows:var(--bar-height) minmax(0,1fr);grid-template-areas:"bar bar bar" "sidebar center rail";height:100vh;overflow:hidden}.app-bar{grid-area:bar;display:flex;align-items:center;gap:var(--sp-3);padding:0 var(--sp-5);background:var(--c-bg-surface);border-bottom:1px solid var(--c-border);z-index:10}.app-bar-title{font-size:var(--text-sm);font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--c-text-accent)}.app-bar-subtitle{font-size:var(--text-xs);color:var(--c-text-muted);letter-spacing:.04em}.sidebar{grid-area:sidebar;overflow-y:auto;padding:var(--sp-3);background:var(--c-bg-surface);border-right:1px solid var(--c-border);display:flex;flex-direction:column;gap:var(--sp-3);scrollbar-width:thin;scrollbar-color:var(--c-text-muted) transparent}.sidebar-section{display:flex;flex-direction:column;gap:var(--sp-2)}.sidebar-heading{font-size:var(--text-2xs);font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--c-text-muted);padding:var(--sp-1) 0;border-bottom:1px solid var(--c-border-subtle);margin-bottom:var(--sp-1)}.sidebar-section-toggle{all:unset;display:flex;align-items:center;gap:var(--sp-2);width:100%;cursor:pointer;font-size:var(--text-2xs);font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--c-text-muted);padding:var(--sp-1) 0;border-bottom:1px solid var(--c-border-subtle);margin-bottom:var(--sp-1);transition:color .15s}.sidebar-section-toggle:hover{color:var(--c-text-secondary)}.sidebar-section-toggle:before{content:"▸";font-size:.6em;transition:transform .2s ease;display:inline-block}.sidebar-section-toggle.is-expanded:before{transform:rotate(90deg)}.sidebar-section-toggle .filter-badge{margin-left:auto;font-family:var(--font-mono);font-size:var(--text-2xs);color:var(--c-text-accent);font-weight:500}.sidebar-section-body{display:grid;gap:var(--sp-2);max-height:0;overflow:hidden;transition:max-height .25s ease,opacity .2s ease;opacity:0}.sidebar-section-body.is-expanded{max-height:600px;opacity:1}.filter-group{display:flex;flex-direction:column;gap:3px}.filter-label{font-size:var(--text-2xs);text-transform:uppercase;letter-spacing:.06em;color:var(--c-text-muted)}.center-area{grid-area:center;display:grid;grid-template-rows:auto minmax(0,1fr) auto;overflow:hidden}.stats-strip{display:flex;gap:var(--sp-2);padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--c-border-subtle);background:var(--c-bg-surface);align-items:center}.stats-group{display:flex;gap:var(--sp-3);align-items:baseline}.stats-divider{width:1px;height:14px;background:var(--c-border);align-self:center;flex-shrink:0}.stat{display:flex;align-items:baseline;gap:var(--sp-1)}.stat-label{font-size:var(--text-2xs);color:var(--c-text-muted);text-transform:uppercase;letter-spacing:.06em}.stat-value{font-family:var(--font-mono);font-size:var(--text-sm);color:var(--c-text-primary);font-weight:500}.canvas{min-height:0;background:radial-gradient(ellipse at 50% 40%,rgba(14,165,233,.025),transparent 65%),radial-gradient(circle at 1px 1px,rgba(148,163,184,.045) 1px,transparent 0),var(--c-bg-base);background-size:100% 100%,28px 28px,100% 100%}.canvas-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-3)}.loading-text{font-size:var(--text-base);color:var(--c-text-muted);letter-spacing:.06em;font-weight:400;animation:pulse-fade 2s ease-in-out infinite}@keyframes pulse-fade{0%,to{opacity:.35}50%{opacity:.85}}.canvas-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-2);color:var(--c-text-muted)}.canvas-empty-icon{font-size:2rem;opacity:.4}.report-tabs{border-top:1px solid var(--c-border);background:var(--c-bg-surface);max-height:360px;overflow:hidden}.report-tabs .tabs{border:none;border-radius:0;background:transparent}.report-tabs .tab-panel{max-height:310px;overflow-y:auto}.report-tabs-empty{padding:var(--sp-3);text-align:center;color:var(--c-text-muted);font-size:var(--text-sm)}.report-stats{display:flex;gap:var(--sp-4);font-size:var(--text-sm);color:var(--c-text-secondary)}.report-stats strong{font-family:var(--font-mono);color:var(--c-text-primary);margin-left:var(--sp-1)}.surprise-row{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}.detail-rail{grid-area:rail;overflow-y:auto;padding:var(--sp-3);background:var(--c-bg-surface);border-left:1px solid var(--c-border);display:flex;flex-direction:column;gap:var(--sp-3);scrollbar-width:thin;scrollbar-color:var(--c-text-muted) transparent}.tabs{border-radius:var(--radius-lg);border:1px solid var(--c-border);background:var(--c-bg-elevated);overflow:hidden}.tab-bar{display:flex;border-bottom:1px solid var(--c-border);padding:0 var(--sp-1);gap:0}.tab-btn{all:unset;padding:var(--sp-2) var(--sp-3);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.06em;color:var(--c-text-muted);cursor:pointer;border-bottom:2px solid transparent;transition:color .12s,border-color .12s;white-space:nowrap}.tab-btn.is-active{color:var(--c-text-primary);border-bottom-color:var(--c-text-accent)}.tab-btn:hover:not(.is-active){color:var(--c-text-secondary)}.tab-btn:focus-visible{outline:2px solid var(--c-border-focus);outline-offset:-2px;border-radius:var(--radius-sm)}.tab-count{margin-left:var(--sp-1);font-family:var(--font-mono);font-size:var(--text-2xs);color:var(--c-text-accent)}.tab-panel{padding:var(--sp-3);max-height:320px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--c-text-muted) transparent}.panel{background:var(--c-bg-elevated);border:1px solid var(--c-border);border-radius:var(--radius-lg);padding:var(--sp-3);transition:border-color .15s}.panel-heading{margin:0 0 var(--sp-2);font-size:var(--text-base);font-weight:700;text-transform:none;letter-spacing:.01em;color:var(--c-text-primary)}.page-panel{min-height:120px}.card-list{display:grid;gap:var(--sp-2)}.card{background:var(--c-bg-inset);border:1px solid var(--c-border);border-radius:var(--radius-md);padding:var(--sp-2) var(--sp-3);display:flex;flex-direction:column;gap:4px}.card>.input+.input,.card>.input+.btn,.card>.btn+.input{margin-top:2px}.card-warning{border-color:var(--c-border-warning)}.card-title{font-size:var(--text-sm);font-weight:600;color:var(--c-text-primary);margin:0;line-height:1.3}.result-card{all:unset;display:flex;flex-direction:column;gap:3px;background:var(--c-bg-inset);border:1px solid var(--c-border);border-radius:var(--radius-md);padding:var(--sp-2) var(--sp-3);cursor:pointer;transition:border-color .12s,background .12s}.result-card:hover{border-color:var(--c-border-focus);background:#0ea5e908}.result-card.is-active{border-color:var(--c-border-focus);background:#0ea5e90f}.input{background:var(--c-bg-input);color:var(--c-text-primary);border:1px solid var(--c-border);border-radius:var(--radius-sm);padding:6px var(--sp-2);font-family:var(--font-sans);font-size:var(--text-sm);width:100%;outline:none;transition:border-color .12s}.input:focus{border-color:var(--c-border-focus)}.input::placeholder{color:var(--c-text-muted)}select.input{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%2364748b'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center;padding-right:24px;text-overflow:ellipsis}.btn{all:unset;display:inline-flex;align-items:center;gap:var(--sp-1);height:28px;padding:0 var(--sp-3);font-size:var(--text-xs);font-family:var(--font-sans);border-radius:var(--radius-sm);cursor:pointer;border:1px solid var(--c-border);background:transparent;color:var(--c-text-primary);transition:border-color .12s,background .12s;white-space:nowrap}.btn:hover{border-color:var(--c-border-focus);background:var(--c-accent-bg)}.btn:focus-visible{outline:2px solid var(--c-border-focus);outline-offset:1px}.btn:disabled{opacity:.35;cursor:default;pointer-events:none}.btn-primary{border-color:var(--c-border-focus);background:var(--c-accent-bg)}.btn-danger{border-color:var(--c-border-danger);background:var(--c-danger-bg);color:var(--c-text-error)}.btn-ghost{border:none;background:transparent;color:var(--c-text-accent);padding:0 var(--sp-1);height:auto;font-size:var(--text-xs)}.btn-ghost:hover{text-decoration:underline;border:none;background:transparent}.action-row{display:flex;flex-wrap:wrap;gap:var(--sp-2);margin-top:var(--sp-1)}.chip-row{display:flex;flex-wrap:wrap;gap:var(--sp-1);margin-top:var(--sp-1)}.label{font-size:var(--text-2xs);text-transform:uppercase;letter-spacing:.06em;color:var(--c-text-muted);font-weight:500}.meta-grid{display:grid;grid-template-columns:auto 1fr;gap:3px var(--sp-3);font-size:var(--text-xs);align-items:baseline}.meta-label{color:var(--c-text-muted);text-transform:uppercase;letter-spacing:.04em;font-size:var(--text-2xs);white-space:nowrap}.meta-value{color:var(--c-text-primary);overflow-wrap:break-word;word-break:break-word}.meta-value-truncate{max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block}.meta-value.mono,.text-mono{font-family:var(--font-mono)}.linked-section{margin-top:var(--sp-2);padding-top:var(--sp-2);border-top:1px solid var(--c-border-subtle)}.asset-preview{display:grid;gap:var(--sp-2);margin-top:var(--sp-2)}.asset-card{margin:0;display:grid;gap:var(--sp-1)}.asset-card img{width:100%;max-height:200px;object-fit:contain;border-radius:var(--radius-md);border:1px solid var(--c-border);background:var(--c-bg-inset)}.asset-card figcaption{color:var(--c-text-muted);font-size:var(--text-2xs)}.content-pre{margin:var(--sp-2) 0 0;white-space:pre-wrap;overflow:auto;max-height:340px;padding:var(--sp-3);border-radius:var(--radius-md);background:var(--c-bg-inset);border:1px solid var(--c-border);font-family:var(--font-mono);font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.55;scrollbar-width:thin;scrollbar-color:var(--c-text-muted) transparent}.content-truncation-note{margin-top:var(--sp-1);font-size:var(--text-2xs);color:var(--c-text-muted);font-style:italic}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--sp-5) var(--sp-3);color:var(--c-text-muted);text-align:center;min-height:80px;gap:var(--sp-2)}.empty-state-icon{font-size:1.5rem;opacity:.4;line-height:1}mark{background:#7dd3fc26;color:var(--c-text-accent);border-radius:2px;padding:0 2px}.text-sm{font-size:var(--text-sm)}.text-muted{color:var(--c-text-muted)}.text-secondary{color:var(--c-text-secondary)}.text-error{color:var(--c-text-error);font-size:var(--text-xs)}code{font-family:var(--font-mono);font-size:var(--text-xs)}p,h3,h4{margin:0}a{color:var(--c-text-accent);text-decoration:none}a:hover{text-decoration:underline}@media(max-width:900px){.app-shell{grid-template-columns:1fr;grid-template-rows:var(--bar-height) auto minmax(300px,1fr) auto;grid-template-areas:"bar" "sidebar" "center" "rail";height:auto;min-height:100vh;overflow:auto}.sidebar{border-right:none;border-bottom:1px solid var(--c-border);max-height:260px}.center-area{grid-template-rows:auto minmax(50vh,1fr) auto}.detail-rail{border-left:none;border-top:1px solid var(--c-border)}.stats-strip{flex-wrap:wrap;gap:var(--sp-2)}}
package/dist/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>SwarmVault Graph</title>
7
- <script type="module" crossorigin src="/assets/index-C7PCTMog.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-DiMCbjBi.css">
7
+ <script type="module" crossorigin src="/assets/index-Csm8eB3P.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-DUJ6MWHL.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/dist/lib.js CHANGED
@@ -2,6 +2,176 @@
2
2
  function embeddedData() {
3
3
  return typeof window !== "undefined" ? window.__SWARMVAULT_EMBEDDED_DATA__ : void 0;
4
4
  }
5
+ function normalizeGraphTarget(value) {
6
+ return value.trim().toLowerCase();
7
+ }
8
+ function embeddedNodeById(graph) {
9
+ return new Map(graph.nodes.map((node) => [node.id, node]));
10
+ }
11
+ function embeddedPageById(graph) {
12
+ return new Map((graph.pages ?? []).map((page) => [page.id, page]));
13
+ }
14
+ function embeddedResolveNode(graph, target) {
15
+ const normalized = normalizeGraphTarget(target);
16
+ return graph.nodes.find((node) => node.id === target || normalizeGraphTarget(node.label) === normalized || node.pageId === target) ?? null;
17
+ }
18
+ function embeddedGraphAdjacency(graph) {
19
+ const adjacency = /* @__PURE__ */ new Map();
20
+ for (const node of graph.nodes) {
21
+ adjacency.set(node.id, []);
22
+ }
23
+ for (const edge of graph.edges) {
24
+ adjacency.get(edge.source)?.push({
25
+ nodeId: edge.target,
26
+ edge,
27
+ direction: "outgoing"
28
+ });
29
+ adjacency.get(edge.target)?.push({
30
+ nodeId: edge.source,
31
+ edge,
32
+ direction: "incoming"
33
+ });
34
+ }
35
+ return adjacency;
36
+ }
37
+ function shortestEmbeddedGraphPath(graph, from, to) {
38
+ const start = embeddedResolveNode(graph, from);
39
+ const end = embeddedResolveNode(graph, to);
40
+ if (!start || !end) {
41
+ return {
42
+ from,
43
+ to,
44
+ resolvedFromNodeId: start?.id,
45
+ resolvedToNodeId: end?.id,
46
+ found: false,
47
+ nodeIds: [],
48
+ edgeIds: [],
49
+ pageIds: [],
50
+ summary: "Could not resolve one or both graph targets."
51
+ };
52
+ }
53
+ const adjacency = embeddedGraphAdjacency(graph);
54
+ const queue = [start.id];
55
+ const visited = /* @__PURE__ */ new Set([start.id]);
56
+ const previous = /* @__PURE__ */ new Map();
57
+ while (queue.length) {
58
+ const current2 = queue.shift();
59
+ if (current2 === end.id) {
60
+ break;
61
+ }
62
+ for (const neighbor of adjacency.get(current2) ?? []) {
63
+ if (visited.has(neighbor.nodeId)) {
64
+ continue;
65
+ }
66
+ visited.add(neighbor.nodeId);
67
+ previous.set(neighbor.nodeId, {
68
+ nodeId: current2,
69
+ edgeId: neighbor.edge.id
70
+ });
71
+ queue.push(neighbor.nodeId);
72
+ }
73
+ }
74
+ if (!visited.has(end.id)) {
75
+ return {
76
+ from,
77
+ to,
78
+ resolvedFromNodeId: start.id,
79
+ resolvedToNodeId: end.id,
80
+ found: false,
81
+ nodeIds: [],
82
+ edgeIds: [],
83
+ pageIds: [],
84
+ summary: `No path found between ${start.label} and ${end.label}.`
85
+ };
86
+ }
87
+ const nodeIds = [];
88
+ const edgeIds = [];
89
+ let current = end.id;
90
+ while (current !== start.id) {
91
+ nodeIds.push(current);
92
+ const prev = previous.get(current);
93
+ if (!prev) {
94
+ break;
95
+ }
96
+ edgeIds.push(prev.edgeId);
97
+ current = prev.nodeId;
98
+ }
99
+ nodeIds.push(start.id);
100
+ nodeIds.reverse();
101
+ edgeIds.reverse();
102
+ const nodes = embeddedNodeById(graph);
103
+ const pageIds = [
104
+ ...new Set(
105
+ nodeIds.flatMap((nodeId) => {
106
+ const pageId = nodes.get(nodeId)?.pageId;
107
+ return pageId ? [pageId] : [];
108
+ })
109
+ )
110
+ ];
111
+ return {
112
+ from,
113
+ to,
114
+ resolvedFromNodeId: start.id,
115
+ resolvedToNodeId: end.id,
116
+ found: true,
117
+ nodeIds,
118
+ edgeIds,
119
+ pageIds,
120
+ summary: nodeIds.map((nodeId) => nodes.get(nodeId)?.label ?? nodeId).join(" -> ")
121
+ };
122
+ }
123
+ function explainEmbeddedGraphTarget(graph, target) {
124
+ const node = embeddedResolveNode(graph, target);
125
+ if (!node) {
126
+ throw new Error(`Could not resolve graph target: ${target}`);
127
+ }
128
+ const pages = embeddedPageById(graph);
129
+ const page = node.pageId ? pages.get(node.pageId) : void 0;
130
+ const nodes = embeddedNodeById(graph);
131
+ const neighbors = [];
132
+ for (const neighbor of embeddedGraphAdjacency(graph).get(node.id) ?? []) {
133
+ const targetNode = nodes.get(neighbor.nodeId);
134
+ if (!targetNode) {
135
+ continue;
136
+ }
137
+ neighbors.push({
138
+ nodeId: targetNode.id,
139
+ label: targetNode.label,
140
+ type: targetNode.type,
141
+ pageId: targetNode.pageId,
142
+ relation: neighbor.edge.relation,
143
+ direction: neighbor.direction,
144
+ confidence: neighbor.edge.confidence ?? 0,
145
+ evidenceClass: neighbor.edge.evidenceClass ?? "unknown"
146
+ });
147
+ }
148
+ neighbors.sort((left, right) => right.confidence - left.confidence || left.label.localeCompare(right.label));
149
+ const hyperedges = (graph.hyperedges ?? []).filter((hyperedge) => hyperedge.nodeIds.includes(node.id));
150
+ const community = graph.communities?.find((candidate) => candidate.id === node.communityId);
151
+ return {
152
+ target,
153
+ node,
154
+ page: page ? {
155
+ id: page.id,
156
+ path: page.path,
157
+ title: page.title
158
+ } : void 0,
159
+ community: community ? {
160
+ id: community.id,
161
+ label: community.label
162
+ } : void 0,
163
+ neighbors,
164
+ hyperedges,
165
+ summary: [
166
+ `Node: ${node.label}`,
167
+ `Type: ${node.type}`,
168
+ `Community: ${node.communityId ?? "none"}`,
169
+ `Neighbors: ${neighbors.length}`,
170
+ `Group patterns: ${hyperedges.length}`,
171
+ `Page: ${page?.path ?? "none"}`
172
+ ].join("\n")
173
+ };
174
+ }
5
175
  function normalizeSnippet(content, query) {
6
176
  const normalized = content.replace(/\s+/g, " ").trim();
7
177
  if (!query) {
@@ -129,6 +299,10 @@ async function fetchGraphReport() {
129
299
  return response.json();
130
300
  }
131
301
  async function fetchGraphPath(from, to) {
302
+ const embedded = embeddedData();
303
+ if (embedded) {
304
+ return shortestEmbeddedGraphPath(embedded.graph, from, to);
305
+ }
132
306
  const params = new URLSearchParams({
133
307
  from,
134
308
  to
@@ -140,6 +314,10 @@ async function fetchGraphPath(from, to) {
140
314
  return response.json();
141
315
  }
142
316
  async function fetchGraphExplain(target) {
317
+ const embedded = embeddedData();
318
+ if (embedded) {
319
+ return explainEmbeddedGraphTarget(embedded.graph, target);
320
+ }
143
321
  const response = await fetch(`/api/graph/explain?target=${encodeURIComponent(target)}`);
144
322
  if (!response.ok) {
145
323
  throw new Error(`Failed to explain graph target: ${response.status} ${response.statusText}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmvaultai/viewer",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "description": "Graph viewer package for SwarmVault graph artifacts.",
5
5
  "type": "module",
6
6
  "main": "dist/lib.js",
@@ -41,7 +41,7 @@
41
41
  "build": "pnpm run build:lib && pnpm run build:app",
42
42
  "build:lib": "tsup src/lib.ts --format esm --dts --out-dir dist --clean",
43
43
  "build:app": "vite build",
44
- "test": "node -e \"process.exit(0)\"",
44
+ "test": "vitest run",
45
45
  "typecheck": "tsc --noEmit"
46
46
  },
47
47
  "dependencies": {
@@ -54,8 +54,10 @@
54
54
  "@types/react": "^19.1.13",
55
55
  "@types/react-dom": "^19.1.9",
56
56
  "@vitejs/plugin-react": "^5.0.4",
57
+ "jsdom": "^27.0.0",
57
58
  "tsup": "^8.5.0",
58
59
  "typescript": "^5.9.2",
59
- "vite": "^7.1.7"
60
+ "vite": "^7.1.7",
61
+ "vitest": "^3.2.4"
60
62
  }
61
63
  }