@sigloch/graph-view-edit 0.1.0
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 +100 -0
- package/bin/gve.mjs +41 -0
- package/dist/assets/index-Cxy3uYBx.css +1 -0
- package/dist/assets/index-UnMYySno.js +180 -0
- package/dist/index.html +13 -0
- package/package.json +50 -0
- package/src/config.mjs +49 -0
- package/vite.config.js +411 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# graph-view-edit
|
|
2
|
+
|
|
3
|
+
Das **Viewer/Editor-Familienmitglied** der aise-Familie — rendert den governten SE-Graphen,
|
|
4
|
+
den `graphcode` headless bereitstellt. graphcode liefert Daten, graph-view-edit rendert.
|
|
5
|
+
|
|
6
|
+
**Status:** MS-1 + MS-2 (Foundation + Renderer) + MS-3 (Edit-Surface) + MS-4 (Operational
|
|
7
|
+
Readiness, bis auf CR-GVE-116) — React/Vite/Zustand-Shell, deklarative View-Registry mit
|
|
8
|
+
11 Graph-Views + 16 Dokument-Views, SE-Dashboard als Sibling-Route, Edit-Mode mit gegatetem
|
|
9
|
+
Write-Pfad (CR-GVE-008).
|
|
10
|
+
|
|
11
|
+
## Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install
|
|
15
|
+
npm run dev # Vite-Dev-Server, http://localhost:5173
|
|
16
|
+
npm run build # Produktions-Bundle
|
|
17
|
+
npm run preview # Bundle lokal servieren
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Liest `docs/graph/<member>.graph.json` **client-seitig** direkt (kein zweiter Kuzu-Handle,
|
|
21
|
+
governance §8) — jede View fetched die committed Datei; kein Backend-Prozess nötig für die
|
|
22
|
+
Graph-Views.
|
|
23
|
+
|
|
24
|
+
## Routen
|
|
25
|
+
|
|
26
|
+
- **`/view`** — View-Registry-Switcher: Hierarchy, Structure, Flows, Action-Flow, State,
|
|
27
|
+
Matrix, Tabular, Impact-Map, radiales/layered Flow-Layout, Schaltplan/IC-View (elkjs) +
|
|
28
|
+
16 Dokument-Views. Deklarativ registriert in `src/view-descriptors.mjs`
|
|
29
|
+
(`src/view-registry.mjs`: `resolveViewCommand({name, scope, params})`).
|
|
30
|
+
- **`/dashboard`** — SE-Dashboard (Readiness/Recommendations/Artifacts/Health), Sibling-Route,
|
|
31
|
+
nicht Teil der View-Registry (Entscheid CR-GVE-D12). Die graphcode-Berechnung läuft
|
|
32
|
+
server-seitig in `vite.config.js`s Plugin-Middleware unter `GET /api/dashboard` —
|
|
33
|
+
`@sigloch/graphcode`s Panel-Funktionen ziehen Node-only-Internals nach, die im
|
|
34
|
+
Browser-Bundle nicht auflösen (siehe `docs/graph/graph-view-edit.graph.json`, CR-GVE-117/120).
|
|
35
|
+
|
|
36
|
+
## Edit-Mode (CR-GVE-008)
|
|
37
|
+
|
|
38
|
+
`?edit=1` an einer View-URL aktiviert die Edit-Surface (`src/edit-mode.mjs`); Default bleibt
|
|
39
|
+
strikt read-only — ohne den Query-Param wird `createEditSurface` gar nicht erst konstruiert.
|
|
40
|
+
|
|
41
|
+
**Write-Pfad — kein zweiter Kuzu-Owner:** graph-view-edit öffnet niemals selbst einen
|
|
42
|
+
Kuzu-Handle. `POST /api/mutate` (`vite.config.js`s `mutateApiPlugin`) forwarded
|
|
43
|
+
`{commands, baseVersion, consumerId}` über `@sigloch/graphcode`s `callHost` (CR-GC-241) an
|
|
44
|
+
`.graphcode/host.sock` — den lokalen Unix-Socket, den der gewählte `graphcode mcp`-Host
|
|
45
|
+
ohnehin schon offen hält (CR-GC-235). Der Aufruf läuft durch **dasselbe** `graph_mutate`, das
|
|
46
|
+
jede MCP-Session auch benutzt — kein zweiter Schreibpfad, kein eigenes Store-Handle. Fehlt der
|
|
47
|
+
Socket (kein `graphcode`-Prozess für dieses Repo aktiv), antwortet `/api/mutate` mit einem
|
|
48
|
+
klaren 503 statt einem stillen No-op.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Browser (?edit=1) → createEditSurface → command-bridge.mjs
|
|
52
|
+
→ POST /api/mutate (vite.config.js, node-seitig)
|
|
53
|
+
→ callHost('.graphcode/host.sock', 'graph_mutate', …)
|
|
54
|
+
→ derselbe Apply-Gate, den `graphcode mcp` bedient
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`src/ontology-adapter.mjs` übersetzt `SE_DESCRIPTOR` (graph-api-core-Form) in
|
|
58
|
+
`@sigloch/graph-edit-surface`s `OntologyDescriptor` (nodeTypes/edgeTypes/tracePatterns) — die
|
|
59
|
+
schema-abgeleitete Edit-Surface (`packages/graph-edit-surface/`) bleibt ontologie-agnostisch
|
|
60
|
+
und bietet nur strukturell legale Optionen an ("das Gate entscheidet, nie das Menü").
|
|
61
|
+
|
|
62
|
+
## Architektur
|
|
63
|
+
|
|
64
|
+
- **Tech-Stack (verriegelt, CR-GVE-D11):** React + Vite + Zustand, Cytoscape (Graph-Render),
|
|
65
|
+
elkjs (V12-Routing), TanStack Table (Data-Grid), Playwright (E2E/visuell), Vitest
|
|
66
|
+
(Unit/Component/API), CSS Custom Properties als Design-Tokens.
|
|
67
|
+
- **Renderer-Kern** (`src/viewer/Renderer.jsx` + `Chrome.jsx`): ein gemeinsamer
|
|
68
|
+
Cytoscape-Mount + Chrome (Zoom/Pan/Fit/Center, Typ+Kanten-Filter, Suche, Tiefe,
|
|
69
|
+
WeightProvider, Breadcrumb-Drilldown, Animation) für alle Graph-Views; jede View liefert
|
|
70
|
+
nur `selector`/`transform`/`layoutName`.
|
|
71
|
+
- **WeightProvider** (`src/weight-provider.mjs`): austauschbare Knotengröße/Kantenbreite
|
|
72
|
+
(uniform/traces/hot-spots).
|
|
73
|
+
- **elkjs-Routing** (`src/elk-routing.mjs`): FUNC als IC-Bausteine, FLOW→Ports, orthogonales
|
|
74
|
+
Layered-Routing — Herkunft: Spike `spikes/sp05-icview-elk.mjs`.
|
|
75
|
+
|
|
76
|
+
## Tests
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm test # vitest: Unit/Component (jsdom) + Playwright-Visualtests (echter Browser)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `tests/*.test.mjs` — Component-/Unit-Tests (Vitest + Testing Library, jsdom).
|
|
83
|
+
- `tests/visual/*.spec.mjs` — CR-GVE-109: Overlap-Gate (Playwright gegen echten
|
|
84
|
+
`vite`-Dev-Server, Bounding-Box-Kollisionsprüfung der Node-Marker) + Kontrast-Gate
|
|
85
|
+
(WCAG-AA-Messung der Design-Token-Farbpaare, reine Berechnung).
|
|
86
|
+
|
|
87
|
+
## Roadmap
|
|
88
|
+
|
|
89
|
+
- **MS-3 (Edit-Surface) — done:** Cytoscape-Edit-Surface (`packages/graph-edit-surface/`,
|
|
90
|
+
CR-ES-001..004), Command-Bridge/Gate-Feedback/Text-Edit/Draft-Status (CR-GVE-110..113),
|
|
91
|
+
Write-Pfad-Adoption (CR-GVE-008, s.o.).
|
|
92
|
+
- **MS-4 (Operational Readiness):** dynamische Config + Access-Control (CR-GVE-114, done),
|
|
93
|
+
NFR-Regression-Suite (CR-GVE-115, done — `tests/nfr/*.bench.mjs`/`*.smoke.mjs`).
|
|
94
|
+
Offen: **CR-GVE-116** (voller Playwright-E2E-Customer-Journey über den echten Edit-Mode).
|
|
95
|
+
- **CR-GVE-124** (Label-Overlap-Vermeidung) — getrackt, noch nicht file-scoped, blockiert
|
|
96
|
+
MS-4 nicht.
|
|
97
|
+
|
|
98
|
+
**Lock:** graphcode bleibt headless; ein Kuzu-Owner pro Repo — der Viewer öffnet **keinen**
|
|
99
|
+
zweiten DB-Handle. Der Edit-Mode-Schreibpfad geht über `.graphcode/host.sock`
|
|
100
|
+
(`@sigloch/graphcode`s CR-GC-241-Export), nicht über einen eigenen Handle.
|
package/bin/gve.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// gve CLI (CR-GVE-181): serve the prebuilt viewer+editor against any repo.
|
|
3
|
+
// npx @sigloch/graph-view-edit --repo <path> [--port <n>]
|
|
4
|
+
// Runs `vite preview` on the shipped dist/; the API plugins (config,
|
|
5
|
+
// dashboard, mutate, events — vite.config.js) already attach there via
|
|
6
|
+
// configurePreviewServer. GVE_REPO_ROOT must be set even for cwd: preview
|
|
7
|
+
// serves only dist/, so docs/graph/* comes from graphStaticPlugin.
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
import { dirname, join, resolve } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const opt = (name) => {
|
|
15
|
+
const i = args.indexOf(name);
|
|
16
|
+
return i !== -1 ? args[i + 1] : undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const repoRoot = resolve(opt('--repo') ?? process.cwd());
|
|
20
|
+
if (!existsSync(repoRoot)) {
|
|
21
|
+
console.error(`gve: repo not found: ${repoRoot}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
if (!existsSync(join(pkgRoot, 'dist', 'index.html'))) {
|
|
25
|
+
console.error('gve: no dist/ in the package — run `npm run build` first (published packages ship it via prepack)');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
process.env.GVE_REPO_ROOT = repoRoot;
|
|
30
|
+
// Pin the config lookup to the TARGET repo (loadConfig treats a missing file
|
|
31
|
+
// as schema defaults) — otherwise a config.json in the cwd/package dir would
|
|
32
|
+
// hijack a --repo run. An explicit GVE_CONFIG_PATH still wins.
|
|
33
|
+
if (!process.env.GVE_CONFIG_PATH) process.env.GVE_CONFIG_PATH = join(repoRoot, 'config.json');
|
|
34
|
+
|
|
35
|
+
const { preview } = await import('vite');
|
|
36
|
+
const port = opt('--port');
|
|
37
|
+
const server = await preview({
|
|
38
|
+
root: pkgRoot,
|
|
39
|
+
preview: port ? { port: Number(port), strictPort: true } : {},
|
|
40
|
+
});
|
|
41
|
+
server.printUrls();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.gve-ctx{position:absolute;z-index:30;min-width:220px;max-width:320px;max-height:60vh;overflow-y:auto;display:flex;flex-direction:column;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:var(--shadow-1);padding:var(--space-1);font-family:var(--font-ui);font-size:var(--text-sm)}.gve-ctx-head{padding:var(--space-1) var(--space-2);font-family:var(--font-mono);font-size:var(--text-xs);color:var(--color-text-dim);border-bottom:1px solid var(--color-border);margin-bottom:var(--space-1)}.gve-ctx-item{text-align:left;padding:var(--space-1) var(--space-2);background:none;border:none;border-radius:var(--radius-sm);color:var(--color-text);cursor:pointer}.gve-ctx-item:hover:not(:disabled){background:var(--color-accent-soft)}.gve-ctx-item:disabled{color:var(--color-text-dim);cursor:not-allowed}.gve-ctx-form{display:flex;flex-direction:column;gap:var(--space-1);padding:var(--space-2)}.gve-ctx-form label{font-size:var(--text-xs);color:var(--color-text-dim)}.gve-ctx-form input,.gve-ctx-form textarea{font-family:var(--font-ui);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-2)}.gve-ctx-empty{padding:var(--space-1) var(--space-2);color:var(--color-text-dim);font-size:var(--text-xs)}.gve-genesis{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:20;display:flex;flex-direction:column;gap:var(--space-2);min-width:240px;padding:var(--space-3);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:var(--shadow-1);font-family:var(--font-ui);font-size:var(--text-sm);color:var(--color-text);text-align:center}.gve-genesis-title{font-family:var(--font-mono);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.04em;color:var(--color-text-dim)}.gve-genesis-hint{margin:0;color:var(--color-text-dim)}.gve-genesis-input{font-family:var(--font-ui);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-2)}.gve-genesis-create{padding:var(--space-1) var(--space-2);background:var(--color-accent-soft);border:1px solid var(--color-border);border-radius:var(--radius-sm);color:var(--color-text);cursor:pointer}.gve-genesis-create:disabled{color:var(--color-text-dim);cursor:not-allowed}.gve-doc{font-family:var(--font-ui);font-size:var(--text-base);line-height:1.55;color:var(--color-text);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:var(--space-8) var(--space-6);margin:var(--space-3) auto;max-width:820px}.gve-doc h1{font-size:var(--text-xl);margin:0 0 var(--space-3)}.gve-doc h2{font-size:var(--text-lg);margin:var(--space-6) 0 var(--space-3);padding-bottom:var(--space-1);border-bottom:1px solid var(--color-border)}.gve-doc h3{font-size:var(--text-base);margin:var(--space-4) 0 var(--space-2)}.gve-doc h4,.gve-doc h5,.gve-doc h6{font-size:var(--text-sm);margin:var(--space-3) 0 var(--space-1)}.gve-doc h5 code,.gve-doc h6 code{color:var(--color-text-dim)}.gve-doc p{margin:0 0 var(--space-2)}.gve-doc code{font-family:var(--font-mono);font-size:.92em;background:var(--color-surface-2);border-radius:var(--radius-sm);padding:0 var(--space-1)}.gve-doc blockquote{margin:0 0 var(--space-2);padding-left:var(--space-3);border-left:2px solid var(--color-border);color:var(--color-text-dim);font-size:var(--text-sm)}.gve-doc hr{border:0;border-top:1px solid var(--color-border);margin:var(--space-4) 0}.gve-doc pre.md-code{font-family:var(--font-mono);font-size:var(--text-sm);background:var(--color-surface-2);border-radius:var(--radius-sm);padding:var(--space-2) var(--space-3);overflow-x:auto}.edit-pen{margin-left:var(--space-2);font-size:var(--text-xs);background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:var(--radius-sm);cursor:pointer}.edit-box{margin:var(--space-2) 0;padding:var(--space-3);background:var(--color-surface-2);border:1px solid var(--color-accent);border-radius:var(--radius-md)}.edit-box textarea{width:100%;box-sizing:border-box;font-family:var(--font-mono);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);padding:var(--space-2)}.cmd-preview{font-family:var(--font-mono);font-size:var(--text-xs);color:var(--color-text-dim);background:var(--color-surface);border:1px dashed var(--color-border);border-radius:var(--radius-sm);padding:var(--space-2);overflow-x:auto;white-space:pre-wrap}.edit-actions{display:flex;gap:var(--space-2);align-items:center;margin-top:var(--space-2)}.edit-actions .btn-primary{background:var(--color-accent);color:var(--color-text-inverse);border:1px solid var(--color-accent);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-3);cursor:pointer}.edit-actions .btn-primary:disabled{opacity:.5;cursor:not-allowed}[data-testid=edit-errors],[data-testid=formate-errors]{color:var(--color-error);font-size:var(--text-xs)}[data-testid=edit-verdict][data-tier=block]{color:var(--color-error)}[data-testid=edit-verdict]{font-size:var(--text-xs);color:var(--color-text-dim)}.gve-cards{flex:1;overflow:auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:var(--space-4);padding:var(--space-4);align-content:start}.gve-card{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:var(--shadow-1);padding:var(--space-4)}.gve-card-wide{grid-column:1 / -1}.gve-card h3{font-size:var(--text-xs);font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--color-text-dim);margin-bottom:var(--space-3);display:flex;align-items:center;gap:var(--space-2)}.gve-card h4{font-size:var(--text-xs);font-weight:600;color:var(--color-text-dim);margin-bottom:var(--space-2);display:flex;align-items:center;gap:var(--space-2)}.gve-count{font-size:var(--text-xs);font-weight:600;color:var(--color-text-inverse);background:var(--color-accent);border-radius:999px;padding:0 var(--space-2)}.gve-kpi-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:var(--space-3);margin-bottom:var(--space-4)}.gve-kpi-tile{display:flex;flex-direction:column;gap:var(--space-1);background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:var(--radius-sm);padding:var(--space-3)}.gve-kpi-value{font-size:var(--text-xl);font-weight:600;line-height:1.1}.gve-kpi-label{font-size:var(--text-xs);color:var(--color-text-dim)}.gve-kpi-tile[data-tone=ok] .gve-kpi-value{color:var(--color-ok)}.gve-kpi-tile[data-tone=warn] .gve-kpi-value{color:var(--color-warn)}.gve-kpi-tile[data-tone=error] .gve-kpi-value{color:var(--color-error)}.gve-gate-cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:var(--space-4)}.gve-gates{list-style:none;padding:0;display:flex;flex-direction:column;gap:var(--space-2)}.gve-gate{display:grid;grid-template-columns:auto 44px 1fr auto 44px;align-items:center;gap:var(--space-2);font-size:var(--text-sm)}.gve-light{width:10px;height:10px;border-radius:50%;flex-shrink:0}.gve-light[data-state=passed]{background:var(--color-ok)}.gve-light[data-state=open]{background:var(--color-warn)}.gve-gate-id{font-family:var(--font-mono);font-weight:600}.gve-gate-label{color:var(--color-text-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.gve-gate-track{grid-column:4;width:90px;height:12px;background:var(--color-surface-2);border-radius:999px;overflow:hidden}.gve-gate-fill{display:block;height:100%;border-radius:999px}.gve-gate-fill[data-state=passed]{background:var(--color-ok)}.gve-gate-fill[data-state=open]{background:var(--color-warn)}.gve-gate-pct{font-family:var(--font-mono);text-align:right}.gve-gate-blk{grid-column:1 / -1;font-size:var(--text-xs);color:var(--color-warn)}.gve-legend{margin-top:var(--space-3);font-size:var(--text-xs);color:var(--color-text-dim)}.gve-rec-group{margin-bottom:var(--space-4)}.gve-rec-group h4[data-severity=error] .gve-count{background:var(--color-error)}.gve-rec-group h4[data-severity=warning] .gve-count{background:var(--color-warn)}.gve-rec-group h4[data-severity=info] .gve-count{background:var(--color-accent)}.gve-rec{border:1px solid var(--color-border);border-left-width:3px;border-radius:var(--radius-sm);padding:var(--space-2) var(--space-3);margin-bottom:var(--space-2);background:var(--color-surface-2)}.gve-rec[data-severity=error]{border-left-color:var(--color-error)}.gve-rec[data-severity=warning]{border-left-color:var(--color-warn)}.gve-rec[data-severity=info]{border-left-color:var(--color-accent)}.gve-rec-head{display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-1)}.gve-rid{font-family:var(--font-mono);font-size:var(--text-xs);color:var(--color-text-dim)}.gve-rec-el{color:var(--color-accent)}.gve-sev{font-size:var(--text-xs);font-weight:600;text-transform:uppercase;border-radius:var(--radius-sm);padding:0 var(--space-2)}.gve-sev[data-severity=error]{color:var(--color-error)}.gve-sev[data-severity=warning]{color:var(--color-warn)}.gve-sev[data-severity=info]{color:var(--color-accent)}.gve-rec-msg{font-size:var(--text-sm)}.gve-hint{margin-top:var(--space-1);font-size:var(--text-xs);color:var(--color-text-dim)}.gve-empty{font-size:var(--text-sm);color:var(--color-text-dim)}.gve-rollup summary{cursor:pointer;font-size:var(--text-sm);color:var(--color-text-dim);padding:var(--space-1) 0;-webkit-user-select:none;user-select:none}.gve-rollup summary:hover{color:var(--color-text)}.gve-rollup[open] summary{margin-bottom:var(--space-2)}.gve-fresh-list{list-style:none;padding:0;display:flex;flex-direction:column;gap:var(--space-2)}.gve-fresh-2col{display:grid;grid-template-columns:1fr 1fr;column-gap:var(--space-4)}.gve-fresh-list li{display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm)}.gve-fresh-label{flex:1}.gve-fresh-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.gve-fresh-dot[data-freshness=live]{background:var(--color-ok)}.gve-fresh-dot[data-freshness=stale]{background:var(--color-warn)}.gve-fresh-dot[data-freshness=absent]{background:var(--color-error)}.gve-pill{font-size:var(--text-xs);border:1px solid var(--color-border);border-radius:999px;padding:0 var(--space-2)}.gve-pill[data-freshness=live]{border-color:var(--color-ok);color:var(--color-ok)}.gve-pill[data-freshness=stale]{border-color:var(--color-warn);color:var(--color-warn)}.gve-pill[data-freshness=absent]{border-color:var(--color-error);color:var(--color-error)}.gve-health-status{margin-bottom:var(--space-3);text-transform:uppercase}.gve-health-status[data-state=ok]{color:var(--color-ok)}.gve-health-status[data-state=error]{color:var(--color-error)}.gve-vtable{width:100%;border-collapse:collapse;font-size:var(--text-sm)}.gve-vtable td{padding:var(--space-1) var(--space-2);border-bottom:1px solid var(--color-border)}.gve-vtable td:first-child{font-family:var(--font-mono);color:var(--color-text-dim);white-space:nowrap}a.gve-btn{text-decoration:none;display:inline-block}.gve-appbar .title{font-size:var(--text-base)}.gve .gve{height:auto;flex:1;min-height:0}.gve-chip .dash{width:12px;height:0;border-top:2px solid var(--color-text-dim)}.gve .gve-body{flex:1;min-height:0}.gve-canvas,.gve-explorer .exp-tree,.gve-details{overflow:auto}.gve-tree .tw{cursor:pointer;-webkit-user-select:none;user-select:none}.gve-tree .row.sel{box-shadow:inset 2px 0 0 var(--color-accent)}.gve-tree .row.dim{opacity:.45;cursor:default}.gve-tree .det-tw{margin-left:var(--space-2);cursor:pointer;-webkit-user-select:none;user-select:none;background:none;border:0;padding:0 var(--space-1);color:var(--color-text-dim);font-size:var(--text-sm)}.gve-tree .det-tw.open,.gve-tree .det-tw:hover{color:var(--color-accent)}.gve-tree .row-details{margin:var(--space-1) 0 var(--space-2) 28px;padding:var(--space-2);border-left:2px solid var(--color-border);display:flex;flex-direction:column;gap:var(--space-1)}.gve-tree .row-details .det-desc{margin:0 0 var(--space-1);color:var(--color-text-dim);font-size:var(--text-sm)}.gve-tree .det-group{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-1) var(--space-2)}.gve-tree .det-type{font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.04em;color:var(--color-accent);min-width:64px}.gve-tree .det-neighbor{display:inline-flex;align-items:center;gap:var(--space-1);font-size:var(--text-sm);padding:0 var(--space-2);border:1px solid var(--color-border);border-radius:var(--radius-sm)}.gve-tree .det-neighbor .kind{font-style:normal;color:var(--color-text-dim);font-size:var(--text-xs)}.gve-grid-scroll{overflow:auto;flex:1;min-height:0}.gve-grid{border-collapse:collapse;font-size:var(--text-sm)}.gve-grid th,.gve-grid td{border:1px solid var(--color-border);padding:var(--space-1) var(--space-2);text-align:left;white-space:nowrap}.gve-grid thead th{position:sticky;top:0;z-index:1;cursor:pointer;-webkit-user-select:none;user-select:none;background:var(--color-surface-alt, var(--color-surface));color:var(--color-text)}.gve-grid thead th.corner{left:0;z-index:2}.gve-grid td.rowhead,.gve-grid th.corner{font-weight:600}.gve-grid td.rowhead{position:sticky;left:0;background:var(--color-surface)}.gve-grid td.hit{background:var(--color-accent-soft);color:var(--color-accent);text-align:center;font-weight:600}.tabular-grid th,.tabular-grid td{max-width:20rem}.tabular-grid:not(.wrap) th,.tabular-grid:not(.wrap) td{overflow:hidden;text-overflow:ellipsis}.tabular-grid.wrap th,.tabular-grid.wrap td{white-space:normal;word-break:break-word;vertical-align:top}.gve-group.tabs .gve-chip.active{background:var(--color-accent-soft);color:var(--color-accent);border-color:var(--color-accent)}:root{--color-bg: #f6f7f9;--color-surface: #ffffff;--color-surface-2: #eef0f3;--color-border: #d7dbe0;--color-text: #1a1d21;--color-text-dim: #5c6470;--color-text-inverse: #ffffff;--color-accent: #2563eb;--color-accent-soft: #dbeafe;--color-ok: #16a34a;--color-warn: #d97706;--color-error: #dc2626;--color-hotspot: #e11d48;--color-type-1: #2563eb;--color-type-2: #7c3aed;--color-type-3: #0891b2;--color-type-4: #16a34a;--color-type-5: #d97706;--color-type-6: #64748b;--color-type-7: #db2777;--color-type-8: #0d9488;--color-type-func: #0d9488;--color-type-flow: #2dd4bf;--fchain-1: #8b5cf6;--fchain-2: #f59e0b;--fchain-3: #ec4899;--fchain-4: #0284c7;--fchain-5: #84cc16;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-6: 24px;--space-8: 32px;--text-xs: 11px;--text-sm: 12.5px;--text-base: 14px;--text-lg: 16px;--text-xl: 20px;--font-ui: -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono: ui-monospace, "SF Mono", Menlo, monospace;--radius-sm: 4px;--radius-md: 8px;--shadow-1: 0 1px 3px rgba(0,0,0,.12)}@media (prefers-color-scheme: dark){:root{--color-bg: #14161a;--color-surface: #1e2126;--color-surface-2: #262a31;--color-border: #363c45;--color-text: #e8eaed;--color-text-dim: #9aa3af;--color-text-inverse: #14161a;--color-accent: #60a5fa;--color-accent-soft: #1e3a5f;--color-type-2: #8b5cf6;--shadow-1: 0 1px 3px rgba(0,0,0,.5)}}*{box-sizing:border-box;margin:0}.gve{font-family:var(--font-ui);font-size:var(--text-base);background:var(--color-bg);color:var(--color-text);display:flex;flex-direction:column;height:100vh;overflow:hidden}.gve-bar{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) var(--space-4);background:var(--color-surface);border-bottom:1px solid var(--color-border);flex-wrap:wrap}.gve-bar .title{font-size:var(--text-lg);font-weight:600;margin-right:var(--space-4)}.gve-bar .title small{color:var(--color-text-dim);font-weight:400;margin-left:var(--space-2)}.gve-group{display:flex;align-items:center;gap:var(--space-1);background:var(--color-surface-2);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:2px var(--space-2)}.gve-group label{font-size:var(--text-xs);color:var(--color-text-dim);margin-right:var(--space-1)}.gve-btn{font:inherit;font-size:var(--text-sm);color:var(--color-text);background:transparent;border:none;border-radius:var(--radius-sm);padding:var(--space-1) var(--space-2);cursor:pointer}.gve-btn:hover{background:var(--color-accent-soft)}.gve-btn.active{background:var(--color-accent);color:var(--color-text-inverse)}.gve-input{font:inherit;font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);padding:var(--space-1) var(--space-2);width:160px}.gve-select{font:inherit;font-size:var(--text-sm);background:var(--color-surface);color:var(--color-text);border:1px solid var(--color-border);border-radius:var(--radius-sm);padding:var(--space-1)}.gve-chip{display:inline-flex;align-items:center;gap:4px;font-size:var(--text-xs);border:1px solid var(--color-border);border-radius:999px;padding:2px var(--space-2);cursor:pointer;background:var(--color-surface);color:var(--color-text)}.gve-chip .dot{width:8px;height:8px;border-radius:50%}.gve-chip.off{opacity:.35}.gve-crumbs{font-size:var(--text-sm);color:var(--color-text-dim)}.gve-crumbs a{color:var(--color-accent);text-decoration:none}.gve-body{flex:1;display:flex;min-height:0}.gve-canvas{flex:1;overflow:auto;position:relative;min-width:0}.gve-explorer{width:260px;flex:0 0 260px;display:flex;flex-direction:column;background:var(--color-surface);border-right:1px solid var(--color-border);overflow:hidden;resize:horizontal}.gve-explorer .exp-tabs{display:flex;border-bottom:1px solid var(--color-border)}.gve-explorer .exp-tab{flex:1;font:inherit;font-size:var(--text-sm);cursor:pointer;background:transparent;border:none;padding:var(--space-2);color:var(--color-text-dim);border-bottom:2px solid transparent}.gve-explorer .exp-tab.active{color:var(--color-text);border-bottom-color:var(--color-accent);font-weight:600}.gve-explorer .exp-tree{flex:1;overflow:auto;padding:var(--space-2) 0;font-size:var(--text-sm)}.gve-tree,.gve-tree ul{list-style:none;margin:0;padding:0}.gve-tree ul{margin-left:14px;border-left:1px solid var(--color-border)}.gve-tree li{position:relative}.gve-tree .row{display:flex;align-items:center;gap:6px;padding:2px var(--space-2);white-space:nowrap;cursor:pointer;border-radius:var(--radius-sm)}.gve-tree .row:hover,.gve-tree .row.sel{background:var(--color-accent-soft)}.gve-tree .tw{width:10px;color:var(--color-text-dim);font-size:10px;flex:none}.gve-tree .ic{width:8px;height:8px;border-radius:2px;flex:none}.gve-tree .ic.ell{border-radius:50%}.gve-tree .lbl{overflow:hidden;text-overflow:ellipsis}.gve-tree .kind{font-size:var(--text-xs);color:var(--color-text-dim)}.gve-details{width:240px;flex:0 0 240px;overflow:auto;background:var(--color-surface);border-left:1px solid var(--color-border);padding:var(--space-3);font-size:var(--text-sm)}.gve-details h4{font-size:var(--text-sm);margin-bottom:var(--space-2)}.gve-details dt{font-size:var(--text-xs);color:var(--color-text-dim);margin-top:var(--space-2)}.gve-details dd{margin:0}.gve-status{display:flex;gap:var(--space-6);align-items:center;padding:var(--space-1) var(--space-4);font-size:var(--text-xs);color:var(--color-text-dim);background:var(--color-surface);border-top:1px solid var(--color-border)}.gve-node rect,.gve-node ellipse{fill:var(--color-surface);stroke:var(--t, var(--color-border));stroke-width:1.5}.gve-node text{font-family:var(--font-ui);font-size:var(--text-sm);fill:var(--color-text)}.gve-node .pill{fill:var(--t)}.gve-node .pill-text{fill:var(--color-text-inverse);font-size:var(--text-xs);font-weight:600}.gve-node.hotspot rect,.gve-node.hotspot ellipse{stroke:var(--color-hotspot);stroke-width:3}.gve-edge{stroke:var(--color-text-dim);fill:none;opacity:.55}.gve-edge.emph{stroke:var(--color-accent);opacity:1}.gve-edge-label{font-size:var(--text-xs);fill:var(--color-text-dim)}.gve-grid{border-collapse:collapse;margin:var(--space-4);font-size:var(--text-sm)}.gve-grid th,.gve-grid td{border:1px solid var(--color-border);padding:var(--space-1) var(--space-2);text-align:left}.gve-grid th{background:var(--color-surface-2);position:sticky;top:0}.gve-grid td.hit{background:var(--color-accent-soft);text-align:center}.gve-grid td.miss{color:var(--color-error);text-align:center}.gve-toast{position:absolute;right:var(--space-4);bottom:var(--space-4);background:var(--color-surface);border:1px solid var(--color-border);border-left:4px solid var(--color-warn);border-radius:var(--radius-md);box-shadow:var(--shadow-1);padding:var(--space-3) var(--space-4);font-size:var(--text-sm);max-width:360px}
|