trellis 2.1.9 → 3.0.2
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 +65 -796
- package/dist/cli/index.d.ts +3 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2948 -182
- package/dist/client/index.js +4 -4
- package/dist/context/heat-map-manager.d.ts +100 -0
- package/dist/context/heat-map-manager.d.ts.map +1 -0
- package/dist/context/manager.d.ts +16 -0
- package/dist/context/manager.d.ts.map +1 -0
- package/dist/context/types.d.ts +20 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/core/agents/harness.d.ts +10 -1
- package/dist/core/agents/harness.d.ts.map +1 -1
- package/dist/core/agents/types.d.ts +18 -2
- package/dist/core/agents/types.d.ts.map +1 -1
- package/dist/core/computation/expr-evaluator.d.ts +52 -0
- package/dist/core/computation/expr-evaluator.d.ts.map +1 -0
- package/dist/core/index.js +93 -5
- package/dist/core/kernel/logic-middleware.d.ts +19 -0
- package/dist/core/kernel/logic-middleware.d.ts.map +1 -0
- package/dist/core/kernel/schema-middleware.d.ts +15 -0
- package/dist/core/kernel/schema-middleware.d.ts.map +1 -0
- package/dist/core/kernel/security-middleware.d.ts +24 -0
- package/dist/core/kernel/security-middleware.d.ts.map +1 -0
- package/dist/core/kernel/sync-provider.d.ts +59 -0
- package/dist/core/kernel/sync-provider.d.ts.map +1 -0
- package/dist/core/kernel/trellis-kernel.d.ts +55 -0
- package/dist/core/kernel/trellis-kernel.d.ts.map +1 -1
- package/dist/core/ontology/builtins.d.ts.map +1 -1
- package/dist/core/ontology/core-ontology.d.ts +20 -0
- package/dist/core/ontology/core-ontology.d.ts.map +1 -0
- package/dist/core/ontology/index.d.ts +3 -1
- package/dist/core/ontology/index.d.ts.map +1 -1
- package/dist/core/ontology/types.d.ts +138 -34
- package/dist/core/ontology/types.d.ts.map +1 -1
- package/dist/core/persist/backend.d.ts +2 -0
- package/dist/core/persist/backend.d.ts.map +1 -1
- package/dist/core/persist/better-sqlite-backend.d.ts +33 -0
- package/dist/core/persist/better-sqlite-backend.d.ts.map +1 -0
- package/dist/core/persist/sqlite-backend.d.ts +2 -0
- package/dist/core/persist/sqlite-backend.d.ts.map +1 -1
- package/dist/core/store/eav-store.d.ts +4 -0
- package/dist/core/store/eav-store.d.ts.map +1 -1
- package/dist/db/index.js +10 -8
- package/dist/{deploy-99j5dc9c.js → deploy-999q207z.js} +2 -1
- package/dist/engine.d.ts +3 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/evals/types.d.ts +29 -0
- package/dist/evals/types.d.ts.map +1 -0
- package/dist/{import-fyg5sgq4.js → import-s2b8e0ft.js} +2 -2
- package/dist/{index-3ejh8k6v.js → index-0q7wbasy.js} +18 -4
- package/dist/{index-7t92ej34.js → index-0zk3fx2s.js} +467 -7
- package/dist/{index-xr7rx360.js → index-6n5dcebj.js} +33 -0
- package/dist/{index-4beszbgg.js → index-7e27kvvj.js} +1 -1
- package/dist/index-bmyt7k8n.js +90 -0
- package/dist/{index-k5kf7sd0.js → index-hmdbnd4n.js} +1 -1
- package/dist/{index-czecrvvn.js → index-q31hfjja.js} +858 -48
- package/dist/{index-8fjwnztt.js → index-skhn0agf.js} +1 -1
- package/dist/{index-04sq3h27.js → index-w7ng765c.js} +3 -1
- package/dist/{index-hgd30epa.js → index-wt8rz4gn.js} +4 -21
- package/dist/{index-5p6zgspx.js → index-y3d71wzd.js} +1 -1
- package/dist/index-y6a4kj0p.js +43 -0
- package/dist/{index-5bhe57y9.js → index-yhwjgfvj.js} +16 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -6
- package/dist/llm/provider.d.ts +11 -0
- package/dist/llm/provider.d.ts.map +1 -0
- package/dist/llm/types.d.ts +74 -0
- package/dist/llm/types.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +7 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/orchestration/types.d.ts +22 -0
- package/dist/orchestration/types.d.ts.map +1 -0
- package/dist/plugins/agent-memory/graph-context-manager.d.ts +75 -0
- package/dist/plugins/agent-memory/graph-context-manager.d.ts.map +1 -0
- package/dist/plugins/agent-memory/index.d.ts +30 -0
- package/dist/plugins/agent-memory/index.d.ts.map +1 -0
- package/dist/plugins/agent-memory/ontology.d.ts +13 -0
- package/dist/plugins/agent-memory/ontology.d.ts.map +1 -0
- package/dist/plugins/agent-memory/plugin.d.ts +17 -0
- package/dist/plugins/agent-memory/plugin.d.ts.map +1 -0
- package/dist/plugins/brand/cache.d.ts +18 -0
- package/dist/plugins/brand/cache.d.ts.map +1 -0
- package/dist/plugins/brand/catalog-generator.d.ts +89 -0
- package/dist/plugins/brand/catalog-generator.d.ts.map +1 -0
- package/dist/plugins/brand/constraints.d.ts +55 -0
- package/dist/plugins/brand/constraints.d.ts.map +1 -0
- package/dist/plugins/brand/index.d.ts +44 -0
- package/dist/plugins/brand/index.d.ts.map +1 -0
- package/dist/plugins/brand/mcp-tools.d.ts +101 -0
- package/dist/plugins/brand/mcp-tools.d.ts.map +1 -0
- package/dist/plugins/brand/ontology.d.ts +13 -0
- package/dist/plugins/brand/ontology.d.ts.map +1 -0
- package/dist/plugins/brand/plugin.d.ts +21 -0
- package/dist/plugins/brand/plugin.d.ts.map +1 -0
- package/dist/plugins/brand/voice-tone.d.ts +24 -0
- package/dist/plugins/brand/voice-tone.d.ts.map +1 -0
- package/dist/plugins/idea-garden/api.d.ts +26 -0
- package/dist/plugins/idea-garden/api.d.ts.map +1 -0
- package/dist/plugins/idea-garden/index.d.ts +12 -0
- package/dist/plugins/idea-garden/index.d.ts.map +1 -0
- package/dist/plugins/idea-garden/plugin.d.ts +16 -0
- package/dist/plugins/idea-garden/plugin.d.ts.map +1 -0
- package/dist/plugins/idea-garden/types.d.ts +22 -0
- package/dist/plugins/idea-garden/types.d.ts.map +1 -0
- package/dist/plugins/plan-approval/index.d.ts +36 -0
- package/dist/plugins/plan-approval/index.d.ts.map +1 -0
- package/dist/plugins/plan-approval/ontology.d.ts +11 -0
- package/dist/plugins/plan-approval/ontology.d.ts.map +1 -0
- package/dist/plugins/plan-approval/plan-manager.d.ts +104 -0
- package/dist/plugins/plan-approval/plan-manager.d.ts.map +1 -0
- package/dist/plugins/plan-approval/plugin.d.ts +110 -0
- package/dist/plugins/plan-approval/plugin.d.ts.map +1 -0
- package/dist/plugins/proactive-watcher/index.d.ts +28 -0
- package/dist/plugins/proactive-watcher/index.d.ts.map +1 -0
- package/dist/plugins/proactive-watcher/ontology.d.ts +8 -0
- package/dist/plugins/proactive-watcher/ontology.d.ts.map +1 -0
- package/dist/plugins/proactive-watcher/plugin.d.ts +20 -0
- package/dist/plugins/proactive-watcher/plugin.d.ts.map +1 -0
- package/dist/plugins/proactive-watcher/watcher-manager.d.ts +36 -0
- package/dist/plugins/proactive-watcher/watcher-manager.d.ts.map +1 -0
- package/dist/plugins/sprite-tools/checkpoint-middleware.d.ts +43 -0
- package/dist/plugins/sprite-tools/checkpoint-middleware.d.ts.map +1 -0
- package/dist/plugins/sprite-tools/index.d.ts +40 -0
- package/dist/plugins/sprite-tools/index.d.ts.map +1 -0
- package/dist/plugins/sprite-tools/plugin.d.ts +69 -0
- package/dist/plugins/sprite-tools/plugin.d.ts.map +1 -0
- package/dist/react/index.js +4 -4
- package/dist/scaffold/index.d.ts +13 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/infer.d.ts +42 -0
- package/dist/scaffold/infer.d.ts.map +1 -0
- package/dist/scaffold/profile.d.ts +51 -0
- package/dist/scaffold/profile.d.ts.map +1 -0
- package/dist/scaffold/seed.d.ts +27 -0
- package/dist/scaffold/seed.d.ts.map +1 -0
- package/dist/scaffold/write.d.ts +53 -0
- package/dist/scaffold/write.d.ts.map +1 -0
- package/dist/{sdk-sj8rp0m7.js → sdk-snn5gad3.js} +4 -4
- package/dist/server/deploy.d.ts.map +1 -1
- package/dist/server/index.d.ts +5 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +37 -7
- package/dist/server/sprites.d.ts +26 -0
- package/dist/server/sprites.d.ts.map +1 -0
- package/dist/server/vm-config.d.ts +60 -0
- package/dist/server/vm-config.d.ts.map +1 -0
- package/dist/{server-3vkpnpbz.js → server-mrctdwzr.js} +2 -2
- package/dist/sprites-vc4qbrp1.js +16 -0
- package/dist/streaming/types.d.ts +43 -0
- package/dist/streaming/types.d.ts.map +1 -0
- package/dist/{tenancy-tjr7kk2v.js → tenancy-7d1g4ayp.js} +3 -3
- package/dist/ui/client.html +460 -664
- package/dist/ui/server.d.ts +6 -2
- package/dist/ui/server.d.ts.map +1 -1
- package/dist/vcs/decompose.d.ts.map +1 -1
- package/dist/vcs/index.js +2 -2
- package/dist/vcs/issue.d.ts.map +1 -1
- package/dist/vcs/types.d.ts +1 -0
- package/dist/vcs/types.d.ts.map +1 -1
- package/dist/vm-config-6xhsj6b3.js +22 -0
- package/package.json +14 -4
- /package/dist/{index-kbnht9p8.js → index-c9h37r6h.js} +0 -0
package/dist/ui/client.html
CHANGED
|
@@ -3,692 +3,488 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Trellis —
|
|
6
|
+
<title>Trellis — System Visualizer</title>
|
|
7
|
+
<meta name="description" content="Interactive system dashboard for exploring the Trellis semantic kernel">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
7
10
|
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
|
|
8
11
|
<style>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.detail-ops .op-kind {
|
|
144
|
-
font-family: 'SF Mono', SFMono-Regular, Consolas, monospace;
|
|
145
|
-
font-size: 11px;
|
|
146
|
-
color: var(--cyan);
|
|
147
|
-
}
|
|
148
|
-
.detail-ops .op-time { color: var(--text-dim); }
|
|
149
|
-
|
|
150
|
-
/* Search results overlay */
|
|
151
|
-
#search-results {
|
|
152
|
-
position: fixed; top: 52px; left: 0; right: 0;
|
|
153
|
-
max-height: 50vh;
|
|
154
|
-
background: var(--surface);
|
|
155
|
-
border-bottom: 1px solid var(--border);
|
|
156
|
-
overflow-y: auto;
|
|
157
|
-
display: none;
|
|
158
|
-
z-index: 80;
|
|
159
|
-
}
|
|
160
|
-
#search-results.visible { display: block; }
|
|
161
|
-
.search-result {
|
|
162
|
-
padding: 10px 16px;
|
|
163
|
-
border-bottom: 1px solid var(--border);
|
|
164
|
-
cursor: pointer;
|
|
165
|
-
transition: background 0.1s;
|
|
166
|
-
}
|
|
167
|
-
.search-result:hover { background: rgba(88,166,255,0.08); }
|
|
168
|
-
.search-result .sr-score {
|
|
169
|
-
font-size: 11px; font-weight: 600;
|
|
170
|
-
color: var(--green); margin-right: 8px;
|
|
171
|
-
}
|
|
172
|
-
.search-result .sr-type {
|
|
173
|
-
font-size: 11px; color: var(--text-dim);
|
|
174
|
-
margin-right: 8px;
|
|
175
|
-
}
|
|
176
|
-
.search-result .sr-file {
|
|
177
|
-
font-size: 12px; color: var(--accent);
|
|
178
|
-
}
|
|
179
|
-
.search-result .sr-preview {
|
|
180
|
-
font-size: 12px; color: var(--text-dim);
|
|
181
|
-
margin-top: 4px;
|
|
182
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/* Loading state */
|
|
186
|
-
#loading {
|
|
187
|
-
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
|
188
|
-
background: var(--bg);
|
|
189
|
-
display: flex; align-items: center; justify-content: center;
|
|
190
|
-
z-index: 200;
|
|
191
|
-
transition: opacity 0.3s;
|
|
192
|
-
}
|
|
193
|
-
#loading.hidden { opacity: 0; pointer-events: none; }
|
|
194
|
-
.spinner {
|
|
195
|
-
width: 32px; height: 32px;
|
|
196
|
-
border: 3px solid var(--border);
|
|
197
|
-
border-top-color: var(--accent);
|
|
198
|
-
border-radius: 50%;
|
|
199
|
-
animation: spin 0.8s linear infinite;
|
|
200
|
-
}
|
|
201
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
202
|
-
|
|
203
|
-
/* Tooltip */
|
|
204
|
-
.tooltip {
|
|
205
|
-
position: fixed;
|
|
206
|
-
background: var(--surface);
|
|
207
|
-
border: 1px solid var(--border);
|
|
208
|
-
border-radius: 6px;
|
|
209
|
-
padding: 6px 10px;
|
|
210
|
-
font-size: 12px;
|
|
211
|
-
color: var(--text);
|
|
212
|
-
pointer-events: none;
|
|
213
|
-
z-index: 150;
|
|
214
|
-
max-width: 280px;
|
|
215
|
-
white-space: nowrap;
|
|
216
|
-
overflow: hidden;
|
|
217
|
-
text-overflow: ellipsis;
|
|
218
|
-
display: none;
|
|
219
|
-
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
|
220
|
-
}
|
|
12
|
+
:root {
|
|
13
|
+
--bg: #09090b; --bg2: #0f1015; --surface: #15161b; --surface2: #1a1b22;
|
|
14
|
+
--border: #25262e; --border2: #32333d;
|
|
15
|
+
--text: #e4e4e7; --text2: #a1a1aa; --text3: #63637a;
|
|
16
|
+
--accent: #6d5bfa; --accent2: #8b7cf6; --accent-glow: rgba(109,91,250,0.15);
|
|
17
|
+
--green: #34d399; --yellow: #fbbf24; --red: #f87171; --cyan: #67e8f9;
|
|
18
|
+
--blue: #60a5fa; --purple: #a78bfa; --orange: #fb923c; --pink: #f472b6;
|
|
19
|
+
--radius: 8px; --radius-lg: 12px;
|
|
20
|
+
--font: 'Inter', -apple-system, sans-serif;
|
|
21
|
+
--mono: 'JetBrains Mono', ui-monospace, monospace;
|
|
22
|
+
--nav-w: 56px; --top-h: 48px;
|
|
23
|
+
--glass: rgba(21,22,27,0.75); --glass-border: rgba(255,255,255,0.04);
|
|
24
|
+
}
|
|
25
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
26
|
+
body{font-family:var(--font);background:var(--bg);color:var(--text);overflow:hidden;height:100vh;width:100vw}
|
|
27
|
+
::-webkit-scrollbar{width:4px;height:4px}
|
|
28
|
+
::-webkit-scrollbar-track{background:transparent}
|
|
29
|
+
::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
|
|
30
|
+
|
|
31
|
+
/* ─── Top Bar ─── */
|
|
32
|
+
#topbar{position:fixed;top:0;left:0;right:0;height:var(--top-h);background:var(--glass);backdrop-filter:blur(16px);border-bottom:1px solid var(--glass-border);display:flex;align-items:center;padding:0 16px;gap:16px;z-index:100}
|
|
33
|
+
.logo{display:flex;align-items:center;gap:8px;font-weight:700;font-size:14px;color:var(--accent2);letter-spacing:-0.3px;white-space:nowrap}
|
|
34
|
+
.logo svg{width:18px;height:18px}
|
|
35
|
+
.logo span{color:var(--text3);font-weight:400;font-size:12px;margin-left:2px}
|
|
36
|
+
.tabs{display:flex;gap:2px;margin-left:24px}
|
|
37
|
+
.tab{background:none;border:none;color:var(--text3);font:500 12px var(--font);padding:6px 14px;border-radius:6px;cursor:pointer;transition:all .15s;letter-spacing:.2px}
|
|
38
|
+
.tab:hover{color:var(--text2);background:var(--surface)}
|
|
39
|
+
.tab.active{color:var(--accent2);background:var(--accent-glow)}
|
|
40
|
+
.top-right{margin-left:auto;display:flex;align-items:center;gap:10px}
|
|
41
|
+
.live-dot{width:6px;height:6px;border-radius:50%;background:var(--green);box-shadow:0 0 6px var(--green);animation:pulse 2s infinite}
|
|
42
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
|
|
43
|
+
.top-stats{font:400 11px var(--mono);color:var(--text3)}
|
|
44
|
+
#cmd-trigger{background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:4px 10px;color:var(--text3);font:400 11px var(--font);cursor:pointer;display:flex;align-items:center;gap:6px;transition:border-color .15s}
|
|
45
|
+
#cmd-trigger:hover{border-color:var(--accent)}
|
|
46
|
+
#cmd-trigger kbd{background:var(--bg);padding:1px 5px;border-radius:3px;font:500 10px var(--mono);color:var(--text3)}
|
|
47
|
+
|
|
48
|
+
/* ─── Main Layout ─── */
|
|
49
|
+
#app{position:fixed;top:var(--top-h);left:0;right:0;bottom:0;display:flex}
|
|
50
|
+
#main{flex:1;overflow:hidden;position:relative}
|
|
51
|
+
.panel{position:absolute;inset:0;display:none;overflow:auto}
|
|
52
|
+
.panel.active{display:flex;flex-direction:column}
|
|
53
|
+
|
|
54
|
+
/* ─── Detail Drawer ─── */
|
|
55
|
+
#drawer{position:fixed;top:var(--top-h);right:0;bottom:0;width:340px;background:var(--glass);backdrop-filter:blur(20px);border-left:1px solid var(--glass-border);transform:translateX(100%);transition:transform .25s cubic-bezier(.4,0,.2,1);z-index:80;overflow-y:auto;display:flex;flex-direction:column}
|
|
56
|
+
#drawer.open{transform:translateX(0)}
|
|
57
|
+
.drawer-head{position:sticky;top:0;background:var(--surface);padding:14px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;z-index:1}
|
|
58
|
+
.drawer-head h3{font:600 13px var(--font);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:260px}
|
|
59
|
+
.drawer-close{background:none;border:none;color:var(--text3);cursor:pointer;font-size:16px;padding:2px 6px;border-radius:4px}
|
|
60
|
+
.drawer-close:hover{color:var(--text);background:var(--surface2)}
|
|
61
|
+
.drawer-body{padding:16px;flex:1}
|
|
62
|
+
.d-section{margin-bottom:16px}
|
|
63
|
+
.d-section h4{font:500 10px var(--font);text-transform:uppercase;letter-spacing:.6px;color:var(--text3);margin-bottom:6px}
|
|
64
|
+
.d-badge{display:inline-block;padding:2px 8px;border-radius:10px;font:500 10px var(--mono);margin-right:4px}
|
|
65
|
+
.d-meta{font:400 12px var(--font);color:var(--text2);line-height:1.8}
|
|
66
|
+
.d-meta strong{color:var(--text);font-weight:500}
|
|
67
|
+
.d-kv{display:grid;grid-template-columns:auto 1fr;gap:2px 12px;font:400 11px var(--mono);color:var(--text2)}
|
|
68
|
+
.d-kv .k{color:var(--text3)}
|
|
69
|
+
.d-kv .v{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
70
|
+
|
|
71
|
+
/* ─── Command Palette ─── */
|
|
72
|
+
#cmd-overlay{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:200;display:none;align-items:flex-start;justify-content:center;padding-top:15vh}
|
|
73
|
+
#cmd-overlay.open{display:flex}
|
|
74
|
+
#cmd-box{width:520px;background:var(--surface);border:1px solid var(--border2);border-radius:var(--radius-lg);box-shadow:0 20px 60px rgba(0,0,0,.6);overflow:hidden}
|
|
75
|
+
#cmd-input{width:100%;background:transparent;border:none;border-bottom:1px solid var(--border);padding:14px 16px;color:var(--text);font:400 14px var(--font);outline:none}
|
|
76
|
+
#cmd-input::placeholder{color:var(--text3)}
|
|
77
|
+
#cmd-results{max-height:320px;overflow-y:auto}
|
|
78
|
+
.cmd-item{padding:10px 16px;cursor:pointer;display:flex;align-items:center;gap:10px;transition:background .1s}
|
|
79
|
+
.cmd-item:hover,.cmd-item.selected{background:var(--accent-glow)}
|
|
80
|
+
.cmd-item .ci-type{font:500 9px var(--mono);color:var(--text3);text-transform:uppercase;width:48px;flex-shrink:0}
|
|
81
|
+
.cmd-item .ci-label{font:400 13px var(--font);color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
82
|
+
|
|
83
|
+
/* ─── Graph Panel ─── */
|
|
84
|
+
#graph-panel svg{width:100%;height:100%}
|
|
85
|
+
.graph-controls{position:absolute;bottom:16px;right:16px;display:flex;flex-direction:column;gap:4px}
|
|
86
|
+
.graph-controls button{width:32px;height:32px;background:var(--surface);border:1px solid var(--border);border-radius:6px;color:var(--text2);font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center}
|
|
87
|
+
.graph-controls button:hover{background:var(--surface2);color:var(--text)}
|
|
88
|
+
.graph-legend{position:absolute;bottom:16px;left:16px;display:flex;gap:12px;font:400 11px var(--font);color:var(--text3)}
|
|
89
|
+
.legend-item{display:flex;align-items:center;gap:5px}
|
|
90
|
+
.legend-dot{width:7px;height:7px;border-radius:50%}
|
|
91
|
+
|
|
92
|
+
/* ─── Timeline Panel ─── */
|
|
93
|
+
#timeline-panel{padding:0}
|
|
94
|
+
.tl-header{padding:14px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-shrink:0}
|
|
95
|
+
.tl-header h2{font:600 14px var(--font);color:var(--text)}
|
|
96
|
+
.tl-header .tl-stats{font:400 11px var(--mono);color:var(--text3)}
|
|
97
|
+
.tl-body{flex:1;overflow:auto;position:relative;padding:20px}
|
|
98
|
+
.tl-body svg{min-width:100%}
|
|
99
|
+
.tl-empty{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:13px}
|
|
100
|
+
|
|
101
|
+
/* ─── Store Panel ─── */
|
|
102
|
+
#store-panel{padding:0}
|
|
103
|
+
.store-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--border);flex:1;overflow:hidden}
|
|
104
|
+
.store-col{background:var(--bg);overflow-y:auto;display:flex;flex-direction:column}
|
|
105
|
+
.store-section{padding:14px 16px}
|
|
106
|
+
.store-section h3{font:600 12px var(--font);color:var(--text2);margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
|
107
|
+
.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px;padding:0 16px}
|
|
108
|
+
.stat-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px;text-align:center}
|
|
109
|
+
.stat-card .sv{font:700 20px var(--mono);color:var(--accent2)}
|
|
110
|
+
.stat-card .sl{font:400 10px var(--font);color:var(--text3);text-transform:uppercase;letter-spacing:.4px;margin-top:2px}
|
|
111
|
+
.entity-row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:4px;cursor:pointer;transition:background .1s;font-size:12px}
|
|
112
|
+
.entity-row:hover{background:var(--surface)}
|
|
113
|
+
.entity-row .et{font:500 10px var(--mono);color:var(--purple);width:70px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
114
|
+
.entity-row .ei{color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
|
|
115
|
+
.entity-row .ec{font:400 10px var(--mono);color:var(--text3);flex-shrink:0}
|
|
116
|
+
.type-pill{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;background:var(--surface);border:1px solid var(--border);border-radius:99px;font:500 10px var(--mono);color:var(--text2);cursor:pointer;transition:all .1s}
|
|
117
|
+
.type-pill:hover,.type-pill.active{border-color:var(--accent);color:var(--accent2)}
|
|
118
|
+
.type-pill .tc{color:var(--text3)}
|
|
119
|
+
.catalog-table{width:100%;border-collapse:collapse;font:400 11px var(--mono)}
|
|
120
|
+
.catalog-table th{text-align:left;color:var(--text3);font-weight:500;padding:4px 8px;border-bottom:1px solid var(--border)}
|
|
121
|
+
.catalog-table td{padding:4px 8px;color:var(--text2);border-bottom:1px solid rgba(37,38,46,.5)}
|
|
122
|
+
|
|
123
|
+
/* ─── System Panel ─── */
|
|
124
|
+
#system-panel{padding:24px;gap:20px}
|
|
125
|
+
.sys-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}
|
|
126
|
+
.sys-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:20px;display:flex;flex-direction:column;gap:12px}
|
|
127
|
+
.sys-card h3{font:600 12px var(--font);color:var(--text);display:flex;align-items:center;gap:8px}
|
|
128
|
+
.sys-card h3 .icon{font-size:14px}
|
|
129
|
+
.sys-row{display:flex;justify-content:space-between;align-items:center;font:400 12px var(--font)}
|
|
130
|
+
.sys-row .label{color:var(--text3)}
|
|
131
|
+
.sys-row .value{color:var(--text2);font-family:var(--mono);font-size:11px}
|
|
132
|
+
.sys-badge{display:inline-block;padding:2px 8px;border-radius:99px;font:600 10px var(--mono)}
|
|
133
|
+
.sys-badge.on{background:rgba(52,211,153,.12);color:var(--green)}
|
|
134
|
+
.sys-badge.off{background:rgba(248,113,113,.1);color:var(--red)}
|
|
135
|
+
.parser-tags{display:flex;flex-wrap:wrap;gap:4px}
|
|
136
|
+
.parser-tag{padding:3px 8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;font:400 10px var(--mono);color:var(--text2)}
|
|
137
|
+
|
|
138
|
+
/* ─── Loading ─── */
|
|
139
|
+
#loading{position:fixed;inset:0;background:var(--bg);display:flex;align-items:center;justify-content:center;z-index:300;transition:opacity .3s}
|
|
140
|
+
#loading.hidden{opacity:0;pointer-events:none}
|
|
141
|
+
.spinner{width:28px;height:28px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .7s linear infinite}
|
|
142
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
143
|
+
|
|
144
|
+
/* ─── Tooltip ─── */
|
|
145
|
+
.tooltip{position:fixed;background:var(--surface2);border:1px solid var(--border2);border-radius:6px;padding:5px 10px;font:400 11px var(--font);color:var(--text);pointer-events:none;z-index:150;max-width:300px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:none;box-shadow:0 4px 16px rgba(0,0,0,.5)}
|
|
221
146
|
</style>
|
|
222
147
|
</head>
|
|
223
148
|
<body>
|
|
224
|
-
|
|
225
149
|
<div id="loading"><div class="spinner"></div></div>
|
|
226
150
|
|
|
227
151
|
<div id="topbar">
|
|
228
|
-
<div class="logo">
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
<
|
|
234
|
-
<
|
|
235
|
-
<
|
|
152
|
+
<div class="logo">
|
|
153
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5"/></svg>
|
|
154
|
+
Trellis <span>System Visualizer</span>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="tabs" id="view-tabs">
|
|
157
|
+
<button class="tab active" data-panel="graph">Graph</button>
|
|
158
|
+
<button class="tab" data-panel="timeline">Timeline</button>
|
|
159
|
+
<button class="tab" data-panel="store">Store</button>
|
|
160
|
+
<button class="tab" data-panel="system">System</button>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="top-right">
|
|
163
|
+
<div class="live-dot"></div>
|
|
164
|
+
<div class="top-stats" id="top-stats"></div>
|
|
165
|
+
<button id="cmd-trigger"><span>Search</span><kbd>⌘K</kbd></button>
|
|
236
166
|
</div>
|
|
237
167
|
</div>
|
|
238
168
|
|
|
239
|
-
<div id="
|
|
240
|
-
<div id="
|
|
241
|
-
|
|
242
|
-
<
|
|
243
|
-
<
|
|
169
|
+
<div id="app">
|
|
170
|
+
<div id="main">
|
|
171
|
+
<div id="graph-panel" class="panel active"></div>
|
|
172
|
+
<div id="timeline-panel" class="panel"></div>
|
|
173
|
+
<div id="store-panel" class="panel"></div>
|
|
174
|
+
<div id="system-panel" class="panel"></div>
|
|
244
175
|
</div>
|
|
245
|
-
<div id="sidebar-body"></div>
|
|
246
176
|
</div>
|
|
247
|
-
<div id="search-results"></div>
|
|
248
|
-
<div class="tooltip" id="tooltip"></div>
|
|
249
|
-
|
|
250
|
-
<script>
|
|
251
|
-
(async function() {
|
|
252
|
-
// -------------------------------------------------------------------------
|
|
253
|
-
// Config
|
|
254
|
-
// -------------------------------------------------------------------------
|
|
255
|
-
const TYPE_COLORS = {
|
|
256
|
-
file: '#58a6ff',
|
|
257
|
-
milestone: '#3fb950',
|
|
258
|
-
issue: '#d29922',
|
|
259
|
-
branch: '#bc8cff',
|
|
260
|
-
};
|
|
261
|
-
const TYPE_RADIUS = {
|
|
262
|
-
file: 4,
|
|
263
|
-
milestone: 8,
|
|
264
|
-
issue: 7,
|
|
265
|
-
branch: 6,
|
|
266
|
-
};
|
|
267
|
-
const EDGE_COLORS = {
|
|
268
|
-
milestone_file: '#3fb95044',
|
|
269
|
-
issue_branch: '#d2992244',
|
|
270
|
-
wikilink: '#58a6ff44',
|
|
271
|
-
causal: '#30363d',
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
// -------------------------------------------------------------------------
|
|
275
|
-
// Fetch graph
|
|
276
|
-
// -------------------------------------------------------------------------
|
|
277
|
-
let graphData;
|
|
278
|
-
try {
|
|
279
|
-
const res = await fetch('/api/graph');
|
|
280
|
-
graphData = await res.json();
|
|
281
|
-
} catch (e) {
|
|
282
|
-
document.getElementById('loading').innerHTML =
|
|
283
|
-
'<div style="color:var(--red);font-size:14px">Failed to load graph data</div>';
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
177
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
// -------------------------------------------------------------------------
|
|
292
|
-
// D3 Force Simulation
|
|
293
|
-
// -------------------------------------------------------------------------
|
|
294
|
-
const container = document.getElementById('graph');
|
|
295
|
-
const width = container.clientWidth;
|
|
296
|
-
const height = container.clientHeight;
|
|
297
|
-
|
|
298
|
-
const svg = d3.select('#graph')
|
|
299
|
-
.append('svg')
|
|
300
|
-
.attr('width', width)
|
|
301
|
-
.attr('height', height);
|
|
302
|
-
|
|
303
|
-
// Zoom group
|
|
304
|
-
const g = svg.append('g');
|
|
305
|
-
const zoom = d3.zoom()
|
|
306
|
-
.scaleExtent([0.1, 8])
|
|
307
|
-
.on('zoom', (e) => g.attr('transform', e.transform));
|
|
308
|
-
svg.call(zoom);
|
|
309
|
-
|
|
310
|
-
// Build link/node data — D3 mutates these
|
|
311
|
-
const simLinks = edges.map(e => ({
|
|
312
|
-
source: e.source,
|
|
313
|
-
target: e.target,
|
|
314
|
-
type: e.type,
|
|
315
|
-
label: e.label,
|
|
316
|
-
}));
|
|
317
|
-
const simNodes = nodes.map(n => ({ ...n }));
|
|
318
|
-
|
|
319
|
-
// Create a lookup for quick access
|
|
320
|
-
const nodeMap = new Map();
|
|
321
|
-
simNodes.forEach(n => nodeMap.set(n.id, n));
|
|
322
|
-
|
|
323
|
-
const simulation = d3.forceSimulation(simNodes)
|
|
324
|
-
.force('link', d3.forceLink(simLinks).id(d => d.id).distance(60).strength(0.3))
|
|
325
|
-
.force('charge', d3.forceManyBody().strength(-80).distanceMax(300))
|
|
326
|
-
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
327
|
-
.force('collision', d3.forceCollide().radius(d => TYPE_RADIUS[d.type] + 2))
|
|
328
|
-
.force('x', d3.forceX(width / 2).strength(0.03))
|
|
329
|
-
.force('y', d3.forceY(height / 2).strength(0.03));
|
|
330
|
-
|
|
331
|
-
// Edges
|
|
332
|
-
const link = g.append('g')
|
|
333
|
-
.selectAll('line')
|
|
334
|
-
.data(simLinks)
|
|
335
|
-
.join('line')
|
|
336
|
-
.attr('stroke', d => EDGE_COLORS[d.type] || '#30363d')
|
|
337
|
-
.attr('stroke-width', d => d.type === 'wikilink' ? 1.5 : 0.8);
|
|
338
|
-
|
|
339
|
-
// Nodes
|
|
340
|
-
const node = g.append('g')
|
|
341
|
-
.selectAll('circle')
|
|
342
|
-
.data(simNodes)
|
|
343
|
-
.join('circle')
|
|
344
|
-
.attr('r', d => TYPE_RADIUS[d.type] || 4)
|
|
345
|
-
.attr('fill', d => TYPE_COLORS[d.type] || '#8b949e')
|
|
346
|
-
.attr('stroke', 'none')
|
|
347
|
-
.attr('cursor', 'pointer')
|
|
348
|
-
.call(drag(simulation));
|
|
349
|
-
|
|
350
|
-
// Labels (only for milestones, issues, branches — not files to avoid clutter)
|
|
351
|
-
const labels = g.append('g')
|
|
352
|
-
.selectAll('text')
|
|
353
|
-
.data(simNodes.filter(d => d.type !== 'file'))
|
|
354
|
-
.join('text')
|
|
355
|
-
.text(d => {
|
|
356
|
-
const maxLen = 24;
|
|
357
|
-
return d.label.length > maxLen ? d.label.slice(0, maxLen) + '…' : d.label;
|
|
358
|
-
})
|
|
359
|
-
.attr('font-size', 10)
|
|
360
|
-
.attr('fill', d => TYPE_COLORS[d.type] || '#8b949e')
|
|
361
|
-
.attr('dx', d => TYPE_RADIUS[d.type] + 4)
|
|
362
|
-
.attr('dy', 3)
|
|
363
|
-
.attr('pointer-events', 'none')
|
|
364
|
-
.attr('opacity', 0.8);
|
|
365
|
-
|
|
366
|
-
simulation.on('tick', () => {
|
|
367
|
-
link
|
|
368
|
-
.attr('x1', d => d.source.x)
|
|
369
|
-
.attr('y1', d => d.source.y)
|
|
370
|
-
.attr('x2', d => d.target.x)
|
|
371
|
-
.attr('y2', d => d.target.y);
|
|
372
|
-
node
|
|
373
|
-
.attr('cx', d => d.x)
|
|
374
|
-
.attr('cy', d => d.y);
|
|
375
|
-
labels
|
|
376
|
-
.attr('x', d => d.x)
|
|
377
|
-
.attr('y', d => d.y);
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// Drag behavior
|
|
381
|
-
function drag(simulation) {
|
|
382
|
-
return d3.drag()
|
|
383
|
-
.on('start', (event, d) => {
|
|
384
|
-
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
385
|
-
d.fx = d.x; d.fy = d.y;
|
|
386
|
-
})
|
|
387
|
-
.on('drag', (event, d) => {
|
|
388
|
-
d.fx = event.x; d.fy = event.y;
|
|
389
|
-
})
|
|
390
|
-
.on('end', (event, d) => {
|
|
391
|
-
if (!event.active) simulation.alphaTarget(0);
|
|
392
|
-
d.fx = null; d.fy = null;
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Hide loading
|
|
397
|
-
document.getElementById('loading').classList.add('hidden');
|
|
398
|
-
|
|
399
|
-
// -------------------------------------------------------------------------
|
|
400
|
-
// Tooltip on hover
|
|
401
|
-
// -------------------------------------------------------------------------
|
|
402
|
-
const tooltip = document.getElementById('tooltip');
|
|
403
|
-
node.on('mouseover', (event, d) => {
|
|
404
|
-
tooltip.textContent = d.label;
|
|
405
|
-
tooltip.style.display = 'block';
|
|
406
|
-
tooltip.style.left = (event.clientX + 12) + 'px';
|
|
407
|
-
tooltip.style.top = (event.clientY - 8) + 'px';
|
|
408
|
-
}).on('mousemove', (event) => {
|
|
409
|
-
tooltip.style.left = (event.clientX + 12) + 'px';
|
|
410
|
-
tooltip.style.top = (event.clientY - 8) + 'px';
|
|
411
|
-
}).on('mouseout', () => {
|
|
412
|
-
tooltip.style.display = 'none';
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// -------------------------------------------------------------------------
|
|
416
|
-
// Click → sidebar detail
|
|
417
|
-
// -------------------------------------------------------------------------
|
|
418
|
-
const sidebar = document.getElementById('sidebar');
|
|
419
|
-
const sidebarTitle = document.getElementById('sidebar-title');
|
|
420
|
-
const sidebarBody = document.getElementById('sidebar-body');
|
|
178
|
+
<div id="drawer">
|
|
179
|
+
<div class="drawer-head"><h3 id="drawer-title"></h3><button class="drawer-close" id="drawer-close">×</button></div>
|
|
180
|
+
<div class="drawer-body" id="drawer-body"></div>
|
|
181
|
+
</div>
|
|
421
182
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
183
|
+
<div id="cmd-overlay">
|
|
184
|
+
<div id="cmd-box">
|
|
185
|
+
<input id="cmd-input" type="text" placeholder="Search entities, ops, files…" autocomplete="off">
|
|
186
|
+
<div id="cmd-results"></div>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
426
189
|
|
|
427
|
-
|
|
428
|
-
event.stopPropagation();
|
|
429
|
-
highlightNode(d);
|
|
430
|
-
|
|
431
|
-
sidebarTitle.textContent = d.label;
|
|
432
|
-
sidebarBody.innerHTML = '<div style="color:var(--text-dim);font-size:12px">Loading…</div>';
|
|
433
|
-
sidebar.classList.add('open');
|
|
434
|
-
|
|
435
|
-
try {
|
|
436
|
-
const res = await fetch(`/api/node/${encodeURIComponent(d.id)}`);
|
|
437
|
-
const detail = await res.json();
|
|
438
|
-
renderDetail(detail, d);
|
|
439
|
-
} catch {
|
|
440
|
-
sidebarBody.innerHTML = '<div style="color:var(--red)">Failed to load</div>';
|
|
441
|
-
}
|
|
442
|
-
});
|
|
190
|
+
<div class="tooltip" id="tooltip"></div>
|
|
443
191
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
192
|
+
<script>
|
|
193
|
+
(async function(){
|
|
194
|
+
// ─── State ───
|
|
195
|
+
const S = { panel: 'graph', graph: null, timeline: null, store: null, system: null, cmdOpen: false, drawerOpen: false };
|
|
196
|
+
const $ = id => document.getElementById(id);
|
|
197
|
+
const TYPE_COLORS = { file:'#60a5fa', milestone:'#34d399', issue:'#fbbf24', branch:'#a78bfa', default:'#63637a' };
|
|
198
|
+
const TYPE_RADIUS = { file:4, milestone:8, issue:7, branch:6, default:4 };
|
|
199
|
+
|
|
200
|
+
// ─── Panel Switching ───
|
|
201
|
+
const tabs = document.querySelectorAll('.tab');
|
|
202
|
+
const panels = document.querySelectorAll('.panel');
|
|
203
|
+
function switchPanel(name){
|
|
204
|
+
S.panel = name;
|
|
205
|
+
tabs.forEach(t => t.classList.toggle('active', t.dataset.panel === name));
|
|
206
|
+
panels.forEach(p => p.classList.toggle('active', p.id === name+'-panel'));
|
|
207
|
+
if(name==='timeline' && !S.timeline) loadTimeline();
|
|
208
|
+
if(name==='store' && !S.store) loadStore();
|
|
209
|
+
if(name==='system' && !S.system) loadSystem();
|
|
210
|
+
}
|
|
211
|
+
tabs.forEach(t => t.addEventListener('click', () => switchPanel(t.dataset.panel)));
|
|
212
|
+
|
|
213
|
+
// ─── Drawer ───
|
|
214
|
+
const drawer = $('drawer'), drawerTitle = $('drawer-title'), drawerBody = $('drawer-body');
|
|
215
|
+
$('drawer-close').onclick = () => { drawer.classList.remove('open'); S.drawerOpen = false; };
|
|
216
|
+
function openDrawer(title, html){ drawerTitle.textContent = title; drawerBody.innerHTML = html; drawer.classList.add('open'); S.drawerOpen = true; }
|
|
217
|
+
|
|
218
|
+
// ─── Command Palette ───
|
|
219
|
+
const cmdOverlay = $('cmd-overlay'), cmdInput = $('cmd-input'), cmdResults = $('cmd-results');
|
|
220
|
+
let cmdItems = [];
|
|
221
|
+
function openCmd(){ cmdOverlay.classList.add('open'); cmdInput.value=''; cmdInput.focus(); renderCmdResults(''); S.cmdOpen=true; }
|
|
222
|
+
function closeCmd(){ cmdOverlay.classList.remove('open'); S.cmdOpen=false; }
|
|
223
|
+
$('cmd-trigger').onclick = openCmd;
|
|
224
|
+
cmdOverlay.addEventListener('click', e => { if(e.target===cmdOverlay) closeCmd(); });
|
|
225
|
+
cmdInput.addEventListener('input', () => renderCmdResults(cmdInput.value));
|
|
226
|
+
cmdInput.addEventListener('keydown', e => { if(e.key==='Escape') closeCmd(); });
|
|
227
|
+
|
|
228
|
+
function buildCmdItems(){
|
|
229
|
+
cmdItems = [];
|
|
230
|
+
if(S.graph){ S.graph.nodes.forEach(n => cmdItems.push({type:n.type, label:n.label, id:n.id})); }
|
|
231
|
+
['graph','timeline','store','system'].forEach(p => cmdItems.push({type:'panel', label:'Go to '+p.charAt(0).toUpperCase()+p.slice(1), id:'panel:'+p}));
|
|
232
|
+
}
|
|
233
|
+
function renderCmdResults(q){
|
|
234
|
+
const query = q.toLowerCase();
|
|
235
|
+
let items = cmdItems;
|
|
236
|
+
if(query) items = items.filter(i => i.label.toLowerCase().includes(query) || i.type.includes(query));
|
|
237
|
+
items = items.slice(0,20);
|
|
238
|
+
cmdResults.innerHTML = items.map(i =>
|
|
239
|
+
`<div class="cmd-item" data-id="${i.id}" data-type="${i.type}"><span class="ci-type">${i.type}</span><span class="ci-label">${esc(i.label)}</span></div>`
|
|
240
|
+
).join('');
|
|
241
|
+
cmdResults.querySelectorAll('.cmd-item').forEach(el => el.addEventListener('click', () => {
|
|
242
|
+
const id = el.dataset.id;
|
|
243
|
+
if(id.startsWith('panel:')){ switchPanel(id.slice(6)); closeCmd(); return; }
|
|
244
|
+
closeCmd();
|
|
245
|
+
if(S.panel==='graph') focusGraphNode(id);
|
|
246
|
+
else loadNodeDetail(id);
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
function esc(s){ return s.replace(/</g,'<').replace(/>/g,'>'); }
|
|
250
|
+
|
|
251
|
+
// ─── Keyboard ───
|
|
252
|
+
document.addEventListener('keydown', e => {
|
|
253
|
+
if((e.metaKey||e.ctrlKey) && e.key==='k'){ e.preventDefault(); openCmd(); }
|
|
254
|
+
if(e.key==='Escape'){ if(S.cmdOpen) closeCmd(); else { drawer.classList.remove('open'); S.drawerOpen=false; } }
|
|
447
255
|
});
|
|
448
256
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
</
|
|
257
|
+
// ─── Node Detail ───
|
|
258
|
+
async function loadNodeDetail(nodeId){
|
|
259
|
+
openDrawer(nodeId, '<div style="color:var(--text3)">Loading…</div>');
|
|
260
|
+
try{
|
|
261
|
+
const res = await fetch('/api/node/'+encodeURIComponent(nodeId));
|
|
262
|
+
const d = await res.json();
|
|
263
|
+
let h = `<div class="d-section"><span class="d-badge" style="background:${TYPE_COLORS[d.type]||TYPE_COLORS.default}22;color:${TYPE_COLORS[d.type]||TYPE_COLORS.default}">${d.type}</span></div>`;
|
|
264
|
+
if(d.type==='file'){
|
|
265
|
+
h += `<div class="d-section"><h4>Path</h4><div class="d-meta"><strong>${esc(d.path)}</strong></div></div>`;
|
|
266
|
+
if(d.contentHash) h += `<div class="d-section"><h4>Hash</h4><div style="font:11px var(--mono);color:var(--text3);word-break:break-all">${d.contentHash}</div></div>`;
|
|
267
|
+
if(d.recentOps?.length) { h += `<div class="d-section"><h4>Recent Ops</h4>`; d.recentOps.forEach(o => h += `<div style="font:11px var(--mono);color:var(--cyan)">${o.kind} <span style="color:var(--text3)">${ago(o.timestamp)}</span></div>`); h += '</div>'; }
|
|
268
|
+
} else if(d.type==='milestone'){
|
|
269
|
+
h += `<div class="d-section"><h4>Message</h4><div class="d-meta"><strong>${esc(d.message||'(none)')}</strong></div></div>`;
|
|
270
|
+
if(d.affectedFiles?.length) h += `<div class="d-section"><h4>Files (${d.affectedFiles.length})</h4><div class="d-meta">${d.affectedFiles.map(f=>'<div>'+esc(f)+'</div>').join('')}</div></div>`;
|
|
271
|
+
} else if(d.type==='issue'){
|
|
272
|
+
h += `<div class="d-section"><h4>Details</h4><div class="d-kv">`;
|
|
273
|
+
if(d.status) h+=`<span class="k">status</span><span class="v">${d.status}</span>`;
|
|
274
|
+
if(d.priority) h+=`<span class="k">priority</span><span class="v">${d.priority}</span>`;
|
|
275
|
+
if(d.assignee) h+=`<span class="k">assignee</span><span class="v">${d.assignee}</span>`;
|
|
276
|
+
h += '</div></div>';
|
|
277
|
+
} else if(d.type==='branch'){
|
|
278
|
+
h += `<div class="d-section"><h4>Branch</h4><div class="d-kv"><span class="k">name</span><span class="v">${esc(d.name||'')}</span></div></div>`;
|
|
468
279
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
</div>`;
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
connected.add(d.id);
|
|
542
|
-
simLinks.forEach(l => {
|
|
543
|
-
const sid = typeof l.source === 'object' ? l.source.id : l.source;
|
|
544
|
-
const tid = typeof l.target === 'object' ? l.target.id : l.target;
|
|
545
|
-
if (sid === d.id) connected.add(tid);
|
|
546
|
-
if (tid === d.id) connected.add(sid);
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
node.attr('opacity', n => connected.has(n.id) ? 1 : 0.15);
|
|
550
|
-
link.attr('opacity', l => {
|
|
551
|
-
const sid = typeof l.source === 'object' ? l.source.id : l.source;
|
|
552
|
-
const tid = typeof l.target === 'object' ? l.target.id : l.target;
|
|
553
|
-
return (sid === d.id || tid === d.id) ? 1 : 0.05;
|
|
280
|
+
drawerBody.innerHTML = h;
|
|
281
|
+
} catch{ drawerBody.innerHTML = '<div style="color:var(--red)">Failed to load</div>'; }
|
|
282
|
+
}
|
|
283
|
+
function ago(iso){ if(!iso) return ''; const d=Date.now()-new Date(iso).getTime(); if(d<60000) return 'now'; if(d<3600000) return Math.floor(d/60000)+'m'; if(d<86400000) return Math.floor(d/3600000)+'h'; return Math.floor(d/86400000)+'d'; }
|
|
284
|
+
|
|
285
|
+
// ─── Entity Detail (store) ───
|
|
286
|
+
async function loadEntityDetail(entityId){
|
|
287
|
+
openDrawer(entityId, '<div style="color:var(--text3)">Loading…</div>');
|
|
288
|
+
try{
|
|
289
|
+
const res = await fetch('/api/store/entity/'+encodeURIComponent(entityId));
|
|
290
|
+
const d = await res.json();
|
|
291
|
+
let h = `<div class="d-section"><span class="d-badge" style="background:var(--accent-glow);color:var(--accent2)">${esc(String(d.type))}</span></div>`;
|
|
292
|
+
h += '<div class="d-section"><h4>Facts</h4><div class="d-kv">';
|
|
293
|
+
d.facts.forEach(f => h += `<span class="k">${esc(f.a)}</span><span class="v">${esc(String(f.v))}</span>`);
|
|
294
|
+
h += '</div></div>';
|
|
295
|
+
if(d.links?.length){ h += '<div class="d-section"><h4>Links ('+d.links.length+')</h4>'; d.links.forEach(l => { h += `<div style="font:11px var(--mono);color:var(--text2)">${l.direction==='outgoing'?'→':'←'} <span style="color:var(--accent2)">${esc(l.a)}</span> ${esc(l.direction==='outgoing'?l.target:l.source)}</div>`; }); h+='</div>'; }
|
|
296
|
+
drawerBody.innerHTML = h;
|
|
297
|
+
} catch{ drawerBody.innerHTML = '<div style="color:var(--red)">Failed to load</div>'; }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
301
|
+
// GRAPH PANEL
|
|
302
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
303
|
+
let graphSim, graphZoom, graphSvg, graphG, graphNodes, graphLinks, graphLabels, graphNodeMap;
|
|
304
|
+
|
|
305
|
+
async function initGraph(){
|
|
306
|
+
try{ const r = await fetch('/api/graph'); S.graph = await r.json(); }
|
|
307
|
+
catch{ $('graph-panel').innerHTML='<div style="padding:40px;color:var(--red)">Failed to load graph</div>'; return; }
|
|
308
|
+
const { nodes, edges } = S.graph;
|
|
309
|
+
$('top-stats').textContent = nodes.length + ' nodes · ' + edges.length + ' edges';
|
|
310
|
+
buildCmdItems();
|
|
311
|
+
|
|
312
|
+
const container = $('graph-panel');
|
|
313
|
+
const w = container.clientWidth, h = container.clientHeight;
|
|
314
|
+
|
|
315
|
+
const svg = d3.select('#graph-panel').append('svg').attr('width', w).attr('height', h);
|
|
316
|
+
graphSvg = svg;
|
|
317
|
+
const g = svg.append('g');
|
|
318
|
+
graphG = g;
|
|
319
|
+
graphZoom = d3.zoom().scaleExtent([0.1,8]).on('zoom', e => g.attr('transform', e.transform));
|
|
320
|
+
svg.call(graphZoom);
|
|
321
|
+
|
|
322
|
+
const simLinks = edges.map(e => ({...e}));
|
|
323
|
+
const simNodes = nodes.map(n => ({...n}));
|
|
324
|
+
graphNodeMap = new Map(); simNodes.forEach(n => graphNodeMap.set(n.id, n));
|
|
325
|
+
|
|
326
|
+
graphSim = d3.forceSimulation(simNodes)
|
|
327
|
+
.force('link', d3.forceLink(simLinks).id(d=>d.id).distance(55).strength(0.3))
|
|
328
|
+
.force('charge', d3.forceManyBody().strength(-70).distanceMax(280))
|
|
329
|
+
.force('center', d3.forceCenter(w/2, h/2))
|
|
330
|
+
.force('collision', d3.forceCollide().radius(d=>(TYPE_RADIUS[d.type]||4)+2))
|
|
331
|
+
.force('x', d3.forceX(w/2).strength(0.03))
|
|
332
|
+
.force('y', d3.forceY(h/2).strength(0.03));
|
|
333
|
+
|
|
334
|
+
graphLinks = g.append('g').selectAll('line').data(simLinks).join('line')
|
|
335
|
+
.attr('stroke', d => d.type==='wikilink'?'#60a5fa33':d.type==='milestone_file'?'#34d39933':d.type==='issue_branch'?'#fbbf2433':'#25262e')
|
|
336
|
+
.attr('stroke-width', d => d.type==='wikilink'?1.2:0.7);
|
|
337
|
+
|
|
338
|
+
graphNodes = g.append('g').selectAll('circle').data(simNodes).join('circle')
|
|
339
|
+
.attr('r', d => TYPE_RADIUS[d.type]||4)
|
|
340
|
+
.attr('fill', d => TYPE_COLORS[d.type]||TYPE_COLORS.default)
|
|
341
|
+
.attr('cursor','pointer')
|
|
342
|
+
.call(d3.drag().on('start',(e,d)=>{if(!e.active)graphSim.alphaTarget(.3).restart();d.fx=d.x;d.fy=d.y}).on('drag',(e,d)=>{d.fx=e.x;d.fy=e.y}).on('end',(e,d)=>{if(!e.active)graphSim.alphaTarget(0);d.fx=null;d.fy=null}));
|
|
343
|
+
|
|
344
|
+
graphLabels = g.append('g').selectAll('text').data(simNodes.filter(d=>d.type!=='file')).join('text')
|
|
345
|
+
.text(d => d.label.length>22?d.label.slice(0,22)+'…':d.label)
|
|
346
|
+
.attr('font-size',10).attr('fill',d=>TYPE_COLORS[d.type]||'#63637a').attr('dx',d=>(TYPE_RADIUS[d.type]||4)+4).attr('dy',3).attr('pointer-events','none').attr('opacity',.8).attr('font-family','Inter,sans-serif');
|
|
347
|
+
|
|
348
|
+
graphSim.on('tick', () => {
|
|
349
|
+
graphLinks.attr('x1',d=>d.source.x).attr('y1',d=>d.source.y).attr('x2',d=>d.target.x).attr('y2',d=>d.target.y);
|
|
350
|
+
graphNodes.attr('cx',d=>d.x).attr('cy',d=>d.y);
|
|
351
|
+
graphLabels.attr('x',d=>d.x).attr('y',d=>d.y);
|
|
554
352
|
});
|
|
555
|
-
labels.attr('opacity', n => connected.has(n.id) ? 1 : 0.1);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function clearHighlight() {
|
|
559
|
-
highlightedId = null;
|
|
560
|
-
node.attr('opacity', 1);
|
|
561
|
-
link.attr('opacity', 1);
|
|
562
|
-
labels.attr('opacity', 0.8);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// -------------------------------------------------------------------------
|
|
566
|
-
// Search
|
|
567
|
-
// -------------------------------------------------------------------------
|
|
568
|
-
const searchBox = document.getElementById('search-box');
|
|
569
|
-
const searchResults = document.getElementById('search-results');
|
|
570
|
-
let searchTimeout = null;
|
|
571
|
-
|
|
572
|
-
searchBox.addEventListener('keydown', (e) => {
|
|
573
|
-
if (e.key === 'Enter') {
|
|
574
|
-
doSearch(searchBox.value.trim());
|
|
575
|
-
}
|
|
576
|
-
if (e.key === 'Escape') {
|
|
577
|
-
searchResults.classList.remove('visible');
|
|
578
|
-
searchBox.blur();
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
async function doSearch(query) {
|
|
583
|
-
if (!query) {
|
|
584
|
-
searchResults.classList.remove('visible');
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
searchResults.innerHTML = '<div style="padding:16px;color:var(--text-dim);font-size:12px">Searching…</div>';
|
|
589
|
-
searchResults.classList.add('visible');
|
|
590
|
-
|
|
591
|
-
try {
|
|
592
|
-
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}&limit=15`);
|
|
593
|
-
const data = await res.json();
|
|
594
|
-
|
|
595
|
-
if (data.message) {
|
|
596
|
-
searchResults.innerHTML = `<div style="padding:16px;color:var(--text-dim);font-size:12px">${data.message}</div>`;
|
|
597
|
-
return;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
if (!data.results || data.results.length === 0) {
|
|
601
|
-
searchResults.innerHTML = '<div style="padding:16px;color:var(--text-dim);font-size:12px">No results</div>';
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// Highlight matching nodes on the graph
|
|
606
|
-
const matchedFiles = new Set(data.results.filter(r => r.filePath).map(r => `file:${r.filePath}`));
|
|
607
|
-
node.attr('opacity', n => matchedFiles.size === 0 || matchedFiles.has(n.id) ? 1 : 0.15);
|
|
608
|
-
link.attr('opacity', 0.1);
|
|
609
|
-
labels.attr('opacity', n => matchedFiles.has(n.id) ? 1 : 0.1);
|
|
610
|
-
|
|
611
|
-
searchResults.innerHTML = data.results.map(r => {
|
|
612
|
-
const score = (r.score * 100).toFixed(1);
|
|
613
|
-
const preview = (r.content || '').slice(0, 120).replace(/</g, '<');
|
|
614
|
-
return `<div class="search-result" data-file="${r.filePath || ''}" data-entity="${r.entityId || ''}">
|
|
615
|
-
<span class="sr-score">${score}%</span>
|
|
616
|
-
<span class="sr-type">[${r.chunkType}]</span>
|
|
617
|
-
${r.filePath ? `<span class="sr-file">${r.filePath}</span>` : ''}
|
|
618
|
-
<div class="sr-preview">${preview}</div>
|
|
619
|
-
</div>`;
|
|
620
|
-
}).join('');
|
|
621
|
-
|
|
622
|
-
// Click search result → focus that node
|
|
623
|
-
searchResults.querySelectorAll('.search-result').forEach(el => {
|
|
624
|
-
el.addEventListener('click', () => {
|
|
625
|
-
const fileId = `file:${el.dataset.file}`;
|
|
626
|
-
const n = nodeMap.get(fileId);
|
|
627
|
-
if (n) {
|
|
628
|
-
highlightNode(n);
|
|
629
|
-
// Pan to node
|
|
630
|
-
const transform = d3.zoomTransform(svg.node());
|
|
631
|
-
const x = transform.applyX(n.x);
|
|
632
|
-
const y = transform.applyY(n.y);
|
|
633
|
-
svg.transition().duration(500).call(
|
|
634
|
-
zoom.translateTo, n.x, n.y
|
|
635
|
-
);
|
|
636
|
-
}
|
|
637
|
-
searchResults.classList.remove('visible');
|
|
638
|
-
});
|
|
639
|
-
});
|
|
640
|
-
} catch (err) {
|
|
641
|
-
searchResults.innerHTML = `<div style="padding:16px;color:var(--red);font-size:12px">Search failed: ${err.message}</div>`;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
353
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
);
|
|
691
|
-
|
|
354
|
+
// Tooltip
|
|
355
|
+
const tip = $('tooltip');
|
|
356
|
+
graphNodes.on('mouseover',(e,d)=>{tip.textContent=d.label;tip.style.display='block';tip.style.left=(e.clientX+12)+'px';tip.style.top=(e.clientY-8)+'px'})
|
|
357
|
+
.on('mousemove',e=>{tip.style.left=(e.clientX+12)+'px';tip.style.top=(e.clientY-8)+'px'})
|
|
358
|
+
.on('mouseout',()=>{tip.style.display='none'});
|
|
359
|
+
|
|
360
|
+
// Click → detail
|
|
361
|
+
graphNodes.on('click',(e,d)=>{e.stopPropagation();highlightGraphNode(d);loadNodeDetail(d.id)});
|
|
362
|
+
svg.on('click',()=>{clearGraphHighlight();drawer.classList.remove('open')});
|
|
363
|
+
|
|
364
|
+
// Legend
|
|
365
|
+
const legendHtml = Object.entries(TYPE_COLORS).filter(([k])=>k!=='default').map(([k,c])=>`<div class="legend-item"><div class="legend-dot" style="background:${c}"></div>${k}</div>`).join('');
|
|
366
|
+
const legendDiv = document.createElement('div'); legendDiv.className='graph-legend'; legendDiv.innerHTML=legendHtml; container.appendChild(legendDiv);
|
|
367
|
+
|
|
368
|
+
// Zoom controls
|
|
369
|
+
const ctrlDiv = document.createElement('div'); ctrlDiv.className='graph-controls';
|
|
370
|
+
ctrlDiv.innerHTML='<button id="gz-in">+</button><button id="gz-out">−</button><button id="gz-fit">⊙</button>';
|
|
371
|
+
container.appendChild(ctrlDiv);
|
|
372
|
+
$('gz-in').onclick=()=>svg.transition().duration(300).call(graphZoom.scaleBy,1.5);
|
|
373
|
+
$('gz-out').onclick=()=>svg.transition().duration(300).call(graphZoom.scaleBy,0.67);
|
|
374
|
+
$('gz-fit').onclick=()=>fitGraph();
|
|
375
|
+
|
|
376
|
+
graphSim.on('end', fitGraph);
|
|
377
|
+
}
|
|
378
|
+
function fitGraph(){ const b=graphG.node().getBBox();if(!b.width)return;const w=$('graph-panel').clientWidth,h=$('graph-panel').clientHeight,p=50;const s=Math.min((w-p*2)/b.width,(h-p*2)/b.height,2);graphSvg.transition().duration(600).call(graphZoom.transform,d3.zoomIdentity.translate(w/2,h/2).scale(s).translate(-b.x-b.width/2,-b.y-b.height/2)); }
|
|
379
|
+
function highlightGraphNode(d){ const c=new Set([d.id]);graphLinks.each(function(l){const s=typeof l.source==='object'?l.source.id:l.source;const t=typeof l.target==='object'?l.target.id:l.target;if(s===d.id)c.add(t);if(t===d.id)c.add(s)});graphNodes.attr('opacity',n=>c.has(n.id)?1:.12);graphLinks.attr('opacity',l=>{const s=typeof l.source==='object'?l.source.id:l.source;const t=typeof l.target==='object'?l.target.id:l.target;return(s===d.id||t===d.id)?1:.04});graphLabels.attr('opacity',n=>c.has(n.id)?1:.08); }
|
|
380
|
+
function clearGraphHighlight(){ graphNodes?.attr('opacity',1); graphLinks?.attr('opacity',1); graphLabels?.attr('opacity',.8); }
|
|
381
|
+
function focusGraphNode(id){ const n=graphNodeMap?.get(id);if(!n)return;highlightGraphNode(n);loadNodeDetail(id);graphSvg.transition().duration(500).call(graphZoom.translateTo,n.x,n.y); }
|
|
382
|
+
|
|
383
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
384
|
+
// TIMELINE PANEL
|
|
385
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
386
|
+
async function loadTimeline(){
|
|
387
|
+
const panel = $('timeline-panel');
|
|
388
|
+
panel.innerHTML = '<div class="tl-header"><h2>Causal Stream</h2><div class="tl-stats" id="tl-stats"></div></div><div class="tl-body" id="tl-body"></div>';
|
|
389
|
+
try{
|
|
390
|
+
const r = await fetch('/api/timeline'); S.timeline = await r.json();
|
|
391
|
+
} catch{ panel.innerHTML='<div class="tl-empty">Failed to load timeline</div>'; return; }
|
|
392
|
+
const { ops, milestones, checkpoints, totalOps } = S.timeline;
|
|
393
|
+
$('tl-stats').textContent = totalOps + ' ops · ' + milestones.length + ' milestones';
|
|
394
|
+
if(!ops.length){ $('tl-body').innerHTML='<div class="tl-empty">No operations yet</div>'; return; }
|
|
395
|
+
const body = $('tl-body');
|
|
396
|
+
const W = Math.max(body.clientWidth, ops.length*14+120), H = body.clientHeight-20;
|
|
397
|
+
const KIND_COLORS = {'vcs:fileAdd':TYPE_COLORS.file,'vcs:fileModify':'#38bdf8','vcs:fileDelete':TYPE_COLORS.issue,'vcs:fileRename':'#fb923c','vcs:branchCreate':TYPE_COLORS.branch,'vcs:branchDelete':'#f472b6','vcs:milestoneCreate':TYPE_COLORS.milestone,'vcs:checkpointCreate':'#6b7280'};
|
|
398
|
+
const svg = d3.select('#tl-body').append('svg').attr('width',W).attr('height',H);
|
|
399
|
+
const x = d3.scaleLinear().domain([0,ops.length-1||1]).range([40,W-40]);
|
|
400
|
+
const laneY = H/2;
|
|
401
|
+
|
|
402
|
+
// Main lane
|
|
403
|
+
svg.append('line').attr('x1',40).attr('y1',laneY).attr('x2',W-40).attr('y2',laneY).attr('stroke','#25262e').attr('stroke-width',1);
|
|
404
|
+
|
|
405
|
+
// Ops
|
|
406
|
+
svg.selectAll('.op-dot').data(ops).join('circle').attr('class','op-dot')
|
|
407
|
+
.attr('cx',(_,i)=>x(i)).attr('cy',laneY).attr('r',3)
|
|
408
|
+
.attr('fill',d=>KIND_COLORS[d.kind]||'#63637a').attr('opacity',.7)
|
|
409
|
+
.attr('cursor','pointer')
|
|
410
|
+
.on('mouseover',function(e,d){const tip=$('tooltip');tip.innerHTML=`<strong>${d.kind}</strong> ${d.filePath||d.branchName||d.message||''}<br><span style="color:var(--text3)">${ago(d.timestamp)}</span>`;tip.style.display='block';tip.style.left=(e.clientX+12)+'px';tip.style.top=(e.clientY-8)+'px';d3.select(this).attr('r',5).attr('opacity',1)})
|
|
411
|
+
.on('mouseout',function(){$('tooltip').style.display='none';d3.select(this).attr('r',3).attr('opacity',.7)})
|
|
412
|
+
.on('click',(e,d)=>{e.stopPropagation();openDrawer('Op: '+d.kind,`<div class="d-section"><div class="d-kv"><span class="k">kind</span><span class="v">${d.kind}</span><span class="k">hash</span><span class="v">${d.hash}</span><span class="k">time</span><span class="v">${d.timestamp||''}</span><span class="k">agent</span><span class="v">${d.agentId||''}</span>${d.filePath?'<span class="k">file</span><span class="v">'+esc(d.filePath)+'</span>':''}${d.branchName?'<span class="k">branch</span><span class="v">'+esc(d.branchName)+'</span>':''}</div></div>`)});
|
|
413
|
+
|
|
414
|
+
// Milestone markers
|
|
415
|
+
milestones.forEach(m=>{const mx=x(m.atOpIndex);svg.append('polygon').attr('points',`${mx},${laneY-18} ${mx+6},${laneY-10} ${mx},${laneY-2} ${mx-6},${laneY-10}`).attr('fill',TYPE_COLORS.milestone).attr('opacity',.85).attr('cursor','pointer')
|
|
416
|
+
.on('click',()=>openDrawer('Milestone',`<div class="d-section"><h4>Message</h4><div class="d-meta"><strong>${esc(m.message||'')}</strong></div></div><div class="d-section"><div class="d-kv"><span class="k">ID</span><span class="v">${m.id}</span><span class="k">at op</span><span class="v">#${m.atOpIndex}</span><span class="k">files</span><span class="v">${m.affectedFiles}</span></div></div>`));
|
|
417
|
+
svg.append('text').attr('x',mx).attr('y',laneY-22).attr('text-anchor','middle').attr('font-size',9).attr('fill',TYPE_COLORS.milestone).attr('font-family','Inter').text(m.message?.slice(0,20)||m.id)});
|
|
418
|
+
|
|
419
|
+
// Checkpoint ticks
|
|
420
|
+
checkpoints.forEach(c=>{const cx=x(c.atOpIndex);svg.append('line').attr('x1',cx).attr('y1',laneY+8).attr('x2',cx).attr('y2',laneY+18).attr('stroke','#6b7280').attr('stroke-width',1).attr('opacity',.5)});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
424
|
+
// STORE PANEL
|
|
425
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
426
|
+
async function loadStore(){
|
|
427
|
+
const panel = $('store-panel');
|
|
428
|
+
panel.innerHTML='<div style="padding:40px;color:var(--text3)">Loading store…</div>';
|
|
429
|
+
try{ const r=await fetch('/api/store'); S.store=await r.json(); } catch{ panel.innerHTML='<div style="padding:40px;color:var(--red)">Failed to load</div>'; return; }
|
|
430
|
+
const d=S.store;
|
|
431
|
+
let h='<div class="stat-grid"><div class="stat-card"><div class="sv">'+d.stats.totalFacts+'</div><div class="sl">Facts</div></div><div class="stat-card"><div class="sv">'+d.stats.totalLinks+'</div><div class="sl">Links</div></div><div class="stat-card"><div class="sv">'+d.stats.uniqueEntities+'</div><div class="sl">Entities</div></div><div class="stat-card"><div class="sv">'+d.stats.uniqueAttributes+'</div><div class="sl">Attributes</div></div></div>';
|
|
432
|
+
h+='<div class="store-grid"><div class="store-col"><div class="store-section"><h3>Entity Types</h3><div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:12px">';
|
|
433
|
+
Object.entries(d.entityTypes).forEach(([t,c])=>h+=`<span class="type-pill" data-type="${esc(t)}">${esc(t)} <span class="tc">${c}</span></span>`);
|
|
434
|
+
h+='</div><div id="entity-list">';
|
|
435
|
+
d.entities.slice(0,200).forEach(e=>h+=`<div class="entity-row" data-eid="${esc(e.id)}"><span class="et">${esc(e.type)}</span><span class="ei">${esc(e.id)}</span><span class="ec">${e.factCount}f</span></div>`);
|
|
436
|
+
h+='</div></div></div><div class="store-col"><div class="store-section"><h3>Attribute Catalog</h3><table class="catalog-table"><thead><tr><th>Attribute</th><th>Type</th><th>Card</th><th>#</th></tr></thead><tbody>';
|
|
437
|
+
d.catalog.forEach(c=>h+=`<tr><td>${esc(c.attribute)}</td><td>${c.type}</td><td>${c.cardinality}</td><td>${c.distinctCount}</td></tr>`);
|
|
438
|
+
h+='</tbody></table></div></div></div>';
|
|
439
|
+
panel.innerHTML=h;
|
|
440
|
+
|
|
441
|
+
// Entity click → detail
|
|
442
|
+
panel.querySelectorAll('.entity-row').forEach(el=>el.addEventListener('click',()=>loadEntityDetail(el.dataset.eid)));
|
|
443
|
+
// Type filter
|
|
444
|
+
panel.querySelectorAll('.type-pill').forEach(el=>el.addEventListener('click',()=>{
|
|
445
|
+
const t=el.dataset.type; const active=el.classList.toggle('active');
|
|
446
|
+
panel.querySelectorAll('.type-pill').forEach(p=>{if(p!==el)p.classList.remove('active')});
|
|
447
|
+
panel.querySelectorAll('.entity-row').forEach(r=>{r.style.display=(!active||r.querySelector('.et').textContent===t)?'':'none'});
|
|
448
|
+
}));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
452
|
+
// SYSTEM PANEL
|
|
453
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
454
|
+
async function loadSystem(){
|
|
455
|
+
const panel=$('system-panel');
|
|
456
|
+
panel.innerHTML='<div style="color:var(--text3)">Loading…</div>';
|
|
457
|
+
try{ const r=await fetch('/api/system'); S.system=await r.json(); }catch{ panel.innerHTML='<div style="color:var(--red)">Failed to load</div>'; return; }
|
|
458
|
+
const d=S.system;
|
|
459
|
+
let h='<h2 style="font:600 16px var(--font);color:var(--text)">System Overview</h2><div class="sys-grid">';
|
|
460
|
+
// Engine card
|
|
461
|
+
h+=`<div class="sys-card"><h3><span class="icon">⚡</span>Engine</h3>
|
|
462
|
+
<div class="sys-row"><span class="label">Root</span><span class="value">${esc(d.engine.rootPath)}</span></div>
|
|
463
|
+
<div class="sys-row"><span class="label">Branch</span><span class="value">${esc(d.engine.branch)}</span></div>
|
|
464
|
+
<div class="sys-row"><span class="label">Total Ops</span><span class="value">${d.engine.totalOps}</span></div>
|
|
465
|
+
<div class="sys-row"><span class="label">Tracked Files</span><span class="value">${d.engine.trackedFiles}</span></div>
|
|
466
|
+
<div class="sys-row"><span class="label">Last Op</span><span class="value">${ago(d.engine.lastOpAt)}</span></div></div>`;
|
|
467
|
+
// Store card
|
|
468
|
+
h+=`<div class="sys-card"><h3><span class="icon">◆</span>EAV Store</h3>
|
|
469
|
+
<div class="sys-row"><span class="label">Facts</span><span class="value">${d.store.totalFacts}</span></div>
|
|
470
|
+
<div class="sys-row"><span class="label">Links</span><span class="value">${d.store.totalLinks}</span></div>
|
|
471
|
+
<div class="sys-row"><span class="label">Entities</span><span class="value">${d.store.uniqueEntities}</span></div>
|
|
472
|
+
<div class="sys-row"><span class="label">Attributes</span><span class="value">${d.store.uniqueAttributes}</span></div></div>`;
|
|
473
|
+
// Features card
|
|
474
|
+
h+=`<div class="sys-card"><h3><span class="icon">⊕</span>Features</h3>
|
|
475
|
+
<div class="sys-row"><span class="label">Embeddings</span><span class="sys-badge ${d.features.embeddings?'on':'off'}">${d.features.embeddings?'active':'off'}</span></div>
|
|
476
|
+
<div class="sys-row"><span class="label">Blob Store</span><span class="sys-badge ${d.features.blobStore?'on':'off'}">${d.features.blobStore?'active':'off'}</span></div></div>`;
|
|
477
|
+
// Parsers card
|
|
478
|
+
h+=`<div class="sys-card"><h3><span class="icon">⟨⟩</span>Parser Adapters</h3><div class="parser-tags">${d.parsers.map(p=>`<span class="parser-tag">${p}</span>`).join('')}</div></div>`;
|
|
479
|
+
h+='</div>';
|
|
480
|
+
panel.innerHTML=h;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
484
|
+
// BOOT
|
|
485
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
486
|
+
await initGraph();
|
|
487
|
+
$('loading').classList.add('hidden');
|
|
692
488
|
})();
|
|
693
489
|
</script>
|
|
694
490
|
</body>
|