ont-run 0.0.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 +228 -0
- package/bin/ont.ts +5 -0
- package/dist/bin/ont.d.ts +2 -0
- package/dist/bin/ont.js +13667 -0
- package/dist/index.js +23152 -0
- package/dist/src/browser/server.d.ts +16 -0
- package/dist/src/browser/transform.d.ts +87 -0
- package/dist/src/cli/commands/init.d.ts +12 -0
- package/dist/src/cli/commands/review.d.ts +17 -0
- package/dist/src/cli/index.d.ts +1 -0
- package/dist/src/cli/utils/config-loader.d.ts +13 -0
- package/dist/src/config/categorical.d.ts +76 -0
- package/dist/src/config/define.d.ts +46 -0
- package/dist/src/config/index.d.ts +4 -0
- package/dist/src/config/schema.d.ts +162 -0
- package/dist/src/config/types.d.ts +94 -0
- package/dist/src/index.d.ts +37 -0
- package/dist/src/lockfile/differ.d.ts +11 -0
- package/dist/src/lockfile/hasher.d.ts +31 -0
- package/dist/src/lockfile/index.d.ts +53 -0
- package/dist/src/lockfile/types.d.ts +90 -0
- package/dist/src/runtime/index.d.ts +28 -0
- package/dist/src/server/api/index.d.ts +20 -0
- package/dist/src/server/api/middleware.d.ts +34 -0
- package/dist/src/server/api/router.d.ts +18 -0
- package/dist/src/server/mcp/index.d.ts +23 -0
- package/dist/src/server/mcp/tools.d.ts +35 -0
- package/dist/src/server/resolver.d.ts +30 -0
- package/dist/src/server/start.d.ts +37 -0
- package/package.json +63 -0
- package/src/browser/server.ts +2567 -0
- package/src/browser/transform.ts +473 -0
- package/src/cli/commands/init.ts +226 -0
- package/src/cli/commands/review.ts +126 -0
- package/src/cli/index.ts +19 -0
- package/src/cli/utils/config-loader.ts +78 -0
- package/src/config/categorical.ts +101 -0
- package/src/config/define.ts +78 -0
- package/src/config/index.ts +23 -0
- package/src/config/schema.ts +196 -0
- package/src/config/types.ts +121 -0
- package/src/index.ts +53 -0
- package/src/lockfile/differ.ts +242 -0
- package/src/lockfile/hasher.ts +175 -0
- package/src/lockfile/index.ts +159 -0
- package/src/lockfile/types.ts +95 -0
- package/src/runtime/index.ts +114 -0
- package/src/server/api/index.ts +92 -0
- package/src/server/api/middleware.ts +118 -0
- package/src/server/api/router.ts +102 -0
- package/src/server/mcp/index.ts +182 -0
- package/src/server/mcp/tools.ts +199 -0
- package/src/server/resolver.ts +109 -0
- package/src/server/start.ts +151 -0
|
@@ -0,0 +1,2567 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import open from "open";
|
|
3
|
+
import type { OntologyConfig } from "../config/types.js";
|
|
4
|
+
import type { OntologyDiff } from "../lockfile/types.js";
|
|
5
|
+
import { writeLockfile } from "../lockfile/index.js";
|
|
6
|
+
import { serve, findAvailablePort } from "../runtime/index.js";
|
|
7
|
+
import { transformToGraphData, enhanceWithDiff, searchNodes, getNodeDetails, type EnhancedGraphData } from "./transform.js";
|
|
8
|
+
|
|
9
|
+
export interface BrowserServerOptions {
|
|
10
|
+
config: OntologyConfig;
|
|
11
|
+
/** Diff data for review mode (null = browse-only, no changes) */
|
|
12
|
+
diff?: OntologyDiff | null;
|
|
13
|
+
/** Directory to write the lockfile to on approval */
|
|
14
|
+
configDir?: string;
|
|
15
|
+
port?: number;
|
|
16
|
+
openBrowser?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BrowserServerResult {
|
|
20
|
+
/** Whether changes were approved (only set if there were changes) */
|
|
21
|
+
approved?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function startBrowserServer(options: BrowserServerOptions): Promise<BrowserServerResult> {
|
|
25
|
+
const { config, diff = null, configDir, port: preferredPort, openBrowser = true } = options;
|
|
26
|
+
|
|
27
|
+
// Transform config to graph data and enhance with diff info
|
|
28
|
+
const baseGraphData = transformToGraphData(config);
|
|
29
|
+
const graphData = enhanceWithDiff(baseGraphData, diff);
|
|
30
|
+
|
|
31
|
+
return new Promise(async (resolve) => {
|
|
32
|
+
const app = new Hono();
|
|
33
|
+
|
|
34
|
+
// API: Get full graph data (enhanced with change status)
|
|
35
|
+
app.get("/api/graph", (c) => c.json(graphData));
|
|
36
|
+
|
|
37
|
+
// API: Get diff data
|
|
38
|
+
app.get("/api/diff", (c) => c.json(diff));
|
|
39
|
+
|
|
40
|
+
// API: Get node details
|
|
41
|
+
app.get("/api/node/:type/:id", (c) => {
|
|
42
|
+
const { type, id } = c.req.param();
|
|
43
|
+
const nodeId = `${type}:${id}`;
|
|
44
|
+
const details = getNodeDetails(baseGraphData, nodeId);
|
|
45
|
+
if (!details.node) {
|
|
46
|
+
return c.json({ error: "Node not found" }, 404);
|
|
47
|
+
}
|
|
48
|
+
return c.json(details);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// API: Search nodes
|
|
52
|
+
app.get("/api/search", (c) => {
|
|
53
|
+
const query = c.req.query("q") || "";
|
|
54
|
+
if (query.length < 1) {
|
|
55
|
+
return c.json({ results: [] });
|
|
56
|
+
}
|
|
57
|
+
const results = searchNodes(baseGraphData, query);
|
|
58
|
+
return c.json({ results });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// API: Approve changes (only works if diff exists)
|
|
62
|
+
app.post("/api/approve", async (c) => {
|
|
63
|
+
if (!diff || !configDir) {
|
|
64
|
+
return c.json({ error: "No changes to approve" }, 400);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
await writeLockfile(configDir, diff.newOntology, diff.newHash);
|
|
68
|
+
// Give time for response to be sent before resolving
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
resolve({ approved: true });
|
|
71
|
+
}, 500);
|
|
72
|
+
return c.json({ success: true });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return c.json(
|
|
75
|
+
{
|
|
76
|
+
error: "Failed to write lockfile",
|
|
77
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
78
|
+
},
|
|
79
|
+
500
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// API: Reject changes
|
|
85
|
+
app.post("/api/reject", (c) => {
|
|
86
|
+
// Give time for response to be sent before resolving
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
resolve({ approved: false });
|
|
89
|
+
}, 500);
|
|
90
|
+
return c.json({ success: true });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Serve UI
|
|
94
|
+
app.get("/", (c) => c.html(generateBrowserUI(graphData)));
|
|
95
|
+
|
|
96
|
+
// Start server
|
|
97
|
+
const port = preferredPort || (await findAvailablePort(3457));
|
|
98
|
+
const server = await serve(app, port);
|
|
99
|
+
|
|
100
|
+
const url = `http://localhost:${server.port}`;
|
|
101
|
+
const hasChanges = diff?.hasChanges ?? false;
|
|
102
|
+
console.log(`\nOntology ${hasChanges ? "Review" : "Browser"} available at: ${url}`);
|
|
103
|
+
|
|
104
|
+
if (openBrowser) {
|
|
105
|
+
console.log("Opening in browser...\n");
|
|
106
|
+
try {
|
|
107
|
+
await open(url);
|
|
108
|
+
} catch {
|
|
109
|
+
console.log("Could not open browser automatically.");
|
|
110
|
+
console.log(`Please open ${url} manually.\n`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (hasChanges) {
|
|
115
|
+
console.log("Waiting for review decision...\n");
|
|
116
|
+
} else {
|
|
117
|
+
console.log("Press Ctrl+C to stop the server.\n");
|
|
118
|
+
// If no changes, resolve immediately with no approval status
|
|
119
|
+
// But keep server running for browsing
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
125
|
+
return `<!DOCTYPE html>
|
|
126
|
+
<html lang="en">
|
|
127
|
+
<head>
|
|
128
|
+
<meta charset="UTF-8">
|
|
129
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
130
|
+
<title>${graphData.meta.ontologyName} - Ontology Browser</title>
|
|
131
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
132
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
133
|
+
<link href="https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600&family=Space+Mono&display=swap" rel="stylesheet">
|
|
134
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.28.1/cytoscape.min.js"></script>
|
|
135
|
+
<style>
|
|
136
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
137
|
+
|
|
138
|
+
:root {
|
|
139
|
+
/* Vanna Brand Colors */
|
|
140
|
+
--vanna-navy: #023d60;
|
|
141
|
+
--vanna-cream: #e7e1cf;
|
|
142
|
+
--vanna-teal: #15a8a8;
|
|
143
|
+
--vanna-orange: #fe5d26;
|
|
144
|
+
--vanna-magenta: #bf1363;
|
|
145
|
+
|
|
146
|
+
/* Change indicator colors (Vanna palette complements) */
|
|
147
|
+
--change-added: #2a9d8f;
|
|
148
|
+
--change-added-bg: rgba(42, 157, 143, 0.12);
|
|
149
|
+
--change-removed: #c44536;
|
|
150
|
+
--change-removed-bg: rgba(196, 69, 54, 0.1);
|
|
151
|
+
--change-modified: var(--vanna-orange);
|
|
152
|
+
--change-modified-bg: rgba(254, 93, 38, 0.1);
|
|
153
|
+
|
|
154
|
+
/* Semantic mappings */
|
|
155
|
+
--bg-primary: #f8f6f1;
|
|
156
|
+
--bg-secondary: #ffffff;
|
|
157
|
+
--bg-tertiary: var(--vanna-cream);
|
|
158
|
+
--bg-hover: #f0ede5;
|
|
159
|
+
--border-primary: rgba(2, 61, 96, 0.12);
|
|
160
|
+
--border-highlight: var(--vanna-teal);
|
|
161
|
+
--text-primary: var(--vanna-navy);
|
|
162
|
+
--text-secondary: #475569;
|
|
163
|
+
--text-muted: #64748b;
|
|
164
|
+
|
|
165
|
+
/* Node colors */
|
|
166
|
+
--node-entity: var(--vanna-teal);
|
|
167
|
+
--node-function: var(--vanna-navy);
|
|
168
|
+
--node-access: var(--vanna-magenta);
|
|
169
|
+
|
|
170
|
+
/* Edge colors */
|
|
171
|
+
--edge-operates: var(--vanna-teal);
|
|
172
|
+
--edge-access: var(--vanna-magenta);
|
|
173
|
+
--edge-depends: var(--vanna-orange);
|
|
174
|
+
|
|
175
|
+
/* Accents */
|
|
176
|
+
--accent-primary: var(--vanna-teal);
|
|
177
|
+
--accent-success: var(--vanna-teal);
|
|
178
|
+
--accent-warning: var(--vanna-orange);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
body {
|
|
182
|
+
font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
183
|
+
background: linear-gradient(to bottom, var(--vanna-cream), #ffffff, var(--vanna-cream));
|
|
184
|
+
background-attachment: fixed;
|
|
185
|
+
color: var(--text-primary);
|
|
186
|
+
height: 100vh;
|
|
187
|
+
overflow: hidden;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.layout {
|
|
191
|
+
display: grid;
|
|
192
|
+
grid-template-rows: 64px 1fr;
|
|
193
|
+
grid-template-columns: 260px 1fr 340px;
|
|
194
|
+
height: 100vh;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Header */
|
|
198
|
+
.header {
|
|
199
|
+
grid-column: 1 / -1;
|
|
200
|
+
background: rgba(231, 225, 207, 0.9);
|
|
201
|
+
backdrop-filter: blur(12px);
|
|
202
|
+
border-bottom: 1px solid rgba(21, 168, 168, 0.2);
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
padding: 0 24px;
|
|
206
|
+
gap: 24px;
|
|
207
|
+
position: relative;
|
|
208
|
+
z-index: 1000;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.logo {
|
|
212
|
+
display: flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
gap: 12px;
|
|
215
|
+
font-family: 'Roboto Slab', serif;
|
|
216
|
+
font-weight: 600;
|
|
217
|
+
font-size: 18px;
|
|
218
|
+
color: var(--vanna-navy);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.logo-icon {
|
|
222
|
+
width: 36px;
|
|
223
|
+
height: 36px;
|
|
224
|
+
flex-shrink: 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.logo-icon svg {
|
|
228
|
+
width: 100%;
|
|
229
|
+
height: 100%;
|
|
230
|
+
border-radius: 8px;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.search-container {
|
|
234
|
+
flex: 1;
|
|
235
|
+
max-width: 420px;
|
|
236
|
+
position: relative;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.search-input {
|
|
240
|
+
width: 100%;
|
|
241
|
+
padding: 10px 14px 10px 40px;
|
|
242
|
+
background: rgba(255, 255, 255, 0.8);
|
|
243
|
+
border: 1px solid rgba(2, 61, 96, 0.15);
|
|
244
|
+
border-radius: 9999px;
|
|
245
|
+
color: var(--text-primary);
|
|
246
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
247
|
+
font-size: 14px;
|
|
248
|
+
outline: none;
|
|
249
|
+
transition: all 0.2s ease;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.search-input:focus {
|
|
253
|
+
background: white;
|
|
254
|
+
border-color: var(--vanna-teal);
|
|
255
|
+
box-shadow: 0 0 0 3px rgba(21, 168, 168, 0.15);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.search-input::placeholder {
|
|
259
|
+
color: var(--text-muted);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.search-icon {
|
|
263
|
+
position: absolute;
|
|
264
|
+
left: 14px;
|
|
265
|
+
top: 50%;
|
|
266
|
+
transform: translateY(-50%);
|
|
267
|
+
color: var(--text-muted);
|
|
268
|
+
pointer-events: none;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.filter-buttons {
|
|
272
|
+
display: flex;
|
|
273
|
+
gap: 8px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.filter-btn {
|
|
277
|
+
padding: 8px 16px;
|
|
278
|
+
background: rgba(255, 255, 255, 0.6);
|
|
279
|
+
border: 1px solid rgba(2, 61, 96, 0.1);
|
|
280
|
+
border-radius: 9999px;
|
|
281
|
+
color: var(--text-secondary);
|
|
282
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
283
|
+
font-size: 13px;
|
|
284
|
+
font-weight: 500;
|
|
285
|
+
cursor: pointer;
|
|
286
|
+
transition: all 0.2s ease;
|
|
287
|
+
display: flex;
|
|
288
|
+
align-items: center;
|
|
289
|
+
gap: 8px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.filter-btn:hover {
|
|
293
|
+
background: white;
|
|
294
|
+
border-color: var(--vanna-teal);
|
|
295
|
+
color: var(--vanna-navy);
|
|
296
|
+
transform: translateY(-1px);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.filter-btn.active {
|
|
300
|
+
background: var(--vanna-teal);
|
|
301
|
+
border-color: var(--vanna-teal);
|
|
302
|
+
color: white;
|
|
303
|
+
box-shadow: 0 4px 15px rgba(21, 168, 168, 0.3);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.filter-btn .dot {
|
|
307
|
+
width: 8px;
|
|
308
|
+
height: 8px;
|
|
309
|
+
border-radius: 50%;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.filter-btn .dot.entity { background: var(--node-entity); }
|
|
313
|
+
.filter-btn .dot.function { background: var(--node-function); }
|
|
314
|
+
.filter-btn .dot.access { background: var(--node-access); }
|
|
315
|
+
.filter-btn.active .dot { background: white; }
|
|
316
|
+
|
|
317
|
+
.layout-selector {
|
|
318
|
+
display: flex;
|
|
319
|
+
gap: 4px;
|
|
320
|
+
margin-left: auto;
|
|
321
|
+
background: rgba(255, 255, 255, 0.5);
|
|
322
|
+
padding: 4px;
|
|
323
|
+
border-radius: 9999px;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.layout-btn {
|
|
327
|
+
padding: 6px 12px;
|
|
328
|
+
background: transparent;
|
|
329
|
+
border: none;
|
|
330
|
+
border-radius: 9999px;
|
|
331
|
+
color: var(--text-muted);
|
|
332
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
333
|
+
font-size: 12px;
|
|
334
|
+
font-weight: 500;
|
|
335
|
+
cursor: pointer;
|
|
336
|
+
transition: all 0.2s ease;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.layout-btn:hover {
|
|
340
|
+
color: var(--vanna-navy);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.layout-btn.active {
|
|
344
|
+
background: white;
|
|
345
|
+
color: var(--vanna-teal);
|
|
346
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* View Tabs */
|
|
350
|
+
.view-tabs {
|
|
351
|
+
display: flex;
|
|
352
|
+
gap: 4px;
|
|
353
|
+
background: rgba(255, 255, 255, 0.5);
|
|
354
|
+
padding: 4px;
|
|
355
|
+
border-radius: 9999px;
|
|
356
|
+
margin-right: 16px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.view-tab {
|
|
360
|
+
padding: 8px 16px;
|
|
361
|
+
background: transparent;
|
|
362
|
+
border: none;
|
|
363
|
+
border-radius: 9999px;
|
|
364
|
+
color: var(--text-muted);
|
|
365
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
366
|
+
font-size: 13px;
|
|
367
|
+
font-weight: 500;
|
|
368
|
+
cursor: pointer;
|
|
369
|
+
transition: all 0.2s ease;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.view-tab:hover {
|
|
373
|
+
color: var(--vanna-navy);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.view-tab.active {
|
|
377
|
+
background: white;
|
|
378
|
+
color: var(--vanna-teal);
|
|
379
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* Change Indicators */
|
|
383
|
+
.change-badge {
|
|
384
|
+
display: inline-flex;
|
|
385
|
+
align-items: center;
|
|
386
|
+
justify-content: center;
|
|
387
|
+
width: 18px;
|
|
388
|
+
height: 18px;
|
|
389
|
+
border-radius: 50%;
|
|
390
|
+
font-size: 12px;
|
|
391
|
+
font-weight: 600;
|
|
392
|
+
margin-left: 6px;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.change-badge.added {
|
|
396
|
+
background: var(--change-added-bg);
|
|
397
|
+
color: var(--change-added);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.change-badge.removed {
|
|
401
|
+
background: var(--change-removed-bg);
|
|
402
|
+
color: var(--change-removed);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.change-badge.modified {
|
|
406
|
+
background: var(--change-modified-bg);
|
|
407
|
+
color: var(--change-modified);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/* Review Footer */
|
|
411
|
+
.review-footer {
|
|
412
|
+
position: fixed;
|
|
413
|
+
bottom: 0;
|
|
414
|
+
left: 0;
|
|
415
|
+
right: 0;
|
|
416
|
+
background: rgba(255, 255, 255, 0.95);
|
|
417
|
+
backdrop-filter: blur(12px);
|
|
418
|
+
border-top: 1px solid rgba(21, 168, 168, 0.2);
|
|
419
|
+
padding: 16px 24px;
|
|
420
|
+
display: flex;
|
|
421
|
+
align-items: center;
|
|
422
|
+
justify-content: space-between;
|
|
423
|
+
z-index: 1000;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.review-footer.hidden {
|
|
427
|
+
display: none;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.changes-summary {
|
|
431
|
+
display: flex;
|
|
432
|
+
align-items: center;
|
|
433
|
+
gap: 16px;
|
|
434
|
+
font-size: 14px;
|
|
435
|
+
color: var(--text-secondary);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.changes-summary .change-count {
|
|
439
|
+
display: flex;
|
|
440
|
+
align-items: center;
|
|
441
|
+
gap: 6px;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.changes-summary .change-count.added { color: var(--change-added); }
|
|
445
|
+
.changes-summary .change-count.removed { color: var(--change-removed); }
|
|
446
|
+
.changes-summary .change-count.modified { color: var(--change-modified); }
|
|
447
|
+
|
|
448
|
+
.review-actions {
|
|
449
|
+
display: flex;
|
|
450
|
+
gap: 12px;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.review-btn {
|
|
454
|
+
padding: 10px 20px;
|
|
455
|
+
border-radius: 9999px;
|
|
456
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
457
|
+
font-size: 14px;
|
|
458
|
+
font-weight: 600;
|
|
459
|
+
cursor: pointer;
|
|
460
|
+
transition: all 0.2s ease;
|
|
461
|
+
border: 1px solid transparent;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.review-btn:disabled {
|
|
465
|
+
opacity: 0.5;
|
|
466
|
+
cursor: not-allowed;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
.review-btn.reject {
|
|
470
|
+
background: var(--change-removed-bg);
|
|
471
|
+
border-color: rgba(196, 69, 54, 0.3);
|
|
472
|
+
color: var(--change-removed);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.review-btn.reject:hover:not(:disabled) {
|
|
476
|
+
background: rgba(196, 69, 54, 0.2);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.review-btn.approve {
|
|
480
|
+
background: var(--vanna-teal);
|
|
481
|
+
color: white;
|
|
482
|
+
box-shadow: 0 4px 15px rgba(21, 168, 168, 0.3);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.review-btn.approve:hover:not(:disabled) {
|
|
486
|
+
transform: translateY(-1px);
|
|
487
|
+
box-shadow: 0 6px 20px rgba(21, 168, 168, 0.4);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* Table View */
|
|
491
|
+
.table-view {
|
|
492
|
+
display: none;
|
|
493
|
+
grid-column: 2 / 4;
|
|
494
|
+
padding: 24px;
|
|
495
|
+
overflow-y: auto;
|
|
496
|
+
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.5), rgba(231, 225, 207, 0.3));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.table-view.active {
|
|
500
|
+
display: block;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.table-section {
|
|
504
|
+
background: white;
|
|
505
|
+
border: 1px solid rgba(2, 61, 96, 0.08);
|
|
506
|
+
border-radius: 16px;
|
|
507
|
+
margin-bottom: 20px;
|
|
508
|
+
overflow: hidden;
|
|
509
|
+
box-shadow: 0 4px 15px rgba(15, 23, 42, 0.04);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.table-section-header {
|
|
513
|
+
display: flex;
|
|
514
|
+
align-items: center;
|
|
515
|
+
justify-content: space-between;
|
|
516
|
+
padding: 16px 20px;
|
|
517
|
+
background: rgba(231, 225, 207, 0.4);
|
|
518
|
+
border-bottom: 1px solid rgba(2, 61, 96, 0.08);
|
|
519
|
+
cursor: pointer;
|
|
520
|
+
user-select: none;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.table-section-header:hover {
|
|
524
|
+
background: rgba(231, 225, 207, 0.6);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.table-section-title {
|
|
528
|
+
font-family: 'Roboto Slab', serif;
|
|
529
|
+
font-size: 14px;
|
|
530
|
+
font-weight: 600;
|
|
531
|
+
color: var(--vanna-navy);
|
|
532
|
+
display: flex;
|
|
533
|
+
align-items: center;
|
|
534
|
+
gap: 10px;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.table-section-count {
|
|
538
|
+
background: rgba(21, 168, 168, 0.1);
|
|
539
|
+
color: var(--vanna-teal);
|
|
540
|
+
padding: 2px 10px;
|
|
541
|
+
border-radius: 9999px;
|
|
542
|
+
font-size: 12px;
|
|
543
|
+
font-weight: 500;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.table-section-toggle {
|
|
547
|
+
color: var(--text-muted);
|
|
548
|
+
transition: transform 0.2s ease;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.table-section.collapsed .table-section-toggle {
|
|
552
|
+
transform: rotate(-90deg);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.table-section.collapsed .table-section-content {
|
|
556
|
+
display: none;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.table-section-content {
|
|
560
|
+
padding: 8px;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.table-item {
|
|
564
|
+
display: flex;
|
|
565
|
+
align-items: flex-start;
|
|
566
|
+
padding: 12px 16px;
|
|
567
|
+
border-radius: 12px;
|
|
568
|
+
margin-bottom: 4px;
|
|
569
|
+
transition: all 0.2s ease;
|
|
570
|
+
border-left: 3px solid transparent;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.table-item:last-child {
|
|
574
|
+
margin-bottom: 0;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.table-item:hover {
|
|
578
|
+
background: rgba(231, 225, 207, 0.3);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.table-item.added {
|
|
582
|
+
border-left-color: var(--change-added);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.table-item.removed {
|
|
586
|
+
background: var(--change-removed-bg);
|
|
587
|
+
border-left-color: var(--change-removed);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.table-item.modified {
|
|
591
|
+
border-left-color: var(--change-modified);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.table-item-icon {
|
|
595
|
+
width: 24px;
|
|
596
|
+
height: 24px;
|
|
597
|
+
display: flex;
|
|
598
|
+
align-items: center;
|
|
599
|
+
justify-content: center;
|
|
600
|
+
font-size: 14px;
|
|
601
|
+
font-weight: 600;
|
|
602
|
+
margin-right: 12px;
|
|
603
|
+
flex-shrink: 0;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.table-item.added .table-item-icon { color: var(--change-added); }
|
|
607
|
+
.table-item.removed .table-item-icon { color: var(--change-removed); }
|
|
608
|
+
.table-item.modified .table-item-icon { color: var(--change-modified); }
|
|
609
|
+
|
|
610
|
+
.table-item-content {
|
|
611
|
+
flex: 1;
|
|
612
|
+
min-width: 0;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.table-item-name {
|
|
616
|
+
font-weight: 600;
|
|
617
|
+
color: var(--vanna-navy);
|
|
618
|
+
margin-bottom: 4px;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.table-item.removed .table-item-name {
|
|
622
|
+
text-decoration: line-through;
|
|
623
|
+
opacity: 0.7;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.table-item-description {
|
|
627
|
+
font-size: 13px;
|
|
628
|
+
color: var(--text-secondary);
|
|
629
|
+
margin-bottom: 8px;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.table-item-tags {
|
|
633
|
+
display: flex;
|
|
634
|
+
flex-wrap: wrap;
|
|
635
|
+
gap: 6px;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.table-item-tag {
|
|
639
|
+
display: inline-flex;
|
|
640
|
+
align-items: center;
|
|
641
|
+
gap: 4px;
|
|
642
|
+
padding: 3px 10px;
|
|
643
|
+
border-radius: 9999px;
|
|
644
|
+
font-size: 11px;
|
|
645
|
+
font-weight: 500;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.table-item-tag.access {
|
|
649
|
+
background: rgba(191, 19, 99, 0.1);
|
|
650
|
+
color: var(--vanna-magenta);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.table-item-tag.entity {
|
|
654
|
+
background: rgba(21, 168, 168, 0.1);
|
|
655
|
+
color: var(--vanna-teal);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.table-item-change {
|
|
659
|
+
margin-top: 8px;
|
|
660
|
+
padding: 8px 12px;
|
|
661
|
+
background: rgba(0, 0, 0, 0.03);
|
|
662
|
+
border-radius: 8px;
|
|
663
|
+
font-size: 12px;
|
|
664
|
+
color: var(--text-secondary);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.table-item-change .old {
|
|
668
|
+
text-decoration: line-through;
|
|
669
|
+
color: var(--change-removed);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.table-item-change .arrow {
|
|
673
|
+
margin: 0 8px;
|
|
674
|
+
color: var(--text-muted);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.table-item-change .new {
|
|
678
|
+
color: var(--change-added);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/* No Changes State */
|
|
682
|
+
.no-changes {
|
|
683
|
+
text-align: center;
|
|
684
|
+
padding: 48px 24px;
|
|
685
|
+
color: var(--text-muted);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.no-changes-icon {
|
|
689
|
+
font-size: 48px;
|
|
690
|
+
margin-bottom: 16px;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.no-changes-text {
|
|
694
|
+
font-size: 16px;
|
|
695
|
+
color: var(--text-secondary);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/* Sidebar */
|
|
699
|
+
.sidebar {
|
|
700
|
+
background: rgba(255, 255, 255, 0.7);
|
|
701
|
+
backdrop-filter: blur(12px);
|
|
702
|
+
border-right: 1px solid rgba(21, 168, 168, 0.15);
|
|
703
|
+
padding: 20px;
|
|
704
|
+
overflow-y: auto;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.sidebar-section {
|
|
708
|
+
margin-bottom: 28px;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.sidebar-title {
|
|
712
|
+
font-family: 'Roboto Slab', serif;
|
|
713
|
+
font-size: 11px;
|
|
714
|
+
font-weight: 600;
|
|
715
|
+
text-transform: uppercase;
|
|
716
|
+
letter-spacing: 0.08em;
|
|
717
|
+
color: var(--vanna-navy);
|
|
718
|
+
margin-bottom: 14px;
|
|
719
|
+
opacity: 0.7;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.stat-grid {
|
|
723
|
+
display: grid;
|
|
724
|
+
gap: 10px;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.stat-card {
|
|
728
|
+
background: white;
|
|
729
|
+
border: 1px solid rgba(2, 61, 96, 0.08);
|
|
730
|
+
border-radius: 16px;
|
|
731
|
+
padding: 16px;
|
|
732
|
+
cursor: pointer;
|
|
733
|
+
transition: all 0.25s ease;
|
|
734
|
+
box-shadow: 0 4px 15px rgba(15, 23, 42, 0.04);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.stat-card:hover {
|
|
738
|
+
border-color: var(--vanna-teal);
|
|
739
|
+
transform: translateY(-2px);
|
|
740
|
+
box-shadow: 0 8px 25px rgba(21, 168, 168, 0.12);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.stat-card.active {
|
|
744
|
+
border-color: var(--vanna-teal);
|
|
745
|
+
background: linear-gradient(135deg, white, rgba(21, 168, 168, 0.08));
|
|
746
|
+
box-shadow: 0 8px 25px rgba(21, 168, 168, 0.15);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.stat-value {
|
|
750
|
+
font-family: 'Roboto Slab', serif;
|
|
751
|
+
font-size: 28px;
|
|
752
|
+
font-weight: 700;
|
|
753
|
+
color: var(--vanna-navy);
|
|
754
|
+
margin-bottom: 4px;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
.stat-label {
|
|
758
|
+
font-size: 13px;
|
|
759
|
+
color: var(--text-secondary);
|
|
760
|
+
display: flex;
|
|
761
|
+
align-items: center;
|
|
762
|
+
gap: 8px;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.stat-label .dot {
|
|
766
|
+
width: 8px;
|
|
767
|
+
height: 8px;
|
|
768
|
+
border-radius: 50%;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.legend-item {
|
|
772
|
+
display: flex;
|
|
773
|
+
align-items: center;
|
|
774
|
+
gap: 12px;
|
|
775
|
+
padding: 10px 0;
|
|
776
|
+
border-bottom: 1px solid rgba(2, 61, 96, 0.06);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
.legend-item:last-child {
|
|
780
|
+
border-bottom: none;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
.legend-shape {
|
|
784
|
+
width: 28px;
|
|
785
|
+
height: 28px;
|
|
786
|
+
display: flex;
|
|
787
|
+
align-items: center;
|
|
788
|
+
justify-content: center;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.legend-shape.entity {
|
|
792
|
+
width: 24px;
|
|
793
|
+
height: 16px;
|
|
794
|
+
border: 2px solid var(--node-entity);
|
|
795
|
+
border-radius: 4px;
|
|
796
|
+
background: rgba(21, 168, 168, 0.1);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
.legend-shape.function {
|
|
800
|
+
width: 22px;
|
|
801
|
+
height: 22px;
|
|
802
|
+
background: rgba(2, 61, 96, 0.1);
|
|
803
|
+
border: 2px solid var(--node-function);
|
|
804
|
+
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
.legend-shape.access {
|
|
808
|
+
width: 20px;
|
|
809
|
+
height: 20px;
|
|
810
|
+
border: 2px solid var(--node-access);
|
|
811
|
+
border-radius: 50%;
|
|
812
|
+
background: rgba(191, 19, 99, 0.1);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.legend-text {
|
|
816
|
+
font-size: 13px;
|
|
817
|
+
color: var(--text-secondary);
|
|
818
|
+
font-weight: 500;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.legend-edge {
|
|
822
|
+
display: flex;
|
|
823
|
+
align-items: center;
|
|
824
|
+
gap: 12px;
|
|
825
|
+
padding: 8px 0;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.legend-line {
|
|
829
|
+
width: 28px;
|
|
830
|
+
height: 3px;
|
|
831
|
+
border-radius: 2px;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.legend-line.operates { background: var(--edge-operates); }
|
|
835
|
+
.legend-line.access { background: var(--edge-access); }
|
|
836
|
+
.legend-line.depends { background: var(--edge-depends); }
|
|
837
|
+
|
|
838
|
+
/* Graph Container */
|
|
839
|
+
.graph-container {
|
|
840
|
+
background:
|
|
841
|
+
radial-gradient(circle at 2px 2px, rgba(2, 61, 96, 0.3) 1px, transparent 0),
|
|
842
|
+
radial-gradient(circle at 30% 20%, rgba(21, 168, 168, 0.06), transparent 50%),
|
|
843
|
+
radial-gradient(circle at 70% 80%, rgba(191, 19, 99, 0.04), transparent 50%),
|
|
844
|
+
linear-gradient(135deg, #faf9f6, #f5f3ed);
|
|
845
|
+
background-size: 32px 32px, 100% 100%, 100% 100%, 100% 100%;
|
|
846
|
+
position: relative;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
#cy {
|
|
850
|
+
width: 100%;
|
|
851
|
+
height: 100%;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.graph-controls {
|
|
855
|
+
position: absolute;
|
|
856
|
+
bottom: 20px;
|
|
857
|
+
left: 20px;
|
|
858
|
+
display: flex;
|
|
859
|
+
gap: 8px;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.graph-control-btn {
|
|
863
|
+
width: 40px;
|
|
864
|
+
height: 40px;
|
|
865
|
+
background: white;
|
|
866
|
+
border: 1px solid rgba(2, 61, 96, 0.1);
|
|
867
|
+
border-radius: 12px;
|
|
868
|
+
color: var(--text-secondary);
|
|
869
|
+
cursor: pointer;
|
|
870
|
+
display: flex;
|
|
871
|
+
align-items: center;
|
|
872
|
+
justify-content: center;
|
|
873
|
+
transition: all 0.2s ease;
|
|
874
|
+
box-shadow: 0 4px 15px rgba(15, 23, 42, 0.06);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
.graph-control-btn:hover {
|
|
878
|
+
background: white;
|
|
879
|
+
border-color: var(--vanna-teal);
|
|
880
|
+
color: var(--vanna-teal);
|
|
881
|
+
transform: translateY(-2px);
|
|
882
|
+
box-shadow: 0 8px 25px rgba(21, 168, 168, 0.15);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/* Detail Panel */
|
|
886
|
+
.detail-panel {
|
|
887
|
+
background: rgba(255, 255, 255, 0.85);
|
|
888
|
+
backdrop-filter: blur(12px);
|
|
889
|
+
border-left: 1px solid rgba(21, 168, 168, 0.15);
|
|
890
|
+
overflow-y: auto;
|
|
891
|
+
display: flex;
|
|
892
|
+
flex-direction: column;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.detail-panel.empty {
|
|
896
|
+
align-items: center;
|
|
897
|
+
justify-content: center;
|
|
898
|
+
padding: 40px;
|
|
899
|
+
text-align: center;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
.empty-state-icon {
|
|
903
|
+
width: 56px;
|
|
904
|
+
height: 56px;
|
|
905
|
+
background: linear-gradient(135deg, rgba(21, 168, 168, 0.1), rgba(191, 19, 99, 0.05));
|
|
906
|
+
border-radius: 16px;
|
|
907
|
+
display: flex;
|
|
908
|
+
align-items: center;
|
|
909
|
+
justify-content: center;
|
|
910
|
+
margin-bottom: 20px;
|
|
911
|
+
color: var(--vanna-teal);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.empty-state-title {
|
|
915
|
+
font-family: 'Roboto Slab', serif;
|
|
916
|
+
font-size: 16px;
|
|
917
|
+
font-weight: 600;
|
|
918
|
+
color: var(--vanna-navy);
|
|
919
|
+
margin-bottom: 10px;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
.empty-state-text {
|
|
923
|
+
font-size: 13px;
|
|
924
|
+
color: var(--text-muted);
|
|
925
|
+
line-height: 1.6;
|
|
926
|
+
max-width: 220px;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
.detail-header {
|
|
930
|
+
padding: 24px;
|
|
931
|
+
border-bottom: 1px solid rgba(2, 61, 96, 0.08);
|
|
932
|
+
background: linear-gradient(135deg, white, rgba(231, 225, 207, 0.3));
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
.detail-type {
|
|
936
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
937
|
+
font-size: 11px;
|
|
938
|
+
font-weight: 600;
|
|
939
|
+
text-transform: uppercase;
|
|
940
|
+
letter-spacing: 0.08em;
|
|
941
|
+
margin-bottom: 10px;
|
|
942
|
+
display: flex;
|
|
943
|
+
align-items: center;
|
|
944
|
+
gap: 8px;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.detail-type.entity { color: var(--node-entity); }
|
|
948
|
+
.detail-type.function { color: var(--node-function); }
|
|
949
|
+
.detail-type.accessGroup { color: var(--node-access); }
|
|
950
|
+
|
|
951
|
+
.detail-name {
|
|
952
|
+
font-family: 'Roboto Slab', serif;
|
|
953
|
+
font-size: 22px;
|
|
954
|
+
font-weight: 700;
|
|
955
|
+
color: var(--vanna-navy);
|
|
956
|
+
margin-bottom: 10px;
|
|
957
|
+
word-break: break-word;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.detail-description {
|
|
961
|
+
font-size: 14px;
|
|
962
|
+
color: var(--text-secondary);
|
|
963
|
+
line-height: 1.6;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
.detail-change-badge {
|
|
967
|
+
padding: 3px 10px;
|
|
968
|
+
border-radius: 9999px;
|
|
969
|
+
font-size: 10px;
|
|
970
|
+
font-weight: 600;
|
|
971
|
+
text-transform: uppercase;
|
|
972
|
+
letter-spacing: 0.05em;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
.detail-change-badge.added {
|
|
976
|
+
background: var(--change-added-bg);
|
|
977
|
+
color: var(--change-added);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
.detail-change-badge.removed {
|
|
981
|
+
background: var(--change-removed-bg);
|
|
982
|
+
color: var(--change-removed);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
.detail-change-badge.modified {
|
|
986
|
+
background: var(--change-modified-bg);
|
|
987
|
+
color: var(--change-modified);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
.change-section {
|
|
991
|
+
border-left: 3px solid var(--text-muted);
|
|
992
|
+
margin: 0 24px 0 24px;
|
|
993
|
+
padding: 16px 20px !important;
|
|
994
|
+
border-radius: 0 12px 12px 0;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.change-section.added {
|
|
998
|
+
background: var(--change-added-bg);
|
|
999
|
+
border-left-color: var(--change-added);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.change-section.removed {
|
|
1003
|
+
background: var(--change-removed-bg);
|
|
1004
|
+
border-left-color: var(--change-removed);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
.change-section.modified {
|
|
1008
|
+
background: var(--change-modified-bg);
|
|
1009
|
+
border-left-color: var(--change-modified);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
.change-section .detail-section-title {
|
|
1013
|
+
opacity: 1;
|
|
1014
|
+
margin-bottom: 12px;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
.change-section.added .detail-section-title { color: var(--change-added); }
|
|
1018
|
+
.change-section.removed .detail-section-title { color: var(--change-removed); }
|
|
1019
|
+
.change-section.modified .detail-section-title { color: var(--change-modified); }
|
|
1020
|
+
|
|
1021
|
+
.change-summary {
|
|
1022
|
+
font-size: 13px;
|
|
1023
|
+
color: var(--text-secondary);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
.change-item {
|
|
1027
|
+
font-size: 13px;
|
|
1028
|
+
color: var(--text-secondary);
|
|
1029
|
+
margin-bottom: 8px;
|
|
1030
|
+
display: flex;
|
|
1031
|
+
flex-wrap: wrap;
|
|
1032
|
+
align-items: center;
|
|
1033
|
+
gap: 6px;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
.change-item:last-child {
|
|
1037
|
+
margin-bottom: 0;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
.change-label {
|
|
1041
|
+
font-weight: 500;
|
|
1042
|
+
color: var(--text-primary);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
.change-old {
|
|
1046
|
+
color: var(--change-removed);
|
|
1047
|
+
text-decoration: line-through;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
.change-arrow {
|
|
1051
|
+
color: var(--text-muted);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
.change-new {
|
|
1055
|
+
color: var(--change-added);
|
|
1056
|
+
font-weight: 500;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
.change-item-block {
|
|
1060
|
+
flex-direction: column;
|
|
1061
|
+
align-items: flex-start;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
.change-description-diff {
|
|
1065
|
+
margin-top: 8px;
|
|
1066
|
+
width: 100%;
|
|
1067
|
+
font-size: 12px;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
.change-description-diff .change-old,
|
|
1071
|
+
.change-description-diff .change-new {
|
|
1072
|
+
padding: 8px 12px;
|
|
1073
|
+
border-radius: 8px;
|
|
1074
|
+
text-decoration: none;
|
|
1075
|
+
font-weight: normal;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
.change-description-diff .change-old {
|
|
1079
|
+
background: rgba(196, 69, 54, 0.08);
|
|
1080
|
+
border: 1px solid rgba(196, 69, 54, 0.2);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.change-description-diff .change-new {
|
|
1084
|
+
background: rgba(42, 157, 143, 0.08);
|
|
1085
|
+
border: 1px solid rgba(42, 157, 143, 0.2);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
.change-description-diff .change-arrow {
|
|
1089
|
+
text-align: center;
|
|
1090
|
+
padding: 4px 0;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
.detail-section {
|
|
1094
|
+
padding: 20px 24px;
|
|
1095
|
+
border-bottom: 1px solid rgba(2, 61, 96, 0.06);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
.detail-section:last-child {
|
|
1099
|
+
border-bottom: none;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
.detail-section-title {
|
|
1103
|
+
font-family: 'Roboto Slab', serif;
|
|
1104
|
+
font-size: 11px;
|
|
1105
|
+
font-weight: 600;
|
|
1106
|
+
text-transform: uppercase;
|
|
1107
|
+
letter-spacing: 0.08em;
|
|
1108
|
+
color: var(--vanna-navy);
|
|
1109
|
+
opacity: 0.6;
|
|
1110
|
+
margin-bottom: 14px;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
.tag-list {
|
|
1114
|
+
display: flex;
|
|
1115
|
+
flex-wrap: wrap;
|
|
1116
|
+
gap: 8px;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.tag {
|
|
1120
|
+
padding: 6px 12px;
|
|
1121
|
+
background: white;
|
|
1122
|
+
border: 1px solid rgba(2, 61, 96, 0.1);
|
|
1123
|
+
border-radius: 9999px;
|
|
1124
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
1125
|
+
font-size: 12px;
|
|
1126
|
+
font-weight: 500;
|
|
1127
|
+
color: var(--text-secondary);
|
|
1128
|
+
cursor: pointer;
|
|
1129
|
+
transition: all 0.2s ease;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.tag:hover {
|
|
1133
|
+
border-color: var(--vanna-teal);
|
|
1134
|
+
color: var(--vanna-teal);
|
|
1135
|
+
transform: translateY(-1px);
|
|
1136
|
+
box-shadow: 0 4px 12px rgba(21, 168, 168, 0.15);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
.tag.entity {
|
|
1140
|
+
border-color: rgba(21, 168, 168, 0.3);
|
|
1141
|
+
background: rgba(21, 168, 168, 0.08);
|
|
1142
|
+
color: var(--vanna-teal);
|
|
1143
|
+
}
|
|
1144
|
+
.tag.function {
|
|
1145
|
+
border-color: rgba(2, 61, 96, 0.2);
|
|
1146
|
+
background: rgba(2, 61, 96, 0.05);
|
|
1147
|
+
color: var(--vanna-navy);
|
|
1148
|
+
}
|
|
1149
|
+
.tag.access {
|
|
1150
|
+
border-color: rgba(191, 19, 99, 0.3);
|
|
1151
|
+
background: rgba(191, 19, 99, 0.08);
|
|
1152
|
+
color: var(--vanna-magenta);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
.schema-viewer {
|
|
1156
|
+
background: linear-gradient(135deg, #faf9f6, #f5f3ed);
|
|
1157
|
+
border: 1px solid rgba(2, 61, 96, 0.08);
|
|
1158
|
+
border-radius: 12px;
|
|
1159
|
+
padding: 16px;
|
|
1160
|
+
font-family: 'Space Mono', monospace;
|
|
1161
|
+
font-size: 12px;
|
|
1162
|
+
line-height: 1.7;
|
|
1163
|
+
overflow-x: auto;
|
|
1164
|
+
color: var(--vanna-navy);
|
|
1165
|
+
max-height: 200px;
|
|
1166
|
+
overflow-y: auto;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
.function-list {
|
|
1170
|
+
list-style: none;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
.function-list li {
|
|
1174
|
+
padding: 10px 0;
|
|
1175
|
+
border-bottom: 1px solid rgba(2, 61, 96, 0.06);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
.function-list li:last-child {
|
|
1179
|
+
border-bottom: none;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
.function-link {
|
|
1183
|
+
color: var(--vanna-navy);
|
|
1184
|
+
text-decoration: none;
|
|
1185
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
1186
|
+
font-size: 13px;
|
|
1187
|
+
font-weight: 500;
|
|
1188
|
+
cursor: pointer;
|
|
1189
|
+
transition: color 0.2s ease;
|
|
1190
|
+
display: flex;
|
|
1191
|
+
align-items: center;
|
|
1192
|
+
gap: 8px;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
.function-link:hover {
|
|
1196
|
+
color: var(--vanna-teal);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
.function-cards {
|
|
1200
|
+
display: flex;
|
|
1201
|
+
flex-direction: column;
|
|
1202
|
+
gap: 10px;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
.function-card {
|
|
1206
|
+
background: white;
|
|
1207
|
+
border: 1px solid rgba(2, 61, 96, 0.1);
|
|
1208
|
+
border-radius: 12px;
|
|
1209
|
+
padding: 14px;
|
|
1210
|
+
cursor: pointer;
|
|
1211
|
+
transition: all 0.2s ease;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
.function-card:hover {
|
|
1215
|
+
border-color: var(--vanna-teal);
|
|
1216
|
+
transform: translateY(-1px);
|
|
1217
|
+
box-shadow: 0 4px 12px rgba(21, 168, 168, 0.12);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
.function-card-header {
|
|
1221
|
+
display: flex;
|
|
1222
|
+
align-items: center;
|
|
1223
|
+
justify-content: space-between;
|
|
1224
|
+
margin-bottom: 6px;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
.function-card-name {
|
|
1228
|
+
font-family: 'Space Mono', monospace;
|
|
1229
|
+
font-size: 13px;
|
|
1230
|
+
font-weight: 500;
|
|
1231
|
+
color: var(--vanna-navy);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
.function-card-desc {
|
|
1235
|
+
font-size: 12px;
|
|
1236
|
+
color: var(--text-muted);
|
|
1237
|
+
line-height: 1.4;
|
|
1238
|
+
margin-bottom: 8px;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
.function-card-returns {
|
|
1242
|
+
display: flex;
|
|
1243
|
+
align-items: center;
|
|
1244
|
+
gap: 6px;
|
|
1245
|
+
padding-top: 8px;
|
|
1246
|
+
border-top: 1px solid rgba(2, 61, 96, 0.06);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
.returns-label {
|
|
1250
|
+
font-size: 11px;
|
|
1251
|
+
color: var(--text-muted);
|
|
1252
|
+
text-transform: uppercase;
|
|
1253
|
+
letter-spacing: 0.05em;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
.returns-type {
|
|
1257
|
+
font-family: 'Space Mono', monospace;
|
|
1258
|
+
font-size: 11px;
|
|
1259
|
+
color: var(--vanna-teal);
|
|
1260
|
+
background: rgba(21, 168, 168, 0.1);
|
|
1261
|
+
padding: 2px 8px;
|
|
1262
|
+
border-radius: 4px;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.returns-section .returns-display {
|
|
1266
|
+
margin-bottom: 12px;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
.returns-type-large {
|
|
1270
|
+
font-family: 'Space Mono', monospace;
|
|
1271
|
+
font-size: 14px;
|
|
1272
|
+
font-weight: 500;
|
|
1273
|
+
color: var(--vanna-teal);
|
|
1274
|
+
background: rgba(21, 168, 168, 0.1);
|
|
1275
|
+
padding: 8px 14px;
|
|
1276
|
+
border-radius: 8px;
|
|
1277
|
+
display: inline-block;
|
|
1278
|
+
border: 1px solid rgba(21, 168, 168, 0.2);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
.dependency-item {
|
|
1282
|
+
display: flex;
|
|
1283
|
+
align-items: center;
|
|
1284
|
+
gap: 10px;
|
|
1285
|
+
padding: 10px 0;
|
|
1286
|
+
border-bottom: 1px solid rgba(2, 61, 96, 0.06);
|
|
1287
|
+
font-size: 13px;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
.dependency-item:last-child {
|
|
1291
|
+
border-bottom: none;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
.dependency-path {
|
|
1295
|
+
font-family: 'Space Mono', monospace;
|
|
1296
|
+
font-size: 11px;
|
|
1297
|
+
color: var(--vanna-teal);
|
|
1298
|
+
background: rgba(21, 168, 168, 0.1);
|
|
1299
|
+
padding: 3px 8px;
|
|
1300
|
+
border-radius: 6px;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
.no-data {
|
|
1304
|
+
font-size: 13px;
|
|
1305
|
+
color: var(--text-muted);
|
|
1306
|
+
font-style: italic;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/* Keyboard hint */
|
|
1310
|
+
.kbd {
|
|
1311
|
+
display: inline-block;
|
|
1312
|
+
padding: 3px 8px;
|
|
1313
|
+
background: white;
|
|
1314
|
+
border: 1px solid rgba(2, 61, 96, 0.15);
|
|
1315
|
+
border-radius: 6px;
|
|
1316
|
+
font-family: 'Space Mono', monospace;
|
|
1317
|
+
font-size: 11px;
|
|
1318
|
+
color: var(--vanna-navy);
|
|
1319
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/* Search Results Dropdown */
|
|
1323
|
+
.search-results {
|
|
1324
|
+
position: absolute;
|
|
1325
|
+
top: 100%;
|
|
1326
|
+
left: 0;
|
|
1327
|
+
right: 0;
|
|
1328
|
+
background: white;
|
|
1329
|
+
border: 1px solid rgba(21, 168, 168, 0.2);
|
|
1330
|
+
border-radius: 16px;
|
|
1331
|
+
margin-top: 8px;
|
|
1332
|
+
max-height: 320px;
|
|
1333
|
+
overflow-y: auto;
|
|
1334
|
+
z-index: 9999;
|
|
1335
|
+
box-shadow: 0 25px 55px rgba(21, 168, 168, 0.18);
|
|
1336
|
+
display: none;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.search-results.visible {
|
|
1340
|
+
display: block;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
.search-result-item {
|
|
1344
|
+
padding: 12px 16px;
|
|
1345
|
+
cursor: pointer;
|
|
1346
|
+
display: flex;
|
|
1347
|
+
align-items: center;
|
|
1348
|
+
gap: 12px;
|
|
1349
|
+
border-bottom: 1px solid rgba(2, 61, 96, 0.06);
|
|
1350
|
+
transition: all 0.15s ease;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
.search-result-item:last-child {
|
|
1354
|
+
border-bottom: none;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
.search-result-item:hover {
|
|
1358
|
+
background: rgba(21, 168, 168, 0.06);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
.search-result-type {
|
|
1362
|
+
width: 10px;
|
|
1363
|
+
height: 10px;
|
|
1364
|
+
border-radius: 50%;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
.search-result-type.entity { background: var(--node-entity); }
|
|
1368
|
+
.search-result-type.function { background: var(--node-function); }
|
|
1369
|
+
.search-result-type.accessGroup { background: var(--node-access); }
|
|
1370
|
+
|
|
1371
|
+
.search-result-label {
|
|
1372
|
+
flex: 1;
|
|
1373
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
1374
|
+
font-size: 14px;
|
|
1375
|
+
font-weight: 500;
|
|
1376
|
+
color: var(--vanna-navy);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
.search-result-match {
|
|
1380
|
+
font-size: 11px;
|
|
1381
|
+
color: var(--text-muted);
|
|
1382
|
+
background: rgba(2, 61, 96, 0.05);
|
|
1383
|
+
padding: 2px 8px;
|
|
1384
|
+
border-radius: 9999px;
|
|
1385
|
+
}
|
|
1386
|
+
</style>
|
|
1387
|
+
</head>
|
|
1388
|
+
<body>
|
|
1389
|
+
<div class="layout">
|
|
1390
|
+
<header class="header">
|
|
1391
|
+
<div class="logo">
|
|
1392
|
+
<div class="logo-icon">
|
|
1393
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" zoomAndPan="magnify" viewBox="0 0 375 374.999991" height="500" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><g/><clipPath id="80f08af47a"><path d="M 4.902344 5.25 L 370.152344 5.25 L 370.152344 370.5 L 4.902344 370.5 Z M 4.902344 5.25 " clip-rule="nonzero"/></clipPath><clipPath id="723b9215da"><path d="M 103.148438 5.25 L 271.851562 5.25 C 273.457031 5.25 275.066406 5.289062 276.671875 5.367188 C 278.277344 5.445312 279.878906 5.566406 281.480469 5.722656 C 283.078125 5.878906 284.675781 6.078125 286.265625 6.3125 C 287.855469 6.546875 289.441406 6.824219 291.015625 7.136719 C 292.59375 7.449219 294.160156 7.800781 295.722656 8.191406 C 297.28125 8.582031 298.832031 9.011719 300.367188 9.480469 C 301.90625 9.945312 303.433594 10.449219 304.949219 10.992188 C 306.460938 11.535156 307.960938 12.113281 309.445312 12.726562 C 310.933594 13.34375 312.402344 13.996094 313.855469 14.679688 C 315.308594 15.367188 316.746094 16.089844 318.164062 16.851562 C 319.582031 17.609375 320.980469 18.398438 322.359375 19.226562 C 323.738281 20.054688 325.09375 20.914062 326.433594 21.804688 C 327.769531 22.699219 329.082031 23.625 330.375 24.582031 C 331.667969 25.539062 332.933594 26.53125 334.175781 27.550781 C 335.417969 28.570312 336.636719 29.621094 337.828125 30.699219 C 339.019531 31.777344 340.183594 32.886719 341.320312 34.023438 C 342.457031 35.160156 343.566406 36.324219 344.644531 37.515625 C 345.726562 38.707031 346.773438 39.925781 347.792969 41.167969 C 348.816406 42.410156 349.804688 43.679688 350.761719 44.96875 C 351.71875 46.261719 352.644531 47.574219 353.539062 48.914062 C 354.429688 50.25 355.292969 51.605469 356.117188 52.984375 C 356.945312 54.367188 357.738281 55.765625 358.496094 57.183594 C 359.253906 58.601562 359.976562 60.035156 360.664062 61.488281 C 361.351562 62.941406 362 64.414062 362.617188 65.898438 C 363.230469 67.382812 363.8125 68.882812 364.351562 70.398438 C 364.894531 71.910156 365.398438 73.4375 365.863281 74.976562 C 366.332031 76.515625 366.761719 78.0625 367.152344 79.621094 C 367.542969 81.183594 367.894531 82.75 368.207031 84.328125 C 368.519531 85.90625 368.796875 87.488281 369.03125 89.078125 C 369.269531 90.667969 369.464844 92.265625 369.621094 93.863281 C 369.78125 95.464844 369.898438 97.066406 369.976562 98.675781 C 370.054688 100.28125 370.09375 101.886719 370.09375 103.496094 L 370.09375 272.195312 C 370.09375 273.800781 370.054688 275.410156 369.976562 277.015625 C 369.898438 278.621094 369.78125 280.222656 369.621094 281.824219 C 369.464844 283.425781 369.269531 285.019531 369.03125 286.609375 C 368.796875 288.199219 368.519531 289.785156 368.207031 291.363281 C 367.894531 292.9375 367.542969 294.507812 367.152344 296.066406 C 366.761719 297.625 366.332031 299.175781 365.863281 300.714844 C 365.398438 302.253906 364.894531 303.777344 364.351562 305.292969 C 363.8125 306.804688 363.230469 308.304688 362.617188 309.792969 C 362 311.277344 361.351562 312.746094 360.664062 314.199219 C 359.976562 315.652344 359.253906 317.089844 358.496094 318.507812 C 357.738281 319.925781 356.945312 321.324219 356.117188 322.703125 C 355.292969 324.082031 354.429688 325.441406 353.539062 326.777344 C 352.644531 328.113281 351.71875 329.429688 350.761719 330.71875 C 349.804688 332.011719 348.816406 333.277344 347.792969 334.519531 C 346.773438 335.765625 345.726562 336.980469 344.644531 338.171875 C 343.566406 339.363281 342.457031 340.527344 341.320312 341.664062 C 340.183594 342.800781 339.019531 343.910156 337.828125 344.988281 C 336.636719 346.070312 335.417969 347.121094 334.175781 348.140625 C 332.933594 349.160156 331.667969 350.148438 330.375 351.105469 C 329.082031 352.0625 327.769531 352.988281 326.433594 353.882812 C 325.09375 354.777344 323.738281 355.636719 322.359375 356.460938 C 320.980469 357.289062 319.582031 358.082031 318.164062 358.839844 C 316.746094 359.597656 315.308594 360.320312 313.855469 361.007812 C 312.402344 361.695312 310.933594 362.347656 309.445312 362.960938 C 307.960938 363.578125 306.460938 364.15625 304.949219 364.695312 C 303.433594 365.238281 301.90625 365.742188 300.367188 366.210938 C 298.832031 366.675781 297.28125 367.105469 295.722656 367.496094 C 294.160156 367.886719 292.59375 368.238281 291.015625 368.550781 C 289.441406 368.867188 287.855469 369.140625 286.265625 369.378906 C 284.675781 369.613281 283.078125 369.808594 281.480469 369.96875 C 279.878906 370.125 278.277344 370.242188 276.671875 370.320312 C 275.066406 370.402344 273.457031 370.441406 271.851562 370.441406 L 103.148438 370.441406 C 101.542969 370.441406 99.933594 370.402344 98.328125 370.320312 C 96.722656 370.242188 95.121094 370.125 93.519531 369.96875 C 91.921875 369.808594 90.324219 369.613281 88.734375 369.378906 C 87.144531 369.140625 85.558594 368.867188 83.984375 368.550781 C 82.40625 368.238281 80.835938 367.886719 79.277344 367.496094 C 77.71875 367.105469 76.167969 366.675781 74.628906 366.210938 C 73.09375 365.742188 71.566406 365.238281 70.050781 364.695312 C 68.539062 364.15625 67.039062 363.578125 65.554688 362.960938 C 64.066406 362.347656 62.597656 361.695312 61.144531 361.007812 C 59.691406 360.320312 58.253906 359.597656 56.835938 358.839844 C 55.417969 358.082031 54.019531 357.289062 52.640625 356.460938 C 51.261719 355.636719 49.90625 354.777344 48.566406 353.882812 C 47.230469 352.988281 45.917969 352.0625 44.625 351.105469 C 43.332031 350.148438 42.066406 349.160156 40.824219 348.140625 C 39.582031 347.121094 38.363281 346.070312 37.171875 344.988281 C 35.980469 343.910156 34.816406 342.800781 33.679688 341.664062 C 32.542969 340.527344 31.433594 339.363281 30.355469 338.171875 C 29.273438 336.980469 28.226562 335.765625 27.203125 334.519531 C 26.183594 333.277344 25.195312 332.011719 24.238281 330.71875 C 23.28125 329.429688 22.355469 328.113281 21.460938 326.777344 C 20.566406 325.441406 19.707031 324.082031 18.882812 322.703125 C 18.054688 321.324219 17.261719 319.925781 16.503906 318.507812 C 15.746094 317.089844 15.023438 315.652344 14.335938 314.199219 C 13.648438 312.746094 12.996094 311.277344 12.382812 309.792969 C 11.765625 308.304688 11.1875 306.804688 10.648438 305.292969 C 10.105469 303.777344 9.601562 302.253906 9.132812 300.714844 C 8.667969 299.175781 8.238281 297.625 7.847656 296.066406 C 7.457031 294.507812 7.105469 292.9375 6.792969 291.363281 C 6.476562 289.785156 6.203125 288.199219 5.96875 286.609375 C 5.730469 285.019531 5.535156 283.425781 5.378906 281.824219 C 5.21875 280.222656 5.101562 278.621094 5.023438 277.015625 C 4.945312 275.410156 4.902344 273.800781 4.902344 272.195312 L 4.902344 103.496094 C 4.902344 101.886719 4.945312 100.28125 5.023438 98.675781 C 5.101562 97.066406 5.21875 95.464844 5.378906 93.863281 C 5.535156 92.265625 5.730469 90.667969 5.96875 89.078125 C 6.203125 87.488281 6.476562 85.90625 6.792969 84.328125 C 7.105469 82.75 7.457031 81.183594 7.847656 79.621094 C 8.238281 78.0625 8.667969 76.515625 9.132812 74.976562 C 9.601562 73.4375 10.105469 71.910156 10.648438 70.398438 C 11.1875 68.882812 11.765625 67.382812 12.382812 65.898438 C 12.996094 64.414062 13.648438 62.941406 14.335938 61.488281 C 15.023438 60.035156 15.746094 58.601562 16.503906 57.183594 C 17.261719 55.765625 18.054688 54.367188 18.882812 52.984375 C 19.707031 51.605469 20.566406 50.25 21.460938 48.914062 C 22.355469 47.574219 23.28125 46.261719 24.238281 44.96875 C 25.195312 43.679688 26.183594 42.410156 27.203125 41.167969 C 28.226562 39.925781 29.273438 38.707031 30.355469 37.515625 C 31.433594 36.324219 32.542969 35.160156 33.679688 34.023438 C 34.816406 32.886719 35.980469 31.777344 37.171875 30.699219 C 38.363281 29.621094 39.582031 28.570312 40.824219 27.550781 C 42.066406 26.53125 43.332031 25.539062 44.625 24.582031 C 45.917969 23.625 47.230469 22.699219 48.566406 21.804688 C 49.90625 20.914062 51.261719 20.054688 52.640625 19.226562 C 54.019531 18.398438 55.417969 17.609375 56.835938 16.851562 C 58.253906 16.089844 59.691406 15.367188 61.144531 14.679688 C 62.597656 13.996094 64.066406 13.34375 65.554688 12.726562 C 67.039062 12.113281 68.539062 11.535156 70.050781 10.992188 C 71.566406 10.449219 73.09375 9.945312 74.628906 9.480469 C 76.167969 9.011719 77.71875 8.582031 79.277344 8.191406 C 80.835938 7.800781 82.40625 7.449219 83.984375 7.136719 C 85.558594 6.824219 87.144531 6.546875 88.734375 6.3125 C 90.324219 6.078125 91.921875 5.878906 93.519531 5.722656 C 95.121094 5.566406 96.722656 5.445312 98.328125 5.367188 C 99.933594 5.289062 101.542969 5.25 103.148438 5.25 Z M 103.148438 5.25 " clip-rule="nonzero"/></clipPath><linearGradient x1="21.35189" gradientTransform="matrix(0, -1.547421, 1.547421, 0, 4.904122, 370.440392)" y1="-15.46228" x2="214.648932" gradientUnits="userSpaceOnUse" y2="251.461714" id="fc3e7af47d"><stop stop-opacity="1" stop-color="rgb(1.33667%, 27.052307%, 39.756775%)" offset="0"/><stop stop-opacity="1" stop-color="rgb(1.371765%, 27.253723%, 39.892578%)" offset="0.00390625"/><stop stop-opacity="1" stop-color="rgb(1.408386%, 27.456665%, 40.029907%)" offset="0.0078125"/><stop stop-opacity="1" stop-color="rgb(1.443481%, 27.659607%, 40.16571%)" offset="0.0117187"/><stop stop-opacity="1" stop-color="rgb(1.480103%, 27.862549%, 40.301514%)" offset="0.015625"/><stop stop-opacity="1" stop-color="rgb(1.515198%, 28.065491%, 40.437317%)" offset="0.0195312"/><stop stop-opacity="1" stop-color="rgb(1.551819%, 28.268433%, 40.574646%)" offset="0.0234375"/><stop stop-opacity="1" stop-color="rgb(1.58844%, 28.469849%, 40.710449%)" offset="0.0273437"/><stop stop-opacity="1" stop-color="rgb(1.625061%, 28.672791%, 40.847778%)" offset="0.03125"/><stop stop-opacity="1" stop-color="rgb(1.660156%, 28.875732%, 40.983582%)" offset="0.0351563"/><stop stop-opacity="1" stop-color="rgb(1.696777%, 29.078674%, 41.120911%)" offset="0.0390625"/><stop stop-opacity="1" stop-color="rgb(1.731873%, 29.28009%, 41.256714%)" offset="0.0429688"/><stop stop-opacity="1" stop-color="rgb(1.768494%, 29.483032%, 41.392517%)" offset="0.046875"/><stop stop-opacity="1" stop-color="rgb(1.803589%, 29.685974%, 41.52832%)" offset="0.0507812"/><stop stop-opacity="1" stop-color="rgb(1.84021%, 29.888916%, 41.665649%)" offset="0.0546875"/><stop stop-opacity="1" stop-color="rgb(1.875305%, 30.091858%, 41.801453%)" offset="0.0585938"/><stop stop-opacity="1" stop-color="rgb(1.911926%, 30.2948%, 41.938782%)" offset="0.0625"/><stop stop-opacity="1" stop-color="rgb(1.948547%, 30.496216%, 42.074585%)" offset="0.0664063"/><stop stop-opacity="1" stop-color="rgb(1.985168%, 30.699158%, 42.210388%)" offset="0.0703125"/><stop stop-opacity="1" stop-color="rgb(2.020264%, 30.9021%, 42.346191%)" offset="0.0742188"/><stop stop-opacity="1" stop-color="rgb(2.056885%, 31.105042%, 42.483521%)" offset="0.078125"/><stop stop-opacity="1" stop-color="rgb(2.09198%, 31.307983%, 42.619324%)" offset="0.0820312"/><stop stop-opacity="1" stop-color="rgb(2.128601%, 31.510925%, 42.756653%)" offset="0.0859375"/><stop stop-opacity="1" stop-color="rgb(2.163696%, 31.712341%, 42.892456%)" offset="0.0898438"/><stop stop-opacity="1" stop-color="rgb(2.200317%, 31.915283%, 43.028259%)" offset="0.09375"/><stop stop-opacity="1" stop-color="rgb(2.236938%, 32.118225%, 43.164062%)" offset="0.0976563"/><stop stop-opacity="1" stop-color="rgb(2.27356%, 32.321167%, 43.301392%)" offset="0.101562"/><stop stop-opacity="1" stop-color="rgb(2.308655%, 32.522583%, 43.437195%)" offset="0.105469"/><stop stop-opacity="1" stop-color="rgb(2.345276%, 32.725525%, 43.574524%)" offset="0.109375"/><stop stop-opacity="1" stop-color="rgb(2.380371%, 32.928467%, 43.710327%)" offset="0.113281"/><stop stop-opacity="1" stop-color="rgb(2.416992%, 33.131409%, 43.847656%)" offset="0.117188"/><stop stop-opacity="1" stop-color="rgb(2.452087%, 33.334351%, 43.983459%)" offset="0.121094"/><stop stop-opacity="1" stop-color="rgb(2.488708%, 33.537292%, 44.119263%)" offset="0.125"/><stop stop-opacity="1" stop-color="rgb(2.523804%, 33.738708%, 44.255066%)" offset="0.128906"/><stop stop-opacity="1" stop-color="rgb(2.560425%, 33.94165%, 44.392395%)" offset="0.132813"/><stop stop-opacity="1" stop-color="rgb(2.597046%, 34.144592%, 44.528198%)" offset="0.136719"/><stop stop-opacity="1" stop-color="rgb(2.633667%, 34.347534%, 44.665527%)" offset="0.140625"/><stop stop-opacity="1" stop-color="rgb(2.668762%, 34.54895%, 44.801331%)" offset="0.144531"/><stop stop-opacity="1" stop-color="rgb(2.705383%, 34.751892%, 44.937134%)" offset="0.148438"/><stop stop-opacity="1" stop-color="rgb(2.740479%, 34.954834%, 45.072937%)" offset="0.152344"/><stop stop-opacity="1" stop-color="rgb(2.7771%, 35.157776%, 45.210266%)" offset="0.15625"/><stop stop-opacity="1" stop-color="rgb(2.812195%, 35.360718%, 45.346069%)" offset="0.160156"/><stop stop-opacity="1" stop-color="rgb(2.848816%, 35.56366%, 45.483398%)" offset="0.164063"/><stop stop-opacity="1" stop-color="rgb(2.885437%, 35.765076%, 45.619202%)" offset="0.167969"/><stop stop-opacity="1" stop-color="rgb(2.922058%, 35.968018%, 45.756531%)" offset="0.171875"/><stop stop-opacity="1" stop-color="rgb(2.957153%, 36.170959%, 45.892334%)" offset="0.175781"/><stop stop-opacity="1" stop-color="rgb(2.993774%, 36.373901%, 46.028137%)" offset="0.179688"/><stop stop-opacity="1" stop-color="rgb(3.02887%, 36.576843%, 46.16394%)" offset="0.183594"/><stop stop-opacity="1" stop-color="rgb(3.065491%, 36.779785%, 46.30127%)" offset="0.1875"/><stop stop-opacity="1" stop-color="rgb(3.100586%, 36.981201%, 46.437073%)" offset="0.191406"/><stop stop-opacity="1" stop-color="rgb(3.137207%, 37.184143%, 46.574402%)" offset="0.195312"/><stop stop-opacity="1" stop-color="rgb(3.173828%, 37.387085%, 46.710205%)" offset="0.199219"/><stop stop-opacity="1" stop-color="rgb(3.210449%, 37.590027%, 46.846008%)" offset="0.203125"/><stop stop-opacity="1" stop-color="rgb(3.245544%, 37.791443%, 46.981812%)" offset="0.207031"/><stop stop-opacity="1" stop-color="rgb(3.282166%, 37.994385%, 47.119141%)" offset="0.210938"/><stop stop-opacity="1" stop-color="rgb(3.317261%, 38.197327%, 47.254944%)" offset="0.214844"/><stop stop-opacity="1" stop-color="rgb(3.353882%, 38.400269%, 47.392273%)" offset="0.21875"/><stop stop-opacity="1" stop-color="rgb(3.388977%, 38.60321%, 47.528076%)" offset="0.222656"/><stop stop-opacity="1" stop-color="rgb(3.425598%, 38.806152%, 47.663879%)" offset="0.226563"/><stop stop-opacity="1" stop-color="rgb(3.460693%, 39.007568%, 47.799683%)" offset="0.230469"/><stop stop-opacity="1" stop-color="rgb(3.497314%, 39.21051%, 47.937012%)" offset="0.234375"/><stop stop-opacity="1" stop-color="rgb(3.533936%, 39.413452%, 48.072815%)" offset="0.238281"/><stop stop-opacity="1" stop-color="rgb(3.570557%, 39.616394%, 48.210144%)" offset="0.242188"/><stop stop-opacity="1" stop-color="rgb(3.605652%, 39.819336%, 48.345947%)" offset="0.246094"/><stop stop-opacity="1" stop-color="rgb(3.642273%, 40.022278%, 48.483276%)" offset="0.25"/><stop stop-opacity="1" stop-color="rgb(3.677368%, 40.223694%, 48.61908%)" offset="0.253906"/><stop stop-opacity="1" stop-color="rgb(3.713989%, 40.426636%, 48.754883%)" offset="0.257812"/><stop stop-opacity="1" stop-color="rgb(3.749084%, 40.629578%, 48.890686%)" offset="0.261719"/><stop stop-opacity="1" stop-color="rgb(3.785706%, 40.83252%, 49.028015%)" offset="0.265625"/><stop stop-opacity="1" stop-color="rgb(3.822327%, 41.033936%, 49.163818%)" offset="0.269531"/><stop stop-opacity="1" stop-color="rgb(3.858948%, 41.236877%, 49.301147%)" offset="0.273438"/><stop stop-opacity="1" stop-color="rgb(3.894043%, 41.439819%, 49.436951%)" offset="0.277344"/><stop stop-opacity="1" stop-color="rgb(3.930664%, 41.642761%, 49.572754%)" offset="0.28125"/><stop stop-opacity="1" stop-color="rgb(3.965759%, 41.845703%, 49.708557%)" offset="0.285156"/><stop stop-opacity="1" stop-color="rgb(4.00238%, 42.048645%, 49.845886%)" offset="0.289063"/><stop stop-opacity="1" stop-color="rgb(4.037476%, 42.250061%, 49.981689%)" offset="0.292969"/><stop stop-opacity="1" stop-color="rgb(4.074097%, 42.453003%, 50.119019%)" offset="0.296875"/><stop stop-opacity="1" stop-color="rgb(4.109192%, 42.655945%, 50.254822%)" offset="0.300781"/><stop stop-opacity="1" stop-color="rgb(4.145813%, 42.858887%, 50.392151%)" offset="0.304688"/><stop stop-opacity="1" stop-color="rgb(4.182434%, 43.060303%, 50.527954%)" offset="0.308594"/><stop stop-opacity="1" stop-color="rgb(4.219055%, 43.263245%, 50.663757%)" offset="0.3125"/><stop stop-opacity="1" stop-color="rgb(4.25415%, 43.466187%, 50.799561%)" offset="0.316406"/><stop stop-opacity="1" stop-color="rgb(4.290771%, 43.669128%, 50.93689%)" offset="0.320313"/><stop stop-opacity="1" stop-color="rgb(4.325867%, 43.87207%, 51.072693%)" offset="0.324219"/><stop stop-opacity="1" stop-color="rgb(4.362488%, 44.075012%, 51.210022%)" offset="0.328125"/><stop stop-opacity="1" stop-color="rgb(4.397583%, 44.276428%, 51.345825%)" offset="0.332031"/><stop stop-opacity="1" stop-color="rgb(4.434204%, 44.47937%, 51.481628%)" offset="0.335938"/><stop stop-opacity="1" stop-color="rgb(4.470825%, 44.682312%, 51.617432%)" offset="0.339844"/><stop stop-opacity="1" stop-color="rgb(4.507446%, 44.885254%, 51.754761%)" offset="0.34375"/><stop stop-opacity="1" stop-color="rgb(4.542542%, 45.088196%, 51.890564%)" offset="0.347656"/><stop stop-opacity="1" stop-color="rgb(4.579163%, 45.291138%, 52.027893%)" offset="0.351562"/><stop stop-opacity="1" stop-color="rgb(4.614258%, 45.492554%, 52.163696%)" offset="0.355469"/><stop stop-opacity="1" stop-color="rgb(4.650879%, 45.695496%, 52.2995%)" offset="0.359375"/><stop stop-opacity="1" stop-color="rgb(4.685974%, 45.898438%, 52.435303%)" offset="0.363281"/><stop stop-opacity="1" stop-color="rgb(4.722595%, 46.101379%, 52.572632%)" offset="0.367188"/><stop stop-opacity="1" stop-color="rgb(4.75769%, 46.302795%, 52.708435%)" offset="0.371094"/><stop stop-opacity="1" stop-color="rgb(4.794312%, 46.505737%, 52.845764%)" offset="0.375"/><stop stop-opacity="1" stop-color="rgb(4.830933%, 46.708679%, 52.981567%)" offset="0.378906"/><stop stop-opacity="1" stop-color="rgb(4.867554%, 46.911621%, 53.118896%)" offset="0.382813"/><stop stop-opacity="1" stop-color="rgb(4.902649%, 47.114563%, 53.2547%)" offset="0.386719"/><stop stop-opacity="1" stop-color="rgb(4.93927%, 47.317505%, 53.390503%)" offset="0.390625"/><stop stop-opacity="1" stop-color="rgb(4.974365%, 47.518921%, 53.526306%)" offset="0.394531"/><stop stop-opacity="1" stop-color="rgb(5.010986%, 47.721863%, 53.663635%)" offset="0.398438"/><stop stop-opacity="1" stop-color="rgb(5.046082%, 47.924805%, 53.799438%)" offset="0.402344"/><stop stop-opacity="1" stop-color="rgb(5.082703%, 48.127747%, 53.936768%)" offset="0.40625"/><stop stop-opacity="1" stop-color="rgb(5.119324%, 48.330688%, 54.072571%)" offset="0.410156"/><stop stop-opacity="1" stop-color="rgb(5.155945%, 48.53363%, 54.208374%)" offset="0.414063"/><stop stop-opacity="1" stop-color="rgb(5.19104%, 48.735046%, 54.344177%)" offset="0.417969"/><stop stop-opacity="1" stop-color="rgb(5.227661%, 48.937988%, 54.481506%)" offset="0.421875"/><stop stop-opacity="1" stop-color="rgb(5.262756%, 49.14093%, 54.61731%)" offset="0.425781"/><stop stop-opacity="1" stop-color="rgb(5.299377%, 49.343872%, 54.754639%)" offset="0.429688"/><stop stop-opacity="1" stop-color="rgb(5.334473%, 49.545288%, 54.890442%)" offset="0.433594"/><stop stop-opacity="1" stop-color="rgb(5.371094%, 49.74823%, 55.026245%)" offset="0.4375"/><stop stop-opacity="1" stop-color="rgb(5.406189%, 49.951172%, 55.162048%)" offset="0.441406"/><stop stop-opacity="1" stop-color="rgb(5.44281%, 50.154114%, 55.299377%)" offset="0.445313"/><stop stop-opacity="1" stop-color="rgb(5.479431%, 50.357056%, 55.435181%)" offset="0.449219"/><stop stop-opacity="1" stop-color="rgb(5.516052%, 50.559998%, 55.57251%)" offset="0.453125"/><stop stop-opacity="1" stop-color="rgb(5.551147%, 50.761414%, 55.708313%)" offset="0.457031"/><stop stop-opacity="1" stop-color="rgb(5.587769%, 50.964355%, 55.845642%)" offset="0.460938"/><stop stop-opacity="1" stop-color="rgb(5.622864%, 51.167297%, 55.981445%)" offset="0.464844"/><stop stop-opacity="1" stop-color="rgb(5.659485%, 51.370239%, 56.117249%)" offset="0.46875"/><stop stop-opacity="1" stop-color="rgb(5.69458%, 51.573181%, 56.253052%)" offset="0.472656"/><stop stop-opacity="1" stop-color="rgb(5.731201%, 51.776123%, 56.390381%)" offset="0.476563"/><stop stop-opacity="1" stop-color="rgb(5.767822%, 51.977539%, 56.526184%)" offset="0.480469"/><stop stop-opacity="1" stop-color="rgb(5.804443%, 52.180481%, 56.663513%)" offset="0.484375"/><stop stop-opacity="1" stop-color="rgb(5.839539%, 52.383423%, 56.799316%)" offset="0.488281"/><stop stop-opacity="1" stop-color="rgb(5.87616%, 52.586365%, 56.93512%)" offset="0.492188"/><stop stop-opacity="1" stop-color="rgb(5.911255%, 52.787781%, 57.070923%)" offset="0.496094"/><stop stop-opacity="1" stop-color="rgb(5.947876%, 52.990723%, 57.208252%)" offset="0.5"/><stop stop-opacity="1" stop-color="rgb(5.982971%, 53.193665%, 57.344055%)" offset="0.503906"/><stop stop-opacity="1" stop-color="rgb(6.019592%, 53.396606%, 57.481384%)" offset="0.507813"/><stop stop-opacity="1" stop-color="rgb(6.054688%, 53.599548%, 57.617188%)" offset="0.511719"/><stop stop-opacity="1" stop-color="rgb(6.091309%, 53.80249%, 57.754517%)" offset="0.515625"/><stop stop-opacity="1" stop-color="rgb(6.12793%, 54.003906%, 57.89032%)" offset="0.519531"/><stop stop-opacity="1" stop-color="rgb(6.164551%, 54.206848%, 58.026123%)" offset="0.523438"/><stop stop-opacity="1" stop-color="rgb(6.199646%, 54.40979%, 58.161926%)" offset="0.527344"/><stop stop-opacity="1" stop-color="rgb(6.236267%, 54.612732%, 58.299255%)" offset="0.53125"/><stop stop-opacity="1" stop-color="rgb(6.271362%, 54.814148%, 58.435059%)" offset="0.535156"/><stop stop-opacity="1" stop-color="rgb(6.307983%, 55.01709%, 58.572388%)" offset="0.539063"/><stop stop-opacity="1" stop-color="rgb(6.343079%, 55.220032%, 58.708191%)" offset="0.542969"/><stop stop-opacity="1" stop-color="rgb(6.3797%, 55.422974%, 58.843994%)" offset="0.546875"/><stop stop-opacity="1" stop-color="rgb(6.416321%, 55.625916%, 58.979797%)" offset="0.550781"/><stop stop-opacity="1" stop-color="rgb(6.452942%, 55.828857%, 59.117126%)" offset="0.554688"/><stop stop-opacity="1" stop-color="rgb(6.488037%, 56.030273%, 59.25293%)" offset="0.558594"/><stop stop-opacity="1" stop-color="rgb(6.524658%, 56.233215%, 59.390259%)" offset="0.5625"/><stop stop-opacity="1" stop-color="rgb(6.559753%, 56.436157%, 59.526062%)" offset="0.566406"/><stop stop-opacity="1" stop-color="rgb(6.596375%, 56.639099%, 59.661865%)" offset="0.570313"/><stop stop-opacity="1" stop-color="rgb(6.63147%, 56.842041%, 59.797668%)" offset="0.574219"/><stop stop-opacity="1" stop-color="rgb(6.668091%, 57.044983%, 59.934998%)" offset="0.578125"/><stop stop-opacity="1" stop-color="rgb(6.703186%, 57.246399%, 60.070801%)" offset="0.582031"/><stop stop-opacity="1" stop-color="rgb(6.739807%, 57.449341%, 60.20813%)" offset="0.585938"/><stop stop-opacity="1" stop-color="rgb(6.776428%, 57.652283%, 60.343933%)" offset="0.589844"/><stop stop-opacity="1" stop-color="rgb(6.813049%, 57.855225%, 60.481262%)" offset="0.59375"/><stop stop-opacity="1" stop-color="rgb(6.848145%, 58.056641%, 60.617065%)" offset="0.597656"/><stop stop-opacity="1" stop-color="rgb(6.884766%, 58.259583%, 60.752869%)" offset="0.601563"/><stop stop-opacity="1" stop-color="rgb(6.919861%, 58.462524%, 60.888672%)" offset="0.605469"/><stop stop-opacity="1" stop-color="rgb(6.956482%, 58.665466%, 61.026001%)" offset="0.609375"/><stop stop-opacity="1" stop-color="rgb(6.991577%, 58.868408%, 61.161804%)" offset="0.613281"/><stop stop-opacity="1" stop-color="rgb(7.028198%, 59.07135%, 61.299133%)" offset="0.617188"/><stop stop-opacity="1" stop-color="rgb(7.064819%, 59.272766%, 61.434937%)" offset="0.621094"/><stop stop-opacity="1" stop-color="rgb(7.10144%, 59.475708%, 61.57074%)" offset="0.625"/><stop stop-opacity="1" stop-color="rgb(7.136536%, 59.67865%, 61.706543%)" offset="0.628906"/><stop stop-opacity="1" stop-color="rgb(7.173157%, 59.881592%, 61.843872%)" offset="0.632813"/><stop stop-opacity="1" stop-color="rgb(7.208252%, 60.084534%, 61.979675%)" offset="0.636719"/><stop stop-opacity="1" stop-color="rgb(7.244873%, 60.287476%, 62.117004%)" offset="0.640625"/><stop stop-opacity="1" stop-color="rgb(7.279968%, 60.488892%, 62.252808%)" offset="0.644531"/><stop stop-opacity="1" stop-color="rgb(7.316589%, 60.691833%, 62.390137%)" offset="0.648438"/><stop stop-opacity="1" stop-color="rgb(7.35321%, 60.894775%, 62.52594%)" offset="0.652344"/><stop stop-opacity="1" stop-color="rgb(7.389832%, 61.097717%, 62.661743%)" offset="0.65625"/><stop stop-opacity="1" stop-color="rgb(7.424927%, 61.299133%, 62.797546%)" offset="0.660156"/><stop stop-opacity="1" stop-color="rgb(7.461548%, 61.502075%, 62.934875%)" offset="0.664063"/><stop stop-opacity="1" stop-color="rgb(7.496643%, 61.705017%, 63.070679%)" offset="0.667969"/><stop stop-opacity="1" stop-color="rgb(7.533264%, 61.907959%, 63.208008%)" offset="0.671875"/><stop stop-opacity="1" stop-color="rgb(7.568359%, 62.110901%, 63.343811%)" offset="0.675781"/><stop stop-opacity="1" stop-color="rgb(7.60498%, 62.313843%, 63.479614%)" offset="0.679688"/><stop stop-opacity="1" stop-color="rgb(7.640076%, 62.515259%, 63.615417%)" offset="0.683594"/><stop stop-opacity="1" stop-color="rgb(7.676697%, 62.718201%, 63.752747%)" offset="0.6875"/><stop stop-opacity="1" stop-color="rgb(7.713318%, 62.921143%, 63.88855%)" offset="0.691406"/><stop stop-opacity="1" stop-color="rgb(7.749939%, 63.124084%, 64.025879%)" offset="0.695313"/><stop stop-opacity="1" stop-color="rgb(7.785034%, 63.3255%, 64.161682%)" offset="0.699219"/><stop stop-opacity="1" stop-color="rgb(7.821655%, 63.528442%, 64.297485%)" offset="0.703125"/><stop stop-opacity="1" stop-color="rgb(7.85675%, 63.731384%, 64.433289%)" offset="0.707031"/><stop stop-opacity="1" stop-color="rgb(7.893372%, 63.934326%, 64.570618%)" offset="0.710938"/><stop stop-opacity="1" stop-color="rgb(7.928467%, 64.137268%, 64.706421%)" offset="0.714844"/><stop stop-opacity="1" stop-color="rgb(7.965088%, 64.34021%, 64.84375%)" offset="0.71875"/><stop stop-opacity="1" stop-color="rgb(8.001709%, 64.541626%, 64.979553%)" offset="0.722656"/><stop stop-opacity="1" stop-color="rgb(8.03833%, 64.744568%, 65.116882%)" offset="0.726563"/><stop stop-opacity="1" stop-color="rgb(8.073425%, 64.94751%, 65.252686%)" offset="0.730469"/><stop stop-opacity="1" stop-color="rgb(8.110046%, 65.150452%, 65.388489%)" offset="0.734375"/><stop stop-opacity="1" stop-color="rgb(8.145142%, 65.353394%, 65.524292%)" offset="0.738281"/><stop stop-opacity="1" stop-color="rgb(8.181763%, 65.556335%, 65.661621%)" offset="0.742188"/><stop stop-opacity="1" stop-color="rgb(8.210754%, 65.718079%, 65.769958%)" offset="0.75"/><stop stop-opacity="1" stop-color="rgb(8.239746%, 65.879822%, 65.879822%)" offset="1"/></linearGradient></defs><g clip-path="url(#80f08af47a)"><g clip-path="url(#723b9215da)"><path fill="url(#fc3e7af47d)" d="M 4.902344 370.441406 L 370.09375 370.441406 L 370.09375 5.25 L 4.902344 5.25 Z M 4.902344 370.441406 " fill-rule="nonzero"/></g></g><g fill="#ffffff" fill-opacity="1"><g transform="translate(14.595707, 240.71852)"><g><path d="M 73.75 -113.28125 C 77.832031 -113.28125 81.632812 -112.507812 85.15625 -110.96875 C 88.6875 -109.425781 91.773438 -107.332031 94.421875 -104.6875 C 97.066406 -102.039062 99.160156 -98.953125 100.703125 -95.421875 C 102.253906 -91.890625 103.03125 -88.140625 103.03125 -84.171875 L 103.03125 -29.109375 C 103.03125 -25.140625 102.253906 -21.390625 100.703125 -17.859375 C 99.160156 -14.328125 97.066406 -11.238281 94.421875 -8.59375 C 91.773438 -5.945312 88.6875 -3.851562 85.15625 -2.3125 C 81.632812 -0.769531 77.832031 0 73.75 0 L 41.515625 0 C 37.429688 0 33.625 -0.769531 30.09375 -2.3125 C 26.570312 -3.851562 23.488281 -5.945312 20.84375 -8.59375 C 18.195312 -11.238281 16.097656 -14.328125 14.546875 -17.859375 C 13.003906 -21.390625 12.234375 -25.140625 12.234375 -29.109375 L 12.234375 -84.171875 C 12.234375 -88.140625 13.003906 -91.890625 14.546875 -95.421875 C 16.097656 -98.953125 18.195312 -102.039062 20.84375 -104.6875 C 23.488281 -107.332031 26.570312 -109.425781 30.09375 -110.96875 C 33.625 -112.507812 37.429688 -113.28125 41.515625 -113.28125 Z M 80.375 -84.171875 C 80.375 -86.046875 79.738281 -87.585938 78.46875 -88.796875 C 77.195312 -90.015625 75.625 -90.625 73.75 -90.625 L 41.515625 -90.625 C 39.640625 -90.625 38.066406 -90.015625 36.796875 -88.796875 C 35.523438 -87.585938 34.890625 -86.046875 34.890625 -84.171875 L 34.890625 -29.109375 C 34.890625 -27.234375 35.523438 -25.6875 36.796875 -24.46875 C 38.066406 -23.257812 39.640625 -22.65625 41.515625 -22.65625 L 73.75 -22.65625 C 75.625 -22.65625 77.195312 -23.257812 78.46875 -24.46875 C 79.738281 -25.6875 80.375 -27.234375 80.375 -29.109375 Z M 80.375 -84.171875 "/></g></g></g><g fill="#ffffff" fill-opacity="1"><g transform="translate(129.862068, 240.71852)"><g><path d="M 80.203125 -109.8125 C 80.203125 -110.800781 80.5625 -111.65625 81.28125 -112.375 C 82 -113.09375 82.851562 -113.453125 83.84375 -113.453125 L 99.390625 -113.453125 C 100.378906 -113.453125 101.234375 -113.09375 101.953125 -112.375 C 102.671875 -111.65625 103.03125 -110.800781 103.03125 -109.8125 L 103.03125 -3.640625 C 103.03125 -2.648438 102.671875 -1.796875 101.953125 -1.078125 C 101.234375 -0.359375 100.378906 0 99.390625 0 L 83.84375 0 C 82.851562 0 82 -0.359375 81.28125 -1.078125 C 80.5625 -1.796875 80.203125 -2.648438 80.203125 -3.640625 L 80.203125 -42.34375 L 34.890625 -83.015625 L 34.890625 -3.640625 C 34.890625 -2.648438 34.53125 -1.796875 33.8125 -1.078125 C 33.101562 -0.359375 32.25 0 31.25 0 L 15.875 0 C 14.882812 0 14.03125 -0.359375 13.3125 -1.078125 C 12.59375 -1.796875 12.234375 -2.648438 12.234375 -3.640625 L 12.234375 -109.8125 C 12.234375 -110.800781 12.59375 -111.65625 13.3125 -112.375 C 14.03125 -113.09375 14.882812 -113.453125 15.875 -113.453125 L 31.25 -113.453125 C 32.25 -113.453125 33.351562 -113.203125 34.5625 -112.703125 C 35.769531 -112.203125 36.765625 -111.625 37.546875 -110.96875 L 80.203125 -72.765625 Z M 80.203125 -109.8125 "/></g></g></g><g fill="#ffffff" fill-opacity="1"><g transform="translate(245.12843, 240.71852)"><g><path d="M 99.390625 -113.453125 C 100.378906 -113.453125 101.234375 -113.09375 101.953125 -112.375 C 102.671875 -111.65625 103.03125 -110.800781 103.03125 -109.8125 L 103.03125 -94.265625 C 103.03125 -93.273438 102.671875 -92.445312 101.953125 -91.78125 C 101.234375 -91.125 100.378906 -90.796875 99.390625 -90.796875 L 68.96875 -90.796875 L 68.96875 -3.640625 C 68.96875 -2.648438 68.609375 -1.796875 67.890625 -1.078125 C 67.171875 -0.359375 66.316406 0 65.328125 0 L 49.78125 0 C 48.789062 0 47.960938 -0.359375 47.296875 -1.078125 C 46.640625 -1.796875 46.3125 -2.648438 46.3125 -3.640625 L 46.3125 -90.796875 L 15.875 -90.796875 C 14.882812 -90.796875 14.03125 -91.125 13.3125 -91.78125 C 12.59375 -92.445312 12.234375 -93.273438 12.234375 -94.265625 L 12.234375 -109.8125 C 12.234375 -110.800781 12.59375 -111.65625 13.3125 -112.375 C 14.03125 -113.09375 14.882812 -113.453125 15.875 -113.453125 Z M 99.390625 -113.453125 "/></g></g></g></svg>
|
|
1394
|
+
</div>
|
|
1395
|
+
<span>${graphData.meta.ontologyName}</span>
|
|
1396
|
+
</div>
|
|
1397
|
+
|
|
1398
|
+
<div class="search-container">
|
|
1399
|
+
<svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1400
|
+
<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>
|
|
1401
|
+
</svg>
|
|
1402
|
+
<input type="text" class="search-input" id="searchInput" placeholder="Search nodes... (press /)" />
|
|
1403
|
+
<div class="search-results" id="searchResults"></div>
|
|
1404
|
+
</div>
|
|
1405
|
+
|
|
1406
|
+
<div class="view-tabs">
|
|
1407
|
+
<button class="view-tab active" data-view="graph">Graph</button>
|
|
1408
|
+
<button class="view-tab" data-view="table">Table</button>
|
|
1409
|
+
</div>
|
|
1410
|
+
|
|
1411
|
+
<div class="filter-buttons" id="graphFilters">
|
|
1412
|
+
<button class="filter-btn active" data-filter="all">All</button>
|
|
1413
|
+
<button class="filter-btn" data-filter="function">
|
|
1414
|
+
<span class="dot function"></span> Functions
|
|
1415
|
+
</button>
|
|
1416
|
+
<button class="filter-btn" data-filter="entity">
|
|
1417
|
+
<span class="dot entity"></span> Entities
|
|
1418
|
+
</button>
|
|
1419
|
+
<button class="filter-btn" data-filter="accessGroup">
|
|
1420
|
+
<span class="dot access"></span> Access
|
|
1421
|
+
</button>
|
|
1422
|
+
</div>
|
|
1423
|
+
|
|
1424
|
+
<div class="layout-selector">
|
|
1425
|
+
<button class="layout-btn active" data-layout="cose" title="Force-directed">Force</button>
|
|
1426
|
+
<button class="layout-btn" data-layout="circle" title="Circular">Circle</button>
|
|
1427
|
+
<button class="layout-btn" data-layout="grid" title="Grid">Grid</button>
|
|
1428
|
+
</div>
|
|
1429
|
+
</header>
|
|
1430
|
+
|
|
1431
|
+
<aside class="sidebar">
|
|
1432
|
+
<div class="sidebar-section">
|
|
1433
|
+
<div class="sidebar-title">Overview</div>
|
|
1434
|
+
<div class="stat-grid">
|
|
1435
|
+
<div class="stat-card" data-filter="function">
|
|
1436
|
+
<div class="stat-value">${graphData.meta.totalFunctions}</div>
|
|
1437
|
+
<div class="stat-label"><span class="dot" style="background: var(--node-function)"></span> Functions</div>
|
|
1438
|
+
</div>
|
|
1439
|
+
<div class="stat-card" data-filter="entity">
|
|
1440
|
+
<div class="stat-value">${graphData.meta.totalEntities}</div>
|
|
1441
|
+
<div class="stat-label"><span class="dot" style="background: var(--node-entity)"></span> Entities</div>
|
|
1442
|
+
</div>
|
|
1443
|
+
<div class="stat-card" data-filter="accessGroup">
|
|
1444
|
+
<div class="stat-value">${graphData.meta.totalAccessGroups}</div>
|
|
1445
|
+
<div class="stat-label"><span class="dot" style="background: var(--node-access)"></span> Access Groups</div>
|
|
1446
|
+
</div>
|
|
1447
|
+
</div>
|
|
1448
|
+
</div>
|
|
1449
|
+
|
|
1450
|
+
<div class="sidebar-section">
|
|
1451
|
+
<div class="sidebar-title">Node Types</div>
|
|
1452
|
+
<div class="legend-item">
|
|
1453
|
+
<div class="legend-shape function"></div>
|
|
1454
|
+
<div class="legend-text">Function</div>
|
|
1455
|
+
</div>
|
|
1456
|
+
<div class="legend-item">
|
|
1457
|
+
<div class="legend-shape entity"></div>
|
|
1458
|
+
<div class="legend-text">Entity</div>
|
|
1459
|
+
</div>
|
|
1460
|
+
<div class="legend-item">
|
|
1461
|
+
<div class="legend-shape access"></div>
|
|
1462
|
+
<div class="legend-text">Access Group</div>
|
|
1463
|
+
</div>
|
|
1464
|
+
</div>
|
|
1465
|
+
|
|
1466
|
+
<div class="sidebar-section">
|
|
1467
|
+
<div class="sidebar-title">Edge Types</div>
|
|
1468
|
+
<div class="legend-edge">
|
|
1469
|
+
<div class="legend-line operates"></div>
|
|
1470
|
+
<div class="legend-text">Operates on</div>
|
|
1471
|
+
</div>
|
|
1472
|
+
<div class="legend-edge">
|
|
1473
|
+
<div class="legend-line access"></div>
|
|
1474
|
+
<div class="legend-text">Requires access</div>
|
|
1475
|
+
</div>
|
|
1476
|
+
<div class="legend-edge">
|
|
1477
|
+
<div class="legend-line depends"></div>
|
|
1478
|
+
<div class="legend-text">Depends on</div>
|
|
1479
|
+
</div>
|
|
1480
|
+
</div>
|
|
1481
|
+
|
|
1482
|
+
<div class="sidebar-section">
|
|
1483
|
+
<div class="sidebar-title">Keyboard Shortcuts</div>
|
|
1484
|
+
<div style="font-size: 12px; color: var(--text-secondary);">
|
|
1485
|
+
<p style="margin-bottom: 8px;"><span class="kbd">/</span> Search</p>
|
|
1486
|
+
<p style="margin-bottom: 8px;"><span class="kbd">Esc</span> Clear selection</p>
|
|
1487
|
+
<p style="margin-bottom: 8px;"><span class="kbd">F</span> Fit to view</p>
|
|
1488
|
+
<p><span class="kbd">1-4</span> Filter types</p>
|
|
1489
|
+
</div>
|
|
1490
|
+
</div>
|
|
1491
|
+
</aside>
|
|
1492
|
+
|
|
1493
|
+
<main class="graph-container">
|
|
1494
|
+
<div id="cy"></div>
|
|
1495
|
+
<div class="graph-controls">
|
|
1496
|
+
<button class="graph-control-btn" id="zoomIn" title="Zoom in">
|
|
1497
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1498
|
+
<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35M11 8v6M8 11h6"/>
|
|
1499
|
+
</svg>
|
|
1500
|
+
</button>
|
|
1501
|
+
<button class="graph-control-btn" id="zoomOut" title="Zoom out">
|
|
1502
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1503
|
+
<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35M8 11h6"/>
|
|
1504
|
+
</svg>
|
|
1505
|
+
</button>
|
|
1506
|
+
<button class="graph-control-btn" id="fitView" title="Fit to view">
|
|
1507
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1508
|
+
<path d="M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3"/>
|
|
1509
|
+
</svg>
|
|
1510
|
+
</button>
|
|
1511
|
+
</div>
|
|
1512
|
+
</main>
|
|
1513
|
+
|
|
1514
|
+
<aside class="detail-panel empty" id="detailPanel">
|
|
1515
|
+
<div class="empty-state-icon">
|
|
1516
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1517
|
+
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>
|
|
1518
|
+
</svg>
|
|
1519
|
+
</div>
|
|
1520
|
+
<div class="empty-state-title">Select a node</div>
|
|
1521
|
+
<div class="empty-state-text">Click on any node in the graph to view its details, connections, and schema.</div>
|
|
1522
|
+
</aside>
|
|
1523
|
+
|
|
1524
|
+
<!-- Table View -->
|
|
1525
|
+
<div class="table-view" id="tableView">
|
|
1526
|
+
<div id="tableContent"></div>
|
|
1527
|
+
</div>
|
|
1528
|
+
</div>
|
|
1529
|
+
|
|
1530
|
+
<!-- Review Footer -->
|
|
1531
|
+
<div class="review-footer ${graphData.meta.hasChanges ? '' : 'hidden'}" id="reviewFooter">
|
|
1532
|
+
<div class="changes-summary">
|
|
1533
|
+
<span>Pending changes:</span>
|
|
1534
|
+
${graphData.meta.addedCount > 0 ? `<span class="change-count added">+${graphData.meta.addedCount} added</span>` : ''}
|
|
1535
|
+
${graphData.meta.removedCount > 0 ? `<span class="change-count removed">−${graphData.meta.removedCount} removed</span>` : ''}
|
|
1536
|
+
${graphData.meta.modifiedCount > 0 ? `<span class="change-count modified">~${graphData.meta.modifiedCount} modified</span>` : ''}
|
|
1537
|
+
</div>
|
|
1538
|
+
<div class="review-actions">
|
|
1539
|
+
<button class="review-btn reject" id="rejectBtn">Reject Changes</button>
|
|
1540
|
+
<button class="review-btn approve" id="approveBtn">Approve & Update Lockfile</button>
|
|
1541
|
+
</div>
|
|
1542
|
+
</div>
|
|
1543
|
+
|
|
1544
|
+
<script>
|
|
1545
|
+
const graphData = ${JSON.stringify(graphData)};
|
|
1546
|
+
let cy;
|
|
1547
|
+
let selectedNode = null;
|
|
1548
|
+
let activeFilter = 'all';
|
|
1549
|
+
|
|
1550
|
+
// Initialize Cytoscape
|
|
1551
|
+
function initGraph() {
|
|
1552
|
+
const elements = [];
|
|
1553
|
+
|
|
1554
|
+
// Add nodes
|
|
1555
|
+
for (const node of graphData.nodes) {
|
|
1556
|
+
elements.push({
|
|
1557
|
+
group: 'nodes',
|
|
1558
|
+
data: {
|
|
1559
|
+
id: node.id,
|
|
1560
|
+
label: node.label,
|
|
1561
|
+
type: node.type,
|
|
1562
|
+
description: node.description,
|
|
1563
|
+
metadata: node.metadata,
|
|
1564
|
+
changeStatus: node.changeStatus || 'unchanged',
|
|
1565
|
+
changeDetails: node.changeDetails || null,
|
|
1566
|
+
},
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// Add edges
|
|
1571
|
+
for (const edge of graphData.edges) {
|
|
1572
|
+
elements.push({
|
|
1573
|
+
group: 'edges',
|
|
1574
|
+
data: {
|
|
1575
|
+
id: edge.id,
|
|
1576
|
+
source: edge.source,
|
|
1577
|
+
target: edge.target,
|
|
1578
|
+
type: edge.type,
|
|
1579
|
+
label: edge.label || '',
|
|
1580
|
+
},
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
cy = cytoscape({
|
|
1585
|
+
container: document.getElementById('cy'),
|
|
1586
|
+
elements,
|
|
1587
|
+
style: [
|
|
1588
|
+
// Base node style
|
|
1589
|
+
{
|
|
1590
|
+
selector: 'node',
|
|
1591
|
+
style: {
|
|
1592
|
+
'label': 'data(label)',
|
|
1593
|
+
'text-valign': 'center',
|
|
1594
|
+
'text-halign': 'center',
|
|
1595
|
+
'font-family': 'Space Grotesk, -apple-system, BlinkMacSystemFont, sans-serif',
|
|
1596
|
+
'font-size': 12,
|
|
1597
|
+
'font-weight': 500,
|
|
1598
|
+
'color': '#023d60',
|
|
1599
|
+
'text-outline-width': 2,
|
|
1600
|
+
'text-outline-color': '#ffffff',
|
|
1601
|
+
'background-color': '#ffffff',
|
|
1602
|
+
'border-width': 2,
|
|
1603
|
+
'width': 90,
|
|
1604
|
+
'height': 45,
|
|
1605
|
+
},
|
|
1606
|
+
},
|
|
1607
|
+
// Function nodes - Navy
|
|
1608
|
+
{
|
|
1609
|
+
selector: 'node[type="function"]',
|
|
1610
|
+
style: {
|
|
1611
|
+
'shape': 'round-hexagon',
|
|
1612
|
+
'border-color': '#023d60',
|
|
1613
|
+
'background-color': 'rgba(2, 61, 96, 0.08)',
|
|
1614
|
+
'width': 100,
|
|
1615
|
+
'height': 55,
|
|
1616
|
+
},
|
|
1617
|
+
},
|
|
1618
|
+
// Entity nodes - Teal
|
|
1619
|
+
{
|
|
1620
|
+
selector: 'node[type="entity"]',
|
|
1621
|
+
style: {
|
|
1622
|
+
'shape': 'round-rectangle',
|
|
1623
|
+
'border-color': '#15a8a8',
|
|
1624
|
+
'background-color': 'rgba(21, 168, 168, 0.12)',
|
|
1625
|
+
'width': 95,
|
|
1626
|
+
'height': 45,
|
|
1627
|
+
},
|
|
1628
|
+
},
|
|
1629
|
+
// Access group nodes - Magenta
|
|
1630
|
+
{
|
|
1631
|
+
selector: 'node[type="accessGroup"]',
|
|
1632
|
+
style: {
|
|
1633
|
+
'shape': 'ellipse',
|
|
1634
|
+
'border-color': '#bf1363',
|
|
1635
|
+
'background-color': 'rgba(191, 19, 99, 0.1)',
|
|
1636
|
+
'width': 80,
|
|
1637
|
+
'height': 80,
|
|
1638
|
+
},
|
|
1639
|
+
},
|
|
1640
|
+
// Selected node
|
|
1641
|
+
{
|
|
1642
|
+
selector: 'node:selected',
|
|
1643
|
+
style: {
|
|
1644
|
+
'border-width': 3,
|
|
1645
|
+
'background-color': '#ffffff',
|
|
1646
|
+
'shadow-blur': 15,
|
|
1647
|
+
'shadow-color': 'rgba(21, 168, 168, 0.4)',
|
|
1648
|
+
'shadow-opacity': 1,
|
|
1649
|
+
'shadow-offset-x': 0,
|
|
1650
|
+
'shadow-offset-y': 4,
|
|
1651
|
+
},
|
|
1652
|
+
},
|
|
1653
|
+
// Highlighted node (connected to selected)
|
|
1654
|
+
{
|
|
1655
|
+
selector: 'node.highlighted',
|
|
1656
|
+
style: {
|
|
1657
|
+
'border-width': 3,
|
|
1658
|
+
'opacity': 1,
|
|
1659
|
+
},
|
|
1660
|
+
},
|
|
1661
|
+
// Dimmed node
|
|
1662
|
+
{
|
|
1663
|
+
selector: 'node.dimmed',
|
|
1664
|
+
style: {
|
|
1665
|
+
'opacity': 0.25,
|
|
1666
|
+
},
|
|
1667
|
+
},
|
|
1668
|
+
// Hidden node
|
|
1669
|
+
{
|
|
1670
|
+
selector: 'node.hidden',
|
|
1671
|
+
style: {
|
|
1672
|
+
'display': 'none',
|
|
1673
|
+
},
|
|
1674
|
+
},
|
|
1675
|
+
// Change status: Added - keep type color, add solid border + glow
|
|
1676
|
+
{
|
|
1677
|
+
selector: 'node[changeStatus="added"]',
|
|
1678
|
+
style: {
|
|
1679
|
+
'border-color': '#2a9d8f',
|
|
1680
|
+
'border-width': 3,
|
|
1681
|
+
'background-opacity': 0.5,
|
|
1682
|
+
'shadow-blur': 15,
|
|
1683
|
+
'shadow-color': 'rgba(42, 157, 143, 0.5)',
|
|
1684
|
+
'shadow-opacity': 1,
|
|
1685
|
+
'shadow-offset-x': 0,
|
|
1686
|
+
'shadow-offset-y': 0,
|
|
1687
|
+
},
|
|
1688
|
+
},
|
|
1689
|
+
// Change status: Removed - faded with dashed border
|
|
1690
|
+
{
|
|
1691
|
+
selector: 'node[changeStatus="removed"]',
|
|
1692
|
+
style: {
|
|
1693
|
+
'border-color': '#c44536',
|
|
1694
|
+
'border-width': 2,
|
|
1695
|
+
'border-style': 'dashed',
|
|
1696
|
+
'background-opacity': 0.3,
|
|
1697
|
+
'opacity': 0.6,
|
|
1698
|
+
},
|
|
1699
|
+
},
|
|
1700
|
+
// Change status: Modified - keep type color, add solid border + subtle glow
|
|
1701
|
+
{
|
|
1702
|
+
selector: 'node[changeStatus="modified"]',
|
|
1703
|
+
style: {
|
|
1704
|
+
'border-color': '#fe5d26',
|
|
1705
|
+
'border-width': 3,
|
|
1706
|
+
'background-opacity': 0.5,
|
|
1707
|
+
'shadow-blur': 12,
|
|
1708
|
+
'shadow-color': 'rgba(254, 93, 38, 0.4)',
|
|
1709
|
+
'shadow-opacity': 1,
|
|
1710
|
+
'shadow-offset-x': 0,
|
|
1711
|
+
'shadow-offset-y': 0,
|
|
1712
|
+
},
|
|
1713
|
+
},
|
|
1714
|
+
// Base edge style
|
|
1715
|
+
{
|
|
1716
|
+
selector: 'edge',
|
|
1717
|
+
style: {
|
|
1718
|
+
'width': 1.5,
|
|
1719
|
+
'line-color': 'rgba(2, 61, 96, 0.2)',
|
|
1720
|
+
'target-arrow-color': 'rgba(2, 61, 96, 0.3)',
|
|
1721
|
+
'target-arrow-shape': 'triangle',
|
|
1722
|
+
'curve-style': 'bezier',
|
|
1723
|
+
'arrow-scale': 0.8,
|
|
1724
|
+
},
|
|
1725
|
+
},
|
|
1726
|
+
// Operates-on edge - Teal
|
|
1727
|
+
{
|
|
1728
|
+
selector: 'edge[type="operates-on"]',
|
|
1729
|
+
style: {
|
|
1730
|
+
'line-color': '#15a8a8',
|
|
1731
|
+
'target-arrow-color': '#15a8a8',
|
|
1732
|
+
'width': 2,
|
|
1733
|
+
},
|
|
1734
|
+
},
|
|
1735
|
+
// Requires-access edge - Magenta
|
|
1736
|
+
{
|
|
1737
|
+
selector: 'edge[type="requires-access"]',
|
|
1738
|
+
style: {
|
|
1739
|
+
'line-color': '#bf1363',
|
|
1740
|
+
'target-arrow-color': '#bf1363',
|
|
1741
|
+
'line-style': 'dashed',
|
|
1742
|
+
'line-dash-pattern': [6, 3],
|
|
1743
|
+
},
|
|
1744
|
+
},
|
|
1745
|
+
// Depends-on edge - Orange
|
|
1746
|
+
{
|
|
1747
|
+
selector: 'edge[type="depends-on"]',
|
|
1748
|
+
style: {
|
|
1749
|
+
'line-color': '#fe5d26',
|
|
1750
|
+
'target-arrow-color': '#fe5d26',
|
|
1751
|
+
'line-style': 'dotted',
|
|
1752
|
+
'line-dash-pattern': [2, 4],
|
|
1753
|
+
},
|
|
1754
|
+
},
|
|
1755
|
+
// Highlighted edge
|
|
1756
|
+
{
|
|
1757
|
+
selector: 'edge.highlighted',
|
|
1758
|
+
style: {
|
|
1759
|
+
'width': 3,
|
|
1760
|
+
'opacity': 1,
|
|
1761
|
+
},
|
|
1762
|
+
},
|
|
1763
|
+
// Dimmed edge
|
|
1764
|
+
{
|
|
1765
|
+
selector: 'edge.dimmed',
|
|
1766
|
+
style: {
|
|
1767
|
+
'opacity': 0.12,
|
|
1768
|
+
},
|
|
1769
|
+
},
|
|
1770
|
+
// Hidden edge
|
|
1771
|
+
{
|
|
1772
|
+
selector: 'edge.hidden',
|
|
1773
|
+
style: {
|
|
1774
|
+
'display': 'none',
|
|
1775
|
+
},
|
|
1776
|
+
},
|
|
1777
|
+
],
|
|
1778
|
+
layout: {
|
|
1779
|
+
name: 'cose',
|
|
1780
|
+
animate: false,
|
|
1781
|
+
nodeRepulsion: 8000,
|
|
1782
|
+
idealEdgeLength: 100,
|
|
1783
|
+
edgeElasticity: 100,
|
|
1784
|
+
gravity: 0.25,
|
|
1785
|
+
numIter: 1000,
|
|
1786
|
+
padding: 50,
|
|
1787
|
+
},
|
|
1788
|
+
minZoom: 0.2,
|
|
1789
|
+
maxZoom: 3,
|
|
1790
|
+
wheelSensitivity: 0.3,
|
|
1791
|
+
});
|
|
1792
|
+
|
|
1793
|
+
// Node click handler
|
|
1794
|
+
cy.on('tap', 'node', function(evt) {
|
|
1795
|
+
const node = evt.target;
|
|
1796
|
+
selectNode(node);
|
|
1797
|
+
});
|
|
1798
|
+
|
|
1799
|
+
// Background click handler
|
|
1800
|
+
cy.on('tap', function(evt) {
|
|
1801
|
+
if (evt.target === cy) {
|
|
1802
|
+
clearSelection();
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
function selectNode(node) {
|
|
1808
|
+
// Clear previous selection styling
|
|
1809
|
+
cy.elements().removeClass('highlighted dimmed');
|
|
1810
|
+
|
|
1811
|
+
// Select node
|
|
1812
|
+
cy.nodes().unselect();
|
|
1813
|
+
node.select();
|
|
1814
|
+
selectedNode = node;
|
|
1815
|
+
|
|
1816
|
+
// Highlight connected nodes and edges
|
|
1817
|
+
const connectedEdges = node.connectedEdges();
|
|
1818
|
+
const connectedNodes = connectedEdges.connectedNodes();
|
|
1819
|
+
|
|
1820
|
+
node.addClass('highlighted');
|
|
1821
|
+
connectedNodes.addClass('highlighted');
|
|
1822
|
+
connectedEdges.addClass('highlighted');
|
|
1823
|
+
|
|
1824
|
+
cy.elements().not(node).not(connectedNodes).not(connectedEdges).addClass('dimmed');
|
|
1825
|
+
|
|
1826
|
+
// Update detail panel
|
|
1827
|
+
updateDetailPanel(node.data());
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
function selectNodeById(nodeId) {
|
|
1831
|
+
const node = cy.getElementById(nodeId);
|
|
1832
|
+
if (node.length > 0) {
|
|
1833
|
+
selectNode(node);
|
|
1834
|
+
cy.animate({
|
|
1835
|
+
center: { eles: node },
|
|
1836
|
+
zoom: 1.5,
|
|
1837
|
+
duration: 300,
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
function clearSelection() {
|
|
1843
|
+
cy.elements().removeClass('highlighted dimmed');
|
|
1844
|
+
cy.nodes().unselect();
|
|
1845
|
+
selectedNode = null;
|
|
1846
|
+
showEmptyState();
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
function showEmptyState() {
|
|
1850
|
+
const panel = document.getElementById('detailPanel');
|
|
1851
|
+
panel.className = 'detail-panel empty';
|
|
1852
|
+
panel.innerHTML = \`
|
|
1853
|
+
<div class="empty-state-icon">
|
|
1854
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1855
|
+
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/>
|
|
1856
|
+
</svg>
|
|
1857
|
+
</div>
|
|
1858
|
+
<div class="empty-state-title">Select a node</div>
|
|
1859
|
+
<div class="empty-state-text">Click on any node in the graph to view its details, connections, and schema.</div>
|
|
1860
|
+
\`;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
async function updateDetailPanel(data) {
|
|
1864
|
+
const panel = document.getElementById('detailPanel');
|
|
1865
|
+
panel.className = 'detail-panel';
|
|
1866
|
+
|
|
1867
|
+
// Fetch detailed node data
|
|
1868
|
+
const [type, id] = data.id.split(':');
|
|
1869
|
+
const response = await fetch(\`/api/node/\${type}/\${id}\`);
|
|
1870
|
+
const details = await response.json();
|
|
1871
|
+
|
|
1872
|
+
// Build change status badge if applicable
|
|
1873
|
+
const changeStatus = data.changeStatus || 'unchanged';
|
|
1874
|
+
const changeBadge = changeStatus !== 'unchanged'
|
|
1875
|
+
? \`<span class="detail-change-badge \${changeStatus}">\${changeStatus === 'added' ? 'New' : changeStatus === 'removed' ? 'Removed' : 'Modified'}</span>\`
|
|
1876
|
+
: '';
|
|
1877
|
+
|
|
1878
|
+
let html = \`
|
|
1879
|
+
<div class="detail-header">
|
|
1880
|
+
<div class="detail-type \${data.type}">\${formatType(data.type)}\${changeBadge}</div>
|
|
1881
|
+
<div class="detail-name">\${data.label}</div>
|
|
1882
|
+
<div class="detail-description">\${data.description || 'No description'}</div>
|
|
1883
|
+
</div>
|
|
1884
|
+
\`;
|
|
1885
|
+
|
|
1886
|
+
// Show change details if this node was modified
|
|
1887
|
+
if (changeStatus !== 'unchanged' && data.changeDetails) {
|
|
1888
|
+
html += buildChangeSection(changeStatus, data.changeDetails);
|
|
1889
|
+
} else if (changeStatus === 'added') {
|
|
1890
|
+
html += \`
|
|
1891
|
+
<div class="detail-section change-section added">
|
|
1892
|
+
<div class="detail-section-title">Change</div>
|
|
1893
|
+
<div class="change-summary">This is a newly added \${formatType(data.type).toLowerCase()}.</div>
|
|
1894
|
+
</div>
|
|
1895
|
+
\`;
|
|
1896
|
+
} else if (changeStatus === 'removed') {
|
|
1897
|
+
html += \`
|
|
1898
|
+
<div class="detail-section change-section removed">
|
|
1899
|
+
<div class="detail-section-title">Change</div>
|
|
1900
|
+
<div class="change-summary">This \${formatType(data.type).toLowerCase()} will be removed.</div>
|
|
1901
|
+
</div>
|
|
1902
|
+
\`;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
if (data.type === 'function') {
|
|
1906
|
+
// Access Groups
|
|
1907
|
+
if (details.connections.accessGroups.length > 0) {
|
|
1908
|
+
html += \`
|
|
1909
|
+
<div class="detail-section">
|
|
1910
|
+
<div class="detail-section-title">Access Groups</div>
|
|
1911
|
+
<div class="tag-list">
|
|
1912
|
+
\${details.connections.accessGroups.map(g =>
|
|
1913
|
+
\`<span class="tag access" onclick="selectNodeById('accessGroup:\${g}')">\${g}</span>\`
|
|
1914
|
+
).join('')}
|
|
1915
|
+
</div>
|
|
1916
|
+
</div>
|
|
1917
|
+
\`;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
// Entities
|
|
1921
|
+
if (details.connections.entities.length > 0) {
|
|
1922
|
+
html += \`
|
|
1923
|
+
<div class="detail-section">
|
|
1924
|
+
<div class="detail-section-title">Entities</div>
|
|
1925
|
+
<div class="tag-list">
|
|
1926
|
+
\${details.connections.entities.map(e =>
|
|
1927
|
+
\`<span class="tag entity" onclick="selectNodeById('entity:\${e}')">\${e}</span>\`
|
|
1928
|
+
).join('')}
|
|
1929
|
+
</div>
|
|
1930
|
+
</div>
|
|
1931
|
+
\`;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// Dependencies
|
|
1935
|
+
if (details.connections.dependsOn.length > 0) {
|
|
1936
|
+
html += \`
|
|
1937
|
+
<div class="detail-section">
|
|
1938
|
+
<div class="detail-section-title">Dependencies (fieldFrom)</div>
|
|
1939
|
+
\${details.connections.dependsOn.map(d => \`
|
|
1940
|
+
<div class="dependency-item">
|
|
1941
|
+
<span class="function-link" onclick="selectNodeById('function:\${d.functionName}')">\${d.functionName}</span>
|
|
1942
|
+
<span class="dependency-path">\${d.path}</span>
|
|
1943
|
+
</div>
|
|
1944
|
+
\`).join('')}
|
|
1945
|
+
</div>
|
|
1946
|
+
\`;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
// Depended on by
|
|
1950
|
+
if (details.connections.dependedOnBy.length > 0) {
|
|
1951
|
+
html += \`
|
|
1952
|
+
<div class="detail-section">
|
|
1953
|
+
<div class="detail-section-title">Used By</div>
|
|
1954
|
+
<ul class="function-list">
|
|
1955
|
+
\${details.connections.dependedOnBy.map(f => \`
|
|
1956
|
+
<li><span class="function-link" onclick="selectNodeById('function:\${f}')">\${f}</span></li>
|
|
1957
|
+
\`).join('')}
|
|
1958
|
+
</ul>
|
|
1959
|
+
</div>
|
|
1960
|
+
\`;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// Input Schema
|
|
1964
|
+
if (data.metadata.inputs) {
|
|
1965
|
+
html += \`
|
|
1966
|
+
<div class="detail-section">
|
|
1967
|
+
<div class="detail-section-title">Input Schema</div>
|
|
1968
|
+
<pre class="schema-viewer">\${formatSchema(data.metadata.inputs)}</pre>
|
|
1969
|
+
</div>
|
|
1970
|
+
\`;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// Returns (prominent display)
|
|
1974
|
+
if (data.metadata.outputs) {
|
|
1975
|
+
html += \`
|
|
1976
|
+
<div class="detail-section returns-section">
|
|
1977
|
+
<div class="detail-section-title">Returns</div>
|
|
1978
|
+
<div class="returns-display">
|
|
1979
|
+
<span class="returns-type-large">\${formatSchemaType(data.metadata.outputs)}</span>
|
|
1980
|
+
</div>
|
|
1981
|
+
<pre class="schema-viewer">\${formatSchema(data.metadata.outputs)}</pre>
|
|
1982
|
+
</div>
|
|
1983
|
+
\`;
|
|
1984
|
+
}
|
|
1985
|
+
} else if (data.type === 'accessGroup' || data.type === 'entity') {
|
|
1986
|
+
// Functions with details
|
|
1987
|
+
if (details.connections.functions.length > 0) {
|
|
1988
|
+
html += \`
|
|
1989
|
+
<div class="detail-section">
|
|
1990
|
+
<div class="detail-section-title">Functions (\${details.connections.functions.length})</div>
|
|
1991
|
+
<div class="function-cards">
|
|
1992
|
+
\${details.connections.functions.map(f => \`
|
|
1993
|
+
<div class="function-card" onclick="selectNodeById('function:\${f.name}')">
|
|
1994
|
+
<div class="function-card-header">
|
|
1995
|
+
<span class="function-card-name">\${f.name}</span>
|
|
1996
|
+
</div>
|
|
1997
|
+
<div class="function-card-desc">\${f.description}</div>
|
|
1998
|
+
\${f.outputs ? \`
|
|
1999
|
+
<div class="function-card-returns">
|
|
2000
|
+
<span class="returns-label">Returns:</span>
|
|
2001
|
+
<span class="returns-type">\${formatSchemaType(f.outputs)}</span>
|
|
2002
|
+
</div>
|
|
2003
|
+
\` : ''}
|
|
2004
|
+
</div>
|
|
2005
|
+
\`).join('')}
|
|
2006
|
+
</div>
|
|
2007
|
+
</div>
|
|
2008
|
+
\`;
|
|
2009
|
+
} else {
|
|
2010
|
+
html += \`
|
|
2011
|
+
<div class="detail-section">
|
|
2012
|
+
<div class="detail-section-title">Functions</div>
|
|
2013
|
+
<p class="no-data">No functions \${data.type === 'accessGroup' ? 'require this access' : 'operate on this entity'}</p>
|
|
2014
|
+
</div>
|
|
2015
|
+
\`;
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
panel.innerHTML = html;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
function formatType(type) {
|
|
2023
|
+
const labels = {
|
|
2024
|
+
function: 'Function',
|
|
2025
|
+
entity: 'Entity',
|
|
2026
|
+
accessGroup: 'Access Group',
|
|
2027
|
+
};
|
|
2028
|
+
return labels[type] || type;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
function formatSchema(schema) {
|
|
2032
|
+
if (!schema) return 'No schema';
|
|
2033
|
+
|
|
2034
|
+
// Simplified schema display
|
|
2035
|
+
if (schema.type === 'object' && schema.properties) {
|
|
2036
|
+
const lines = [];
|
|
2037
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
2038
|
+
const required = schema.required?.includes(key) ? '' : '?';
|
|
2039
|
+
const type = formatSchemaType(value);
|
|
2040
|
+
lines.push(\` \${key}\${required}: \${type}\`);
|
|
2041
|
+
}
|
|
2042
|
+
return '{\\n' + lines.join(',\\n') + '\\n}';
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
return JSON.stringify(schema, null, 2);
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
function formatSchemaType(schema) {
|
|
2049
|
+
if (!schema) return 'unknown';
|
|
2050
|
+
if (schema.type === 'array') {
|
|
2051
|
+
return \`\${formatSchemaType(schema.items)}[]\`;
|
|
2052
|
+
}
|
|
2053
|
+
if (schema.type === 'object') {
|
|
2054
|
+
return 'object';
|
|
2055
|
+
}
|
|
2056
|
+
if (schema.enum) {
|
|
2057
|
+
return schema.enum.map(e => \`"\${e}"\`).join(' | ');
|
|
2058
|
+
}
|
|
2059
|
+
let type = schema.type || 'unknown';
|
|
2060
|
+
if (schema.format) {
|
|
2061
|
+
type += \` (\${schema.format})\`;
|
|
2062
|
+
}
|
|
2063
|
+
return type;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
function buildChangeSection(changeStatus, details) {
|
|
2067
|
+
if (!details) return '';
|
|
2068
|
+
|
|
2069
|
+
let items = [];
|
|
2070
|
+
|
|
2071
|
+
if (details.oldAccess && details.newAccess) {
|
|
2072
|
+
const oldList = details.oldAccess.join(', ');
|
|
2073
|
+
const newList = details.newAccess.join(', ');
|
|
2074
|
+
items.push(\`
|
|
2075
|
+
<div class="change-item">
|
|
2076
|
+
<span class="change-label">Access:</span>
|
|
2077
|
+
<span class="change-old">\${oldList}</span>
|
|
2078
|
+
<span class="change-arrow">→</span>
|
|
2079
|
+
<span class="change-new">\${newList}</span>
|
|
2080
|
+
</div>
|
|
2081
|
+
\`);
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
if (details.oldEntities && details.newEntities) {
|
|
2085
|
+
const oldList = details.oldEntities.join(', ') || '(none)';
|
|
2086
|
+
const newList = details.newEntities.join(', ') || '(none)';
|
|
2087
|
+
items.push(\`
|
|
2088
|
+
<div class="change-item">
|
|
2089
|
+
<span class="change-label">Entities:</span>
|
|
2090
|
+
<span class="change-old">\${oldList}</span>
|
|
2091
|
+
<span class="change-arrow">→</span>
|
|
2092
|
+
<span class="change-new">\${newList}</span>
|
|
2093
|
+
</div>
|
|
2094
|
+
\`);
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
if (details.oldDescription && details.newDescription) {
|
|
2098
|
+
items.push(\`
|
|
2099
|
+
<div class="change-item change-item-block">
|
|
2100
|
+
<span class="change-label">Description:</span>
|
|
2101
|
+
<div class="change-description-diff">
|
|
2102
|
+
<div class="change-old">\${details.oldDescription}</div>
|
|
2103
|
+
<div class="change-arrow">↓</div>
|
|
2104
|
+
<div class="change-new">\${details.newDescription}</div>
|
|
2105
|
+
</div>
|
|
2106
|
+
</div>
|
|
2107
|
+
\`);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
if (details.inputsChanged) {
|
|
2111
|
+
items.push(\`
|
|
2112
|
+
<div class="change-item">
|
|
2113
|
+
<span class="change-label">Input schema changed</span>
|
|
2114
|
+
</div>
|
|
2115
|
+
\`);
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
if (details.outputsChanged) {
|
|
2119
|
+
items.push(\`
|
|
2120
|
+
<div class="change-item">
|
|
2121
|
+
<span class="change-label">Output schema changed</span>
|
|
2122
|
+
</div>
|
|
2123
|
+
\`);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
if (items.length === 0) {
|
|
2127
|
+
items.push('<div class="change-item"><span class="change-label">Details modified</span></div>');
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
return \`
|
|
2131
|
+
<div class="detail-section change-section \${changeStatus}">
|
|
2132
|
+
<div class="detail-section-title">Changes</div>
|
|
2133
|
+
\${items.join('')}
|
|
2134
|
+
</div>
|
|
2135
|
+
\`;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// Filtering
|
|
2139
|
+
function setFilter(filter) {
|
|
2140
|
+
activeFilter = filter;
|
|
2141
|
+
|
|
2142
|
+
// Update button states
|
|
2143
|
+
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
2144
|
+
btn.classList.toggle('active', btn.dataset.filter === filter);
|
|
2145
|
+
});
|
|
2146
|
+
document.querySelectorAll('.stat-card').forEach(card => {
|
|
2147
|
+
card.classList.toggle('active', card.dataset.filter === filter);
|
|
2148
|
+
});
|
|
2149
|
+
|
|
2150
|
+
// Apply filter to graph
|
|
2151
|
+
if (filter === 'all') {
|
|
2152
|
+
cy.nodes().removeClass('hidden');
|
|
2153
|
+
cy.edges().removeClass('hidden');
|
|
2154
|
+
} else {
|
|
2155
|
+
cy.nodes().forEach(node => {
|
|
2156
|
+
if (node.data('type') === filter) {
|
|
2157
|
+
node.removeClass('hidden');
|
|
2158
|
+
} else {
|
|
2159
|
+
node.addClass('hidden');
|
|
2160
|
+
}
|
|
2161
|
+
});
|
|
2162
|
+
cy.edges().forEach(edge => {
|
|
2163
|
+
const source = cy.getElementById(edge.data('source'));
|
|
2164
|
+
const target = cy.getElementById(edge.data('target'));
|
|
2165
|
+
if (source.hasClass('hidden') || target.hasClass('hidden')) {
|
|
2166
|
+
edge.addClass('hidden');
|
|
2167
|
+
} else {
|
|
2168
|
+
edge.removeClass('hidden');
|
|
2169
|
+
}
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
// Layout
|
|
2175
|
+
function setLayout(layoutName) {
|
|
2176
|
+
document.querySelectorAll('.layout-btn').forEach(btn => {
|
|
2177
|
+
btn.classList.toggle('active', btn.dataset.layout === layoutName);
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
const layouts = {
|
|
2181
|
+
cose: {
|
|
2182
|
+
name: 'cose',
|
|
2183
|
+
animate: true,
|
|
2184
|
+
animationDuration: 500,
|
|
2185
|
+
nodeRepulsion: 8000,
|
|
2186
|
+
idealEdgeLength: 100,
|
|
2187
|
+
gravity: 0.25,
|
|
2188
|
+
padding: 50,
|
|
2189
|
+
},
|
|
2190
|
+
circle: {
|
|
2191
|
+
name: 'circle',
|
|
2192
|
+
animate: true,
|
|
2193
|
+
animationDuration: 500,
|
|
2194
|
+
padding: 50,
|
|
2195
|
+
},
|
|
2196
|
+
grid: {
|
|
2197
|
+
name: 'grid',
|
|
2198
|
+
animate: true,
|
|
2199
|
+
animationDuration: 500,
|
|
2200
|
+
padding: 50,
|
|
2201
|
+
rows: Math.ceil(Math.sqrt(graphData.nodes.length)),
|
|
2202
|
+
},
|
|
2203
|
+
};
|
|
2204
|
+
|
|
2205
|
+
cy.layout(layouts[layoutName]).run();
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
// Search
|
|
2209
|
+
let searchTimeout;
|
|
2210
|
+
const searchInput = document.getElementById('searchInput');
|
|
2211
|
+
const searchResults = document.getElementById('searchResults');
|
|
2212
|
+
|
|
2213
|
+
searchInput.addEventListener('input', (e) => {
|
|
2214
|
+
clearTimeout(searchTimeout);
|
|
2215
|
+
const query = e.target.value.trim();
|
|
2216
|
+
|
|
2217
|
+
if (query.length < 1) {
|
|
2218
|
+
searchResults.classList.remove('visible');
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
searchTimeout = setTimeout(async () => {
|
|
2223
|
+
const response = await fetch(\`/api/search?q=\${encodeURIComponent(query)}\`);
|
|
2224
|
+
const data = await response.json();
|
|
2225
|
+
|
|
2226
|
+
if (data.results.length > 0) {
|
|
2227
|
+
searchResults.innerHTML = data.results.map(r => \`
|
|
2228
|
+
<div class="search-result-item" data-node-id="\${r.id}">
|
|
2229
|
+
<span class="search-result-type \${r.type}"></span>
|
|
2230
|
+
<span class="search-result-label">\${r.label}</span>
|
|
2231
|
+
<span class="search-result-match">\${r.matchType}</span>
|
|
2232
|
+
</div>
|
|
2233
|
+
\`).join('');
|
|
2234
|
+
searchResults.classList.add('visible');
|
|
2235
|
+
} else {
|
|
2236
|
+
searchResults.innerHTML = '<div class="search-result-item"><span class="search-result-label" style="color: var(--text-muted)">No results found</span></div>';
|
|
2237
|
+
searchResults.classList.add('visible');
|
|
2238
|
+
}
|
|
2239
|
+
}, 150);
|
|
2240
|
+
});
|
|
2241
|
+
|
|
2242
|
+
searchResults.addEventListener('click', (e) => {
|
|
2243
|
+
const item = e.target.closest('.search-result-item');
|
|
2244
|
+
if (item && item.dataset.nodeId) {
|
|
2245
|
+
selectNodeById(item.dataset.nodeId);
|
|
2246
|
+
searchInput.value = '';
|
|
2247
|
+
searchResults.classList.remove('visible');
|
|
2248
|
+
}
|
|
2249
|
+
});
|
|
2250
|
+
|
|
2251
|
+
searchInput.addEventListener('blur', () => {
|
|
2252
|
+
setTimeout(() => searchResults.classList.remove('visible'), 200);
|
|
2253
|
+
});
|
|
2254
|
+
|
|
2255
|
+
// Event listeners
|
|
2256
|
+
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
2257
|
+
btn.addEventListener('click', () => setFilter(btn.dataset.filter));
|
|
2258
|
+
});
|
|
2259
|
+
|
|
2260
|
+
document.querySelectorAll('.stat-card').forEach(card => {
|
|
2261
|
+
card.addEventListener('click', () => setFilter(card.dataset.filter));
|
|
2262
|
+
});
|
|
2263
|
+
|
|
2264
|
+
document.querySelectorAll('.layout-btn').forEach(btn => {
|
|
2265
|
+
btn.addEventListener('click', () => setLayout(btn.dataset.layout));
|
|
2266
|
+
});
|
|
2267
|
+
|
|
2268
|
+
document.getElementById('zoomIn').addEventListener('click', () => {
|
|
2269
|
+
cy.zoom(cy.zoom() * 1.3);
|
|
2270
|
+
});
|
|
2271
|
+
|
|
2272
|
+
document.getElementById('zoomOut').addEventListener('click', () => {
|
|
2273
|
+
cy.zoom(cy.zoom() / 1.3);
|
|
2274
|
+
});
|
|
2275
|
+
|
|
2276
|
+
document.getElementById('fitView').addEventListener('click', () => {
|
|
2277
|
+
cy.fit(50);
|
|
2278
|
+
});
|
|
2279
|
+
|
|
2280
|
+
// Keyboard shortcuts
|
|
2281
|
+
document.addEventListener('keydown', (e) => {
|
|
2282
|
+
// Ignore if typing in input
|
|
2283
|
+
if (e.target.tagName === 'INPUT') {
|
|
2284
|
+
if (e.key === 'Escape') {
|
|
2285
|
+
e.target.blur();
|
|
2286
|
+
searchResults.classList.remove('visible');
|
|
2287
|
+
}
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
switch (e.key) {
|
|
2292
|
+
case '/':
|
|
2293
|
+
e.preventDefault();
|
|
2294
|
+
searchInput.focus();
|
|
2295
|
+
break;
|
|
2296
|
+
case 'Escape':
|
|
2297
|
+
clearSelection();
|
|
2298
|
+
break;
|
|
2299
|
+
case 'f':
|
|
2300
|
+
case 'F':
|
|
2301
|
+
cy.fit(50);
|
|
2302
|
+
break;
|
|
2303
|
+
case '1':
|
|
2304
|
+
setFilter('all');
|
|
2305
|
+
break;
|
|
2306
|
+
case '2':
|
|
2307
|
+
setFilter('function');
|
|
2308
|
+
break;
|
|
2309
|
+
case '3':
|
|
2310
|
+
setFilter('entity');
|
|
2311
|
+
break;
|
|
2312
|
+
case '4':
|
|
2313
|
+
setFilter('accessGroup');
|
|
2314
|
+
break;
|
|
2315
|
+
}
|
|
2316
|
+
});
|
|
2317
|
+
|
|
2318
|
+
// Initialize - explicitly load fonts before rendering graph
|
|
2319
|
+
// document.fonts.ready is unreliable in Chromium, use document.fonts.load() instead
|
|
2320
|
+
async function loadFontsAndInit() {
|
|
2321
|
+
try {
|
|
2322
|
+
// Explicitly load the fonts we need for canvas
|
|
2323
|
+
await Promise.all([
|
|
2324
|
+
document.fonts.load('500 12px "Space Grotesk"'),
|
|
2325
|
+
document.fonts.load('600 12px "Space Grotesk"'),
|
|
2326
|
+
document.fonts.load('400 12px "Space Mono"'),
|
|
2327
|
+
]);
|
|
2328
|
+
console.log('Fonts loaded:', document.fonts.check('500 12px "Space Grotesk"') ? 'Space Grotesk OK' : 'Space Grotesk FAILED');
|
|
2329
|
+
} catch (e) {
|
|
2330
|
+
console.warn('Font loading error:', e);
|
|
2331
|
+
}
|
|
2332
|
+
initGraph();
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
// Debug: Check font after render (call from console: checkFont())
|
|
2336
|
+
window.checkFont = function() {
|
|
2337
|
+
const canvas = document.createElement('canvas');
|
|
2338
|
+
const ctx = canvas.getContext('2d');
|
|
2339
|
+
|
|
2340
|
+
// Measure text with Space Grotesk vs fallback
|
|
2341
|
+
ctx.font = '500 12px "Space Grotesk", sans-serif';
|
|
2342
|
+
const spaceWidth = ctx.measureText('Hello World').width;
|
|
2343
|
+
|
|
2344
|
+
ctx.font = '500 12px sans-serif';
|
|
2345
|
+
const fallbackWidth = ctx.measureText('Hello World').width;
|
|
2346
|
+
|
|
2347
|
+
ctx.font = '500 12px Arial';
|
|
2348
|
+
const arialWidth = ctx.measureText('Hello World').width;
|
|
2349
|
+
|
|
2350
|
+
console.log('Font widths:', {
|
|
2351
|
+
'Space Grotesk': spaceWidth,
|
|
2352
|
+
'sans-serif fallback': fallbackWidth,
|
|
2353
|
+
'Arial': arialWidth,
|
|
2354
|
+
'Using Space Grotesk': spaceWidth !== fallbackWidth && spaceWidth !== arialWidth
|
|
2355
|
+
});
|
|
2356
|
+
|
|
2357
|
+
return document.fonts.check('500 12px "Space Grotesk"');
|
|
2358
|
+
};
|
|
2359
|
+
|
|
2360
|
+
// View tab switching
|
|
2361
|
+
let currentView = 'graph';
|
|
2362
|
+
|
|
2363
|
+
function switchView(view) {
|
|
2364
|
+
currentView = view;
|
|
2365
|
+
|
|
2366
|
+
// Update tab buttons
|
|
2367
|
+
document.querySelectorAll('.view-tab').forEach(btn => {
|
|
2368
|
+
btn.classList.toggle('active', btn.dataset.view === view);
|
|
2369
|
+
});
|
|
2370
|
+
|
|
2371
|
+
// Show/hide views
|
|
2372
|
+
const graphContainer = document.querySelector('.graph-container');
|
|
2373
|
+
const detailPanel = document.getElementById('detailPanel');
|
|
2374
|
+
const tableView = document.getElementById('tableView');
|
|
2375
|
+
const graphFilters = document.getElementById('graphFilters');
|
|
2376
|
+
const layoutSelector = document.querySelector('.layout-selector');
|
|
2377
|
+
|
|
2378
|
+
if (view === 'graph') {
|
|
2379
|
+
graphContainer.style.display = 'block';
|
|
2380
|
+
detailPanel.style.display = 'block';
|
|
2381
|
+
tableView.classList.remove('active');
|
|
2382
|
+
if (graphFilters) graphFilters.style.display = 'flex';
|
|
2383
|
+
if (layoutSelector) layoutSelector.style.display = 'flex';
|
|
2384
|
+
} else {
|
|
2385
|
+
graphContainer.style.display = 'none';
|
|
2386
|
+
detailPanel.style.display = 'none';
|
|
2387
|
+
tableView.classList.add('active');
|
|
2388
|
+
if (graphFilters) graphFilters.style.display = 'none';
|
|
2389
|
+
if (layoutSelector) layoutSelector.style.display = 'none';
|
|
2390
|
+
renderTableView();
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
document.querySelectorAll('.view-tab').forEach(btn => {
|
|
2395
|
+
btn.addEventListener('click', () => switchView(btn.dataset.view));
|
|
2396
|
+
});
|
|
2397
|
+
|
|
2398
|
+
// Table view rendering
|
|
2399
|
+
function renderTableView() {
|
|
2400
|
+
const container = document.getElementById('tableContent');
|
|
2401
|
+
const nodes = graphData.nodes;
|
|
2402
|
+
const diff = graphData.diff;
|
|
2403
|
+
|
|
2404
|
+
// Group nodes by type
|
|
2405
|
+
const accessGroups = nodes.filter(n => n.type === 'accessGroup');
|
|
2406
|
+
const entities = nodes.filter(n => n.type === 'entity');
|
|
2407
|
+
const functions = nodes.filter(n => n.type === 'function');
|
|
2408
|
+
|
|
2409
|
+
let html = '';
|
|
2410
|
+
|
|
2411
|
+
// Access Groups section
|
|
2412
|
+
html += renderTableSection('Access Groups', accessGroups, 'accessGroup');
|
|
2413
|
+
|
|
2414
|
+
// Entities section
|
|
2415
|
+
if (entities.length > 0) {
|
|
2416
|
+
html += renderTableSection('Entities', entities, 'entity');
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
// Functions section
|
|
2420
|
+
html += renderTableSection('Functions', functions, 'function');
|
|
2421
|
+
|
|
2422
|
+
container.innerHTML = html;
|
|
2423
|
+
|
|
2424
|
+
// Add collapse/expand handlers
|
|
2425
|
+
container.querySelectorAll('.table-section-header').forEach(header => {
|
|
2426
|
+
header.addEventListener('click', () => {
|
|
2427
|
+
header.parentElement.classList.toggle('collapsed');
|
|
2428
|
+
});
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
function renderTableSection(title, items, type) {
|
|
2433
|
+
const changedCount = items.filter(n => n.changeStatus !== 'unchanged').length;
|
|
2434
|
+
|
|
2435
|
+
let html = '<div class="table-section">';
|
|
2436
|
+
html += '<div class="table-section-header">';
|
|
2437
|
+
html += '<div class="table-section-title">';
|
|
2438
|
+
html += '<span class="dot" style="background: var(--node-' + (type === 'accessGroup' ? 'access' : type) + ')"></span>';
|
|
2439
|
+
html += title;
|
|
2440
|
+
html += '<span class="table-section-count">' + items.length + '</span>';
|
|
2441
|
+
if (changedCount > 0) {
|
|
2442
|
+
html += '<span class="change-badge modified">' + changedCount + '</span>';
|
|
2443
|
+
}
|
|
2444
|
+
html += '</div>';
|
|
2445
|
+
html += '<svg class="table-section-toggle" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>';
|
|
2446
|
+
html += '</div>';
|
|
2447
|
+
html += '<div class="table-section-content">';
|
|
2448
|
+
|
|
2449
|
+
// Sort: changed items first, then by name
|
|
2450
|
+
const sortedItems = [...items].sort((a, b) => {
|
|
2451
|
+
const aChanged = a.changeStatus !== 'unchanged' ? 0 : 1;
|
|
2452
|
+
const bChanged = b.changeStatus !== 'unchanged' ? 0 : 1;
|
|
2453
|
+
if (aChanged !== bChanged) return aChanged - bChanged;
|
|
2454
|
+
return a.label.localeCompare(b.label);
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
for (const item of sortedItems) {
|
|
2458
|
+
html += renderTableItem(item, type);
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
html += '</div></div>';
|
|
2462
|
+
return html;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
function renderTableItem(item, type) {
|
|
2466
|
+
const statusClass = item.changeStatus !== 'unchanged' ? item.changeStatus : '';
|
|
2467
|
+
const icon = item.changeStatus === 'added' ? '+' : item.changeStatus === 'removed' ? '−' : item.changeStatus === 'modified' ? '~' : '';
|
|
2468
|
+
|
|
2469
|
+
let html = '<div class="table-item ' + statusClass + '">';
|
|
2470
|
+
html += '<div class="table-item-icon">' + icon + '</div>';
|
|
2471
|
+
html += '<div class="table-item-content">';
|
|
2472
|
+
html += '<div class="table-item-name">' + escapeHtml(item.label) + '</div>';
|
|
2473
|
+
html += '<div class="table-item-description">' + escapeHtml(item.description) + '</div>';
|
|
2474
|
+
|
|
2475
|
+
// Show tags for functions
|
|
2476
|
+
if (type === 'function' && graphData.edges) {
|
|
2477
|
+
const accessEdges = graphData.edges.filter(e => e.source === item.id && e.type === 'requires-access');
|
|
2478
|
+
const entityEdges = graphData.edges.filter(e => e.source === item.id && e.type === 'operates-on');
|
|
2479
|
+
|
|
2480
|
+
if (accessEdges.length > 0 || entityEdges.length > 0) {
|
|
2481
|
+
html += '<div class="table-item-tags">';
|
|
2482
|
+
for (const edge of accessEdges) {
|
|
2483
|
+
const groupName = edge.target.replace('accessGroup:', '');
|
|
2484
|
+
html += '<span class="table-item-tag access">' + escapeHtml(groupName) + '</span>';
|
|
2485
|
+
}
|
|
2486
|
+
for (const edge of entityEdges) {
|
|
2487
|
+
const entityName = edge.target.replace('entity:', '');
|
|
2488
|
+
html += '<span class="table-item-tag entity">' + escapeHtml(entityName) + '</span>';
|
|
2489
|
+
}
|
|
2490
|
+
html += '</div>';
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
// Show change details
|
|
2495
|
+
if (item.changeDetails && item.changeStatus === 'modified') {
|
|
2496
|
+
const details = item.changeDetails;
|
|
2497
|
+
html += '<div class="table-item-change">';
|
|
2498
|
+
if (details.oldAccess && details.newAccess) {
|
|
2499
|
+
html += '<div>Access: <span class="old">' + details.oldAccess.join(', ') + '</span><span class="arrow">→</span><span class="new">' + details.newAccess.join(', ') + '</span></div>';
|
|
2500
|
+
}
|
|
2501
|
+
if (details.inputsChanged) {
|
|
2502
|
+
html += '<div>Input schema changed</div>';
|
|
2503
|
+
}
|
|
2504
|
+
if (details.outputsChanged) {
|
|
2505
|
+
html += '<div>Output schema changed</div>';
|
|
2506
|
+
}
|
|
2507
|
+
if (details.entitiesChanged) {
|
|
2508
|
+
html += '<div>Entities: <span class="old">' + (details.oldEntities || []).join(', ') + '</span><span class="arrow">→</span><span class="new">' + (details.newEntities || []).join(', ') + '</span></div>';
|
|
2509
|
+
}
|
|
2510
|
+
html += '</div>';
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
html += '</div></div>';
|
|
2514
|
+
return html;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
function escapeHtml(text) {
|
|
2518
|
+
const div = document.createElement('div');
|
|
2519
|
+
div.textContent = text || '';
|
|
2520
|
+
return div.innerHTML;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
// Approve/Reject handlers
|
|
2524
|
+
const approveBtn = document.getElementById('approveBtn');
|
|
2525
|
+
const rejectBtn = document.getElementById('rejectBtn');
|
|
2526
|
+
|
|
2527
|
+
if (approveBtn) {
|
|
2528
|
+
approveBtn.addEventListener('click', async () => {
|
|
2529
|
+
approveBtn.disabled = true;
|
|
2530
|
+
rejectBtn.disabled = true;
|
|
2531
|
+
try {
|
|
2532
|
+
const response = await fetch('/api/approve', { method: 'POST' });
|
|
2533
|
+
if (response.ok) {
|
|
2534
|
+
document.getElementById('reviewFooter').innerHTML =
|
|
2535
|
+
'<div class="changes-summary" style="color: var(--vanna-teal);">✓ Changes approved! Lockfile updated. You can close this window.</div>';
|
|
2536
|
+
} else {
|
|
2537
|
+
throw new Error('Failed to approve');
|
|
2538
|
+
}
|
|
2539
|
+
} catch (error) {
|
|
2540
|
+
alert('Failed to approve changes: ' + error.message);
|
|
2541
|
+
approveBtn.disabled = false;
|
|
2542
|
+
rejectBtn.disabled = false;
|
|
2543
|
+
}
|
|
2544
|
+
});
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
if (rejectBtn) {
|
|
2548
|
+
rejectBtn.addEventListener('click', async () => {
|
|
2549
|
+
approveBtn.disabled = true;
|
|
2550
|
+
rejectBtn.disabled = true;
|
|
2551
|
+
try {
|
|
2552
|
+
await fetch('/api/reject', { method: 'POST' });
|
|
2553
|
+
document.getElementById('reviewFooter').innerHTML =
|
|
2554
|
+
'<div class="changes-summary" style="color: var(--change-removed);">✕ Changes rejected. You can close this window.</div>';
|
|
2555
|
+
} catch (error) {
|
|
2556
|
+
alert('Failed to reject changes: ' + error.message);
|
|
2557
|
+
approveBtn.disabled = false;
|
|
2558
|
+
rejectBtn.disabled = false;
|
|
2559
|
+
}
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
loadFontsAndInit();
|
|
2564
|
+
</script>
|
|
2565
|
+
</body>
|
|
2566
|
+
</html>`;
|
|
2567
|
+
}
|