@luanpdd/kit-mcp 1.2.3 → 1.3.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/CHANGELOG.md +39 -0
- package/package.json +1 -1
- package/src/ui/static/index.html +1636 -966
package/src/ui/static/index.html
CHANGED
|
@@ -1,1022 +1,1692 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="
|
|
2
|
+
<html lang="pt-BR">
|
|
3
3
|
<head>
|
|
4
|
-
<meta charset="utf-8"
|
|
5
|
-
<meta name="viewport" content="width=device-width,
|
|
6
|
-
<title>kit-mcp sidecar</title>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
+
<title>kit-mcp · sidecar</title>
|
|
7
7
|
<style>
|
|
8
|
-
|
|
8
|
+
/* ───────────────────────── tokens ───────────────────────── */
|
|
9
|
+
:root {
|
|
10
|
+
/* surfaces */
|
|
11
|
+
--bg: #000000;
|
|
12
|
+
--surface-1: #0b0d10;
|
|
13
|
+
--surface-2: #11141a;
|
|
14
|
+
--surface-3: #171b22;
|
|
15
|
+
--hover: #1c2129;
|
|
16
|
+
|
|
17
|
+
/* lines + text */
|
|
18
|
+
--line: rgba(255, 255, 255, 0.06);
|
|
19
|
+
--line-strong: rgba(255, 255, 255, 0.12);
|
|
20
|
+
--text: #e6e8eb;
|
|
21
|
+
--text-2: #a8adb6;
|
|
22
|
+
--text-3: #6c7280;
|
|
23
|
+
--text-4: #4a4f57;
|
|
24
|
+
|
|
25
|
+
/* states */
|
|
26
|
+
--accent-h: 130;
|
|
27
|
+
--accent: oklch(82% 0.18 var(--accent-h));
|
|
28
|
+
--accent-soft: oklch(82% 0.18 var(--accent-h) / 0.16);
|
|
29
|
+
--accent-glow: oklch(82% 0.18 var(--accent-h) / 0.42);
|
|
30
|
+
--ok: #d4d8df;
|
|
31
|
+
--err: oklch(70% 0.18 25);
|
|
32
|
+
--err-soft: oklch(70% 0.18 25 / 0.14);
|
|
33
|
+
--warn: oklch(82% 0.14 75);
|
|
34
|
+
--info: oklch(78% 0.10 250);
|
|
35
|
+
|
|
36
|
+
/* type */
|
|
37
|
+
--sans: -apple-system, "Segoe UI", system-ui, "Helvetica Neue", Arial, sans-serif;
|
|
38
|
+
--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
|
|
39
|
+
|
|
40
|
+
/* shape */
|
|
41
|
+
--radius: 10px;
|
|
42
|
+
--radius-sm: 6px;
|
|
43
|
+
|
|
44
|
+
/* motion */
|
|
45
|
+
--ease: cubic-bezier(.2,.7,.2,1);
|
|
46
|
+
|
|
47
|
+
/* layout */
|
|
48
|
+
--pad: 16px;
|
|
49
|
+
--pad-tight: 10px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@media (prefers-color-scheme: light) {
|
|
9
53
|
:root {
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
54
|
+
--bg: #fbfbfa;
|
|
55
|
+
--surface-1: #ffffff;
|
|
56
|
+
--surface-2: #f7f7f6;
|
|
57
|
+
--surface-3: #f1f1f0;
|
|
58
|
+
--hover: #ebebea;
|
|
59
|
+
--line: rgba(0,0,0,.07);
|
|
60
|
+
--line-strong: rgba(0,0,0,.14);
|
|
61
|
+
--text: #15171a;
|
|
62
|
+
--text-2: #54585f;
|
|
63
|
+
--text-3: #80858d;
|
|
64
|
+
--text-4: #a8adb4;
|
|
65
|
+
--accent: oklch(58% 0.18 var(--accent-h));
|
|
66
|
+
--accent-soft: oklch(58% 0.18 var(--accent-h) / 0.10);
|
|
67
|
+
--accent-glow: oklch(58% 0.18 var(--accent-h) / 0.30);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* density tweak */
|
|
72
|
+
:root[data-density="compact"] { --pad: 10px; --pad-tight: 6px; }
|
|
73
|
+
:root[data-density="comfy"] { --pad: 20px; --pad-tight: 14px; }
|
|
74
|
+
|
|
75
|
+
/* ───────────────────────── base ───────────────────────── */
|
|
76
|
+
* { box-sizing: border-box; }
|
|
77
|
+
html, body {
|
|
78
|
+
margin: 0;
|
|
79
|
+
background: var(--bg);
|
|
80
|
+
color: var(--text);
|
|
81
|
+
font-family: var(--sans);
|
|
82
|
+
font-size: 13px;
|
|
83
|
+
line-height: 1.4;
|
|
84
|
+
-webkit-font-smoothing: antialiased;
|
|
85
|
+
text-rendering: optimizeLegibility;
|
|
86
|
+
}
|
|
87
|
+
body {
|
|
88
|
+
min-height: 100vh;
|
|
89
|
+
font-feature-settings: "ss01", "cv11";
|
|
90
|
+
}
|
|
91
|
+
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; padding: 0; }
|
|
92
|
+
::selection { background: var(--accent-soft); color: var(--text); }
|
|
93
|
+
|
|
94
|
+
/* scrollbar */
|
|
95
|
+
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
96
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
97
|
+
::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 10px; border: 2px solid var(--bg); }
|
|
98
|
+
::-webkit-scrollbar-thumb:hover { background: var(--text-4); }
|
|
99
|
+
|
|
100
|
+
/* ───────────────────────── shell ───────────────────────── */
|
|
101
|
+
.app {
|
|
102
|
+
display: grid;
|
|
103
|
+
grid-template-rows: auto auto 1fr auto;
|
|
104
|
+
min-height: 100vh;
|
|
105
|
+
max-width: 980px;
|
|
106
|
+
margin: 0 auto;
|
|
107
|
+
padding: 18px 22px 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@media (max-width: 520px) {
|
|
111
|
+
.app { padding: 12px 14px 0; }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* ───────────────────────── header ───────────────────────── */
|
|
115
|
+
.header {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 12px;
|
|
119
|
+
padding: 6px 0 16px;
|
|
120
|
+
border-bottom: 1px solid var(--line);
|
|
121
|
+
margin-bottom: 16px;
|
|
122
|
+
}
|
|
123
|
+
.logo {
|
|
124
|
+
width: 22px; height: 22px;
|
|
125
|
+
display: grid; place-items: center;
|
|
126
|
+
border-radius: 6px;
|
|
127
|
+
background: var(--accent-soft);
|
|
128
|
+
color: var(--accent);
|
|
129
|
+
flex-shrink: 0;
|
|
130
|
+
}
|
|
131
|
+
.logo svg { width: 14px; height: 14px; }
|
|
132
|
+
.brand {
|
|
133
|
+
display: flex; flex-direction: column; gap: 1px;
|
|
134
|
+
min-width: 0;
|
|
135
|
+
}
|
|
136
|
+
.brand-row {
|
|
137
|
+
display: flex; align-items: baseline; gap: 8px;
|
|
138
|
+
font-family: var(--mono);
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
letter-spacing: -0.01em;
|
|
141
|
+
}
|
|
142
|
+
.brand-name { color: var(--text); font-weight: 500; }
|
|
143
|
+
.brand-meta { color: var(--text-3); font-size: 11px; }
|
|
144
|
+
.brand-sub {
|
|
145
|
+
font-size: 11px;
|
|
146
|
+
color: var(--text-3);
|
|
147
|
+
}
|
|
148
|
+
.spacer { flex: 1; }
|
|
149
|
+
.conn {
|
|
150
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
151
|
+
font-family: var(--mono);
|
|
152
|
+
font-size: 11px;
|
|
153
|
+
color: var(--text-2);
|
|
154
|
+
padding: 4px 8px;
|
|
155
|
+
border: 1px solid var(--line);
|
|
156
|
+
border-radius: 999px;
|
|
157
|
+
background: var(--surface-1);
|
|
158
|
+
}
|
|
159
|
+
.conn .dot {
|
|
160
|
+
width: 6px; height: 6px; border-radius: 999px;
|
|
161
|
+
background: var(--accent);
|
|
162
|
+
box-shadow: 0 0 0 0 var(--accent-glow);
|
|
163
|
+
animation: connpulse 2.4s ease-out infinite;
|
|
164
|
+
}
|
|
165
|
+
@keyframes connpulse {
|
|
166
|
+
0% { box-shadow: 0 0 0 0 var(--accent-glow); }
|
|
167
|
+
70% { box-shadow: 0 0 0 6px transparent; }
|
|
168
|
+
100% { box-shadow: 0 0 0 0 transparent; }
|
|
169
|
+
}
|
|
170
|
+
.conn[data-state="off"] .dot { background: var(--text-4); animation: none; }
|
|
171
|
+
.conn[data-state="off"] { color: var(--text-3); }
|
|
172
|
+
|
|
173
|
+
/* ───────────────────────── toolbar ───────────────────────── */
|
|
174
|
+
.toolbar {
|
|
175
|
+
display: flex; align-items: center; gap: 8px;
|
|
176
|
+
margin-bottom: 16px;
|
|
177
|
+
}
|
|
178
|
+
.search {
|
|
179
|
+
flex: 1;
|
|
180
|
+
display: flex; align-items: center; gap: 8px;
|
|
181
|
+
padding: 7px 10px;
|
|
182
|
+
border: 1px solid var(--line);
|
|
183
|
+
border-radius: var(--radius-sm);
|
|
184
|
+
background: var(--surface-1);
|
|
185
|
+
transition: border-color .15s var(--ease), background .15s var(--ease);
|
|
186
|
+
}
|
|
187
|
+
.search:focus-within { border-color: var(--line-strong); background: var(--surface-2); }
|
|
188
|
+
.search svg { width: 13px; height: 13px; color: var(--text-3); flex-shrink: 0; }
|
|
189
|
+
.search input {
|
|
190
|
+
flex: 1;
|
|
191
|
+
background: transparent; border: 0; outline: 0; color: var(--text);
|
|
192
|
+
font: inherit;
|
|
193
|
+
font-size: 12px;
|
|
194
|
+
}
|
|
195
|
+
.search input::placeholder { color: var(--text-3); }
|
|
196
|
+
.search kbd {
|
|
197
|
+
font-family: var(--mono);
|
|
198
|
+
font-size: 10px;
|
|
199
|
+
padding: 2px 5px;
|
|
200
|
+
border-radius: 3px;
|
|
201
|
+
background: var(--surface-3);
|
|
202
|
+
border: 1px solid var(--line);
|
|
203
|
+
color: var(--text-3);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.iconbtn {
|
|
207
|
+
display: grid; place-items: center;
|
|
208
|
+
width: 30px; height: 30px;
|
|
209
|
+
border-radius: var(--radius-sm);
|
|
210
|
+
border: 1px solid var(--line);
|
|
211
|
+
background: var(--surface-1);
|
|
212
|
+
color: var(--text-2);
|
|
213
|
+
transition: all .15s var(--ease);
|
|
214
|
+
}
|
|
215
|
+
.iconbtn:hover { color: var(--text); background: var(--surface-2); border-color: var(--line-strong); }
|
|
216
|
+
.iconbtn[aria-pressed="true"] { color: var(--accent); border-color: var(--accent-soft); background: var(--accent-soft); }
|
|
217
|
+
.iconbtn svg { width: 13px; height: 13px; }
|
|
218
|
+
|
|
219
|
+
/* filter popover */
|
|
220
|
+
.filter-wrap { position: relative; }
|
|
221
|
+
.filter-pop {
|
|
222
|
+
position: absolute;
|
|
223
|
+
top: calc(100% + 6px); right: 0;
|
|
224
|
+
min-width: 200px;
|
|
225
|
+
padding: 6px;
|
|
226
|
+
background: var(--surface-2);
|
|
227
|
+
border: 1px solid var(--line-strong);
|
|
228
|
+
border-radius: var(--radius);
|
|
229
|
+
box-shadow: 0 8px 28px rgba(0,0,0,.6), 0 0 0 1px rgba(255,255,255,.02);
|
|
230
|
+
z-index: 30;
|
|
231
|
+
display: none;
|
|
232
|
+
}
|
|
233
|
+
.filter-pop.open { display: block; animation: pop-in .14s var(--ease); }
|
|
234
|
+
@keyframes pop-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
|
|
235
|
+
.fp-title { font-size: 10px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-3); padding: 6px 8px 4px; }
|
|
236
|
+
.fp-row {
|
|
237
|
+
display: flex; align-items: center; gap: 8px;
|
|
238
|
+
padding: 6px 8px;
|
|
239
|
+
border-radius: 6px;
|
|
240
|
+
font-size: 12px;
|
|
241
|
+
color: var(--text-2);
|
|
242
|
+
cursor: pointer;
|
|
243
|
+
user-select: none;
|
|
244
|
+
}
|
|
245
|
+
.fp-row:hover { background: var(--surface-3); color: var(--text); }
|
|
246
|
+
.fp-row[data-on="true"] { color: var(--text); }
|
|
247
|
+
.fp-row[data-on="true"] .fp-check { color: var(--accent); }
|
|
248
|
+
.fp-check { width: 12px; opacity: 0; }
|
|
249
|
+
.fp-row[data-on="true"] .fp-check { opacity: 1; }
|
|
250
|
+
.fp-row .fp-glyph { font-family: var(--mono); width: 12px; text-align: center; color: var(--text-3); }
|
|
251
|
+
|
|
252
|
+
/* ───────────────────────── active runs hero ───────────────────────── */
|
|
253
|
+
.active-region {
|
|
254
|
+
margin-bottom: 18px;
|
|
255
|
+
display: flex; flex-direction: column; gap: 10px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.run-card {
|
|
259
|
+
position: relative;
|
|
260
|
+
padding: var(--pad);
|
|
261
|
+
background:
|
|
262
|
+
radial-gradient(120% 80% at 0% 0%, var(--accent-soft) 0%, transparent 45%),
|
|
263
|
+
var(--surface-1);
|
|
264
|
+
border: 1px solid var(--line);
|
|
265
|
+
border-radius: var(--radius);
|
|
266
|
+
overflow: hidden;
|
|
267
|
+
isolation: isolate;
|
|
268
|
+
}
|
|
269
|
+
.run-card.enter { animation: run-in .35s var(--ease); }
|
|
270
|
+
@keyframes run-in {
|
|
271
|
+
from { opacity: 0; transform: translateY(6px) scale(.995); }
|
|
272
|
+
to { opacity: 1; transform: none; }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* ambient running glow on the card edge */
|
|
276
|
+
.run-card::before {
|
|
277
|
+
content: "";
|
|
278
|
+
position: absolute; inset: -1px;
|
|
279
|
+
border-radius: inherit;
|
|
280
|
+
padding: 1px;
|
|
281
|
+
background: conic-gradient(from var(--ang, 0deg),
|
|
282
|
+
transparent 0deg, transparent 280deg,
|
|
283
|
+
var(--accent) 320deg, var(--accent) 340deg, transparent 360deg);
|
|
284
|
+
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
|
285
|
+
-webkit-mask-composite: xor;
|
|
286
|
+
mask-composite: exclude;
|
|
287
|
+
opacity: .8;
|
|
288
|
+
pointer-events: none;
|
|
289
|
+
animation: spin 4s linear infinite;
|
|
290
|
+
}
|
|
291
|
+
@property --ang { syntax: "<angle>"; initial-value: 0deg; inherits: false; }
|
|
292
|
+
@keyframes spin { to { --ang: 360deg; } }
|
|
293
|
+
|
|
294
|
+
:root[data-motion="subtle"] .run-card::before { animation: none; opacity: .4; }
|
|
295
|
+
|
|
296
|
+
.rc-head {
|
|
297
|
+
display: flex; align-items: center; gap: 10px;
|
|
298
|
+
margin-bottom: 12px;
|
|
299
|
+
}
|
|
300
|
+
.rc-icon {
|
|
301
|
+
width: 32px; height: 32px;
|
|
302
|
+
border-radius: 8px;
|
|
303
|
+
display: grid; place-items: center;
|
|
304
|
+
background: var(--surface-3);
|
|
305
|
+
color: var(--text);
|
|
306
|
+
font-family: var(--mono);
|
|
307
|
+
font-size: 12px;
|
|
308
|
+
font-weight: 600;
|
|
309
|
+
border: 1px solid var(--line-strong);
|
|
310
|
+
position: relative;
|
|
311
|
+
flex-shrink: 0;
|
|
312
|
+
}
|
|
313
|
+
.rc-icon svg { width: 15px; height: 15px; }
|
|
314
|
+
.rc-icon[data-tool="sync"] { color: var(--accent); }
|
|
315
|
+
.rc-icon[data-tool="reverse"] { color: oklch(78% 0.14 280); }
|
|
316
|
+
.rc-icon[data-tool="gates"] { color: oklch(82% 0.14 75); }
|
|
317
|
+
|
|
318
|
+
.rc-title-block { min-width: 0; flex: 1; }
|
|
319
|
+
.rc-tool {
|
|
320
|
+
font-family: var(--mono);
|
|
321
|
+
font-size: 11px;
|
|
322
|
+
color: var(--text-3);
|
|
323
|
+
letter-spacing: -0.01em;
|
|
324
|
+
}
|
|
325
|
+
.rc-title {
|
|
326
|
+
font-size: 16px;
|
|
327
|
+
font-weight: 500;
|
|
328
|
+
color: var(--text);
|
|
329
|
+
letter-spacing: -0.01em;
|
|
330
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
331
|
+
}
|
|
332
|
+
.rc-elapsed {
|
|
333
|
+
font-family: var(--mono);
|
|
334
|
+
font-size: 12px;
|
|
335
|
+
color: var(--text-2);
|
|
336
|
+
text-align: right;
|
|
337
|
+
font-variant-numeric: tabular-nums;
|
|
338
|
+
flex-shrink: 0;
|
|
339
|
+
display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
|
|
340
|
+
}
|
|
341
|
+
.rc-elapsed .em { color: var(--accent); }
|
|
342
|
+
.rc-elapsed .em.warn { color: var(--warn); }
|
|
343
|
+
.rc-elapsed small { color: var(--text-3); font-size: 10px; text-transform: uppercase; letter-spacing: .08em; }
|
|
344
|
+
|
|
345
|
+
/* progress */
|
|
346
|
+
.rc-progress {
|
|
347
|
+
display: flex; align-items: center; gap: 12px;
|
|
348
|
+
margin-bottom: 10px;
|
|
349
|
+
}
|
|
350
|
+
.rc-bar {
|
|
351
|
+
flex: 1;
|
|
352
|
+
height: 6px;
|
|
353
|
+
border-radius: 999px;
|
|
354
|
+
background: var(--surface-3);
|
|
355
|
+
overflow: hidden;
|
|
356
|
+
position: relative;
|
|
357
|
+
}
|
|
358
|
+
.rc-bar-fill {
|
|
359
|
+
height: 100%;
|
|
360
|
+
background: linear-gradient(90deg, var(--accent) 0%, oklch(88% 0.16 calc(var(--accent-h) + 30)) 100%);
|
|
361
|
+
border-radius: inherit;
|
|
362
|
+
transition: width .6s var(--ease);
|
|
363
|
+
position: relative;
|
|
364
|
+
}
|
|
365
|
+
.rc-bar-fill::after {
|
|
366
|
+
content: "";
|
|
367
|
+
position: absolute; inset: 0;
|
|
368
|
+
background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,.35) 50%, transparent 100%);
|
|
369
|
+
animation: shimmer 2s linear infinite;
|
|
370
|
+
mix-blend-mode: overlay;
|
|
371
|
+
}
|
|
372
|
+
@keyframes shimmer { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
|
|
373
|
+
:root[data-motion="subtle"] .rc-bar-fill::after { display: none; }
|
|
374
|
+
|
|
375
|
+
.rc-pct {
|
|
376
|
+
font-family: var(--mono);
|
|
377
|
+
font-size: 13px;
|
|
378
|
+
color: var(--text);
|
|
379
|
+
font-variant-numeric: tabular-nums;
|
|
380
|
+
min-width: 38px;
|
|
381
|
+
text-align: right;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* current step caption */
|
|
385
|
+
.rc-step {
|
|
386
|
+
display: flex; align-items: center; gap: 8px;
|
|
387
|
+
font-family: var(--mono);
|
|
388
|
+
font-size: 12px;
|
|
389
|
+
color: var(--text-2);
|
|
390
|
+
padding: 8px 10px;
|
|
391
|
+
background: var(--surface-2);
|
|
392
|
+
border: 1px solid var(--line);
|
|
393
|
+
border-radius: 6px;
|
|
394
|
+
white-space: nowrap; overflow: hidden;
|
|
395
|
+
}
|
|
396
|
+
.rc-step .glyph {
|
|
397
|
+
color: var(--accent);
|
|
398
|
+
display: inline-grid; place-items: center;
|
|
399
|
+
width: 12px; height: 12px;
|
|
400
|
+
flex-shrink: 0;
|
|
401
|
+
}
|
|
402
|
+
.rc-step .glyph svg { width: 12px; height: 12px; animation: spin-slow 1.4s linear infinite; }
|
|
403
|
+
@keyframes spin-slow { to { transform: rotate(360deg); } }
|
|
404
|
+
|
|
405
|
+
.rc-step-text {
|
|
406
|
+
flex: 1;
|
|
407
|
+
overflow: hidden; text-overflow: ellipsis;
|
|
408
|
+
transition: opacity .25s var(--ease);
|
|
409
|
+
}
|
|
410
|
+
.rc-step-text.fade { opacity: 0.3; }
|
|
411
|
+
|
|
412
|
+
.rc-step-count {
|
|
413
|
+
color: var(--text-3);
|
|
414
|
+
font-variant-numeric: tabular-nums;
|
|
415
|
+
font-size: 11px;
|
|
416
|
+
flex-shrink: 0;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* run id chip */
|
|
420
|
+
.rc-foot {
|
|
421
|
+
display: flex; align-items: center; gap: 10px;
|
|
422
|
+
margin-top: 10px;
|
|
423
|
+
font-family: var(--mono);
|
|
424
|
+
font-size: 10px;
|
|
425
|
+
color: var(--text-3);
|
|
426
|
+
}
|
|
427
|
+
.rc-runid {
|
|
428
|
+
padding: 2px 6px;
|
|
429
|
+
border-radius: 4px;
|
|
430
|
+
background: var(--surface-3);
|
|
431
|
+
border: 1px solid var(--line);
|
|
432
|
+
color: var(--text-2);
|
|
433
|
+
}
|
|
434
|
+
.rc-foot .sep { color: var(--text-4); }
|
|
435
|
+
|
|
436
|
+
/* multiple active runs => stack 'em */
|
|
437
|
+
.active-region[data-count="2"] .run-card,
|
|
438
|
+
.active-region[data-count="3"] .run-card { padding: 12px; }
|
|
439
|
+
.active-region[data-count="2"] .run-card .rc-title,
|
|
440
|
+
.active-region[data-count="3"] .run-card .rc-title { font-size: 14px; }
|
|
441
|
+
|
|
442
|
+
/* ───────────────────────── log section ───────────────────────── */
|
|
443
|
+
.section-head {
|
|
444
|
+
display: flex; align-items: baseline; gap: 8px;
|
|
445
|
+
margin: 8px 0 10px;
|
|
446
|
+
padding: 0 2px;
|
|
447
|
+
}
|
|
448
|
+
.section-title {
|
|
449
|
+
font-family: var(--mono);
|
|
450
|
+
font-size: 10px;
|
|
451
|
+
text-transform: uppercase;
|
|
452
|
+
letter-spacing: .12em;
|
|
453
|
+
color: var(--text-3);
|
|
454
|
+
}
|
|
455
|
+
.section-count {
|
|
456
|
+
font-family: var(--mono);
|
|
457
|
+
font-size: 10px;
|
|
458
|
+
color: var(--text-4);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/* timeline */
|
|
462
|
+
.timeline {
|
|
463
|
+
position: relative;
|
|
464
|
+
padding: 0 0 32px 0;
|
|
465
|
+
}
|
|
466
|
+
.tl-day {
|
|
467
|
+
display: flex; align-items: center; gap: 8px;
|
|
468
|
+
font-family: var(--mono);
|
|
469
|
+
font-size: 10px;
|
|
470
|
+
color: var(--text-3);
|
|
471
|
+
text-transform: uppercase;
|
|
472
|
+
letter-spacing: .08em;
|
|
473
|
+
padding: 14px 0 10px;
|
|
474
|
+
position: sticky;
|
|
475
|
+
top: 0;
|
|
476
|
+
background: linear-gradient(var(--bg) 70%, transparent);
|
|
477
|
+
z-index: 1;
|
|
478
|
+
}
|
|
479
|
+
.tl-day::before, .tl-day::after {
|
|
480
|
+
content: ""; flex: 1;
|
|
481
|
+
border-top: 1px dashed var(--line);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.tl-row {
|
|
485
|
+
display: grid;
|
|
486
|
+
grid-template-columns: 64px 18px 1fr;
|
|
487
|
+
gap: 0;
|
|
488
|
+
padding: var(--pad-tight) 0;
|
|
489
|
+
position: relative;
|
|
490
|
+
border-radius: 4px;
|
|
491
|
+
transition: background .15s var(--ease);
|
|
492
|
+
}
|
|
493
|
+
.tl-row.enter { animation: row-in .35s var(--ease); }
|
|
494
|
+
.tl-row:hover { background: var(--surface-1); }
|
|
495
|
+
@keyframes row-in {
|
|
496
|
+
from { opacity: 0; transform: translateX(-4px); }
|
|
497
|
+
to { opacity: 1; transform: none; }
|
|
498
|
+
}
|
|
499
|
+
.tl-row.is-new { background: var(--accent-soft); }
|
|
500
|
+
.tl-row.is-new::after {
|
|
501
|
+
content: "novo";
|
|
502
|
+
position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
|
|
503
|
+
font-family: var(--mono);
|
|
504
|
+
font-size: 9px;
|
|
505
|
+
text-transform: uppercase;
|
|
506
|
+
letter-spacing: .1em;
|
|
507
|
+
color: var(--accent);
|
|
508
|
+
animation: fade-out 2.4s 1s forwards;
|
|
509
|
+
}
|
|
510
|
+
@keyframes fade-out { to { opacity: 0; } }
|
|
511
|
+
|
|
512
|
+
.tl-time {
|
|
513
|
+
font-family: var(--mono);
|
|
514
|
+
font-size: 11px;
|
|
515
|
+
color: var(--text-3);
|
|
516
|
+
font-variant-numeric: tabular-nums;
|
|
517
|
+
padding-left: 4px;
|
|
518
|
+
padding-top: 1px;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/* rail with node */
|
|
522
|
+
.tl-rail {
|
|
523
|
+
position: relative;
|
|
524
|
+
display: flex; justify-content: center;
|
|
525
|
+
}
|
|
526
|
+
.tl-rail::before {
|
|
527
|
+
content: "";
|
|
528
|
+
position: absolute;
|
|
529
|
+
top: -10px; bottom: -10px;
|
|
530
|
+
left: 50%; width: 1px;
|
|
531
|
+
background: var(--line);
|
|
532
|
+
transform: translateX(-.5px);
|
|
533
|
+
}
|
|
534
|
+
.tl-row:first-of-type .tl-rail::before { top: 8px; }
|
|
535
|
+
.tl-row:last-of-type .tl-rail::before { bottom: calc(100% - 8px); }
|
|
536
|
+
|
|
537
|
+
.tl-node {
|
|
538
|
+
position: relative;
|
|
539
|
+
z-index: 1;
|
|
540
|
+
width: 7px; height: 7px;
|
|
541
|
+
border-radius: 999px;
|
|
542
|
+
margin-top: 5px;
|
|
543
|
+
background: var(--text-4);
|
|
544
|
+
border: 2px solid var(--bg);
|
|
545
|
+
box-sizing: content-box;
|
|
546
|
+
}
|
|
547
|
+
.tl-row[data-type="run.start"] .tl-node { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
548
|
+
.tl-row[data-type="run.end"] .tl-node { background: var(--text-2); }
|
|
549
|
+
.tl-row[data-type="run.end"][data-ok="false"] .tl-node { background: var(--err); }
|
|
550
|
+
.tl-row[data-type="error"] .tl-node { background: var(--err); box-shadow: 0 0 0 3px var(--err-soft); }
|
|
551
|
+
.tl-row[data-type="milestone"] .tl-node { background: var(--accent); }
|
|
552
|
+
.tl-row[data-type="progress"] .tl-node { width: 5px; height: 5px; margin-top: 6px; }
|
|
553
|
+
|
|
554
|
+
/* group runId connector — visually subtle indent */
|
|
555
|
+
.tl-row[data-grouped="true"] { padding-left: 0; }
|
|
556
|
+
.tl-row[data-grouped="true"] .tl-node { background: var(--text-3); }
|
|
557
|
+
|
|
558
|
+
.tl-content {
|
|
559
|
+
display: flex; align-items: baseline; gap: 8px;
|
|
560
|
+
padding-left: 8px;
|
|
561
|
+
min-width: 0;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.tl-badge {
|
|
565
|
+
font-family: var(--mono);
|
|
566
|
+
font-size: 10px;
|
|
567
|
+
letter-spacing: .04em;
|
|
568
|
+
padding: 1px 6px;
|
|
569
|
+
border-radius: 3px;
|
|
570
|
+
border: 1px solid var(--line);
|
|
571
|
+
color: var(--text-2);
|
|
572
|
+
background: var(--surface-1);
|
|
573
|
+
flex-shrink: 0;
|
|
574
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
575
|
+
}
|
|
576
|
+
.tl-badge .g { font-family: var(--mono); }
|
|
577
|
+
|
|
578
|
+
.tl-row[data-type="run.start"] .tl-badge { color: var(--accent); border-color: var(--accent-soft); background: var(--accent-soft); }
|
|
579
|
+
.tl-row[data-type="error"] .tl-badge { color: var(--err); border-color: var(--err-soft); background: var(--err-soft); }
|
|
580
|
+
.tl-row[data-type="milestone"] .tl-badge { color: var(--accent); border-color: var(--accent-soft); background: var(--accent-soft); }
|
|
581
|
+
.tl-row[data-type="run.end"][data-ok="true"] .tl-badge { color: var(--text); }
|
|
582
|
+
.tl-row[data-type="run.end"][data-ok="false"] .tl-badge { color: var(--err); border-color: var(--err-soft); background: var(--err-soft); }
|
|
583
|
+
|
|
584
|
+
.tl-msg {
|
|
585
|
+
flex: 1;
|
|
586
|
+
min-width: 0;
|
|
587
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
588
|
+
font-size: 12px;
|
|
589
|
+
color: var(--text);
|
|
590
|
+
}
|
|
591
|
+
.tl-row[data-type="progress"] .tl-msg { color: var(--text-2); }
|
|
592
|
+
|
|
593
|
+
.tl-msg .path,
|
|
594
|
+
.tl-msg .ident {
|
|
595
|
+
font-family: var(--mono);
|
|
596
|
+
font-size: 11px;
|
|
597
|
+
color: var(--text-2);
|
|
598
|
+
background: var(--surface-2);
|
|
599
|
+
padding: 1px 5px;
|
|
600
|
+
border-radius: 3px;
|
|
601
|
+
border: 1px solid var(--line);
|
|
602
|
+
}
|
|
603
|
+
.tl-msg .arrow { color: var(--text-3); margin: 0 4px; }
|
|
604
|
+
|
|
605
|
+
.tl-runid {
|
|
606
|
+
font-family: var(--mono);
|
|
607
|
+
font-size: 10px;
|
|
608
|
+
color: var(--text-4);
|
|
609
|
+
flex-shrink: 0;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/* ───────────────────────── empty state ───────────────────────── */
|
|
613
|
+
.empty {
|
|
614
|
+
margin: 32px 0;
|
|
615
|
+
padding: 48px 24px;
|
|
616
|
+
border: 1px dashed var(--line);
|
|
617
|
+
border-radius: var(--radius);
|
|
618
|
+
background:
|
|
619
|
+
radial-gradient(60% 40% at 50% 50%, var(--accent-soft) 0%, transparent 70%),
|
|
620
|
+
var(--surface-1);
|
|
621
|
+
display: flex; flex-direction: column; align-items: center; gap: 16px;
|
|
622
|
+
text-align: center;
|
|
623
|
+
}
|
|
624
|
+
.empty-viz {
|
|
625
|
+
width: 200px; height: 60px;
|
|
626
|
+
display: flex; align-items: center; justify-content: center;
|
|
627
|
+
gap: 4px;
|
|
628
|
+
}
|
|
629
|
+
.empty-viz .bar {
|
|
630
|
+
width: 4px;
|
|
631
|
+
background: var(--accent);
|
|
632
|
+
border-radius: 2px;
|
|
633
|
+
opacity: .5;
|
|
634
|
+
animation: heartbeat 1.6s var(--ease) infinite;
|
|
635
|
+
}
|
|
636
|
+
.empty-viz .bar:nth-child(1) { animation-delay: 0.0s; height: 12px; }
|
|
637
|
+
.empty-viz .bar:nth-child(2) { animation-delay: 0.05s; height: 18px; }
|
|
638
|
+
.empty-viz .bar:nth-child(3) { animation-delay: 0.10s; height: 28px; }
|
|
639
|
+
.empty-viz .bar:nth-child(4) { animation-delay: 0.15s; height: 40px; }
|
|
640
|
+
.empty-viz .bar:nth-child(5) { animation-delay: 0.20s; height: 52px; }
|
|
641
|
+
.empty-viz .bar:nth-child(6) { animation-delay: 0.25s; height: 40px; }
|
|
642
|
+
.empty-viz .bar:nth-child(7) { animation-delay: 0.30s; height: 28px; }
|
|
643
|
+
.empty-viz .bar:nth-child(8) { animation-delay: 0.35s; height: 18px; }
|
|
644
|
+
.empty-viz .bar:nth-child(9) { animation-delay: 0.40s; height: 12px; }
|
|
645
|
+
.empty-viz .bar:nth-child(10) { animation-delay: 0.45s; height: 22px; }
|
|
646
|
+
.empty-viz .bar:nth-child(11) { animation-delay: 0.50s; height: 36px; }
|
|
647
|
+
.empty-viz .bar:nth-child(12) { animation-delay: 0.55s; height: 24px; }
|
|
648
|
+
.empty-viz .bar:nth-child(13) { animation-delay: 0.60s; height: 14px; }
|
|
649
|
+
|
|
650
|
+
@keyframes heartbeat {
|
|
651
|
+
0%, 100% { transform: scaleY(0.5); opacity: 0.3; }
|
|
652
|
+
50% { transform: scaleY(1.0); opacity: 0.9; }
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.empty h2 {
|
|
656
|
+
margin: 0;
|
|
657
|
+
font-size: 14px;
|
|
658
|
+
font-weight: 500;
|
|
659
|
+
color: var(--text);
|
|
660
|
+
letter-spacing: -.01em;
|
|
661
|
+
}
|
|
662
|
+
.empty p {
|
|
663
|
+
margin: 0;
|
|
664
|
+
font-size: 12px;
|
|
665
|
+
color: var(--text-3);
|
|
666
|
+
max-width: 320px;
|
|
667
|
+
}
|
|
668
|
+
.empty .hint {
|
|
669
|
+
font-family: var(--mono);
|
|
670
|
+
font-size: 10px;
|
|
671
|
+
color: var(--text-4);
|
|
672
|
+
display: flex; align-items: center; gap: 6px;
|
|
673
|
+
margin-top: 4px;
|
|
674
|
+
}
|
|
675
|
+
.empty .hint kbd {
|
|
676
|
+
font-family: var(--mono);
|
|
677
|
+
padding: 1px 5px;
|
|
678
|
+
border: 1px solid var(--line-strong);
|
|
679
|
+
border-bottom-width: 2px;
|
|
680
|
+
border-radius: 3px;
|
|
681
|
+
color: var(--text-2);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/* ───────────────────────── footer ───────────────────────── */
|
|
685
|
+
.footer {
|
|
686
|
+
padding: 16px 0;
|
|
687
|
+
margin-top: auto;
|
|
688
|
+
border-top: 1px solid var(--line);
|
|
689
|
+
display: flex; align-items: center; gap: 14px;
|
|
690
|
+
font-family: var(--mono);
|
|
691
|
+
font-size: 10px;
|
|
692
|
+
color: var(--text-4);
|
|
693
|
+
}
|
|
694
|
+
.footer .sep { color: var(--text-4); }
|
|
695
|
+
.footer .live { color: var(--accent); }
|
|
696
|
+
|
|
697
|
+
/* ───────────────────────── tweaks panel ───────────────────────── */
|
|
698
|
+
.tweaks {
|
|
699
|
+
position: fixed;
|
|
700
|
+
right: 16px; bottom: 16px;
|
|
701
|
+
width: 280px;
|
|
702
|
+
background: var(--surface-2);
|
|
703
|
+
border: 1px solid var(--line-strong);
|
|
704
|
+
border-radius: 12px;
|
|
705
|
+
box-shadow: 0 24px 60px rgba(0,0,0,.6), 0 0 0 1px rgba(255,255,255,.02);
|
|
706
|
+
z-index: 50;
|
|
707
|
+
overflow: hidden;
|
|
708
|
+
display: none;
|
|
709
|
+
animation: tweaks-in .25s var(--ease);
|
|
710
|
+
}
|
|
711
|
+
.tweaks.open { display: block; }
|
|
712
|
+
@keyframes tweaks-in {
|
|
713
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
714
|
+
to { opacity: 1; transform: none; }
|
|
715
|
+
}
|
|
716
|
+
.tweaks h3 {
|
|
717
|
+
margin: 0;
|
|
718
|
+
padding: 14px 14px 10px;
|
|
719
|
+
font-family: var(--mono);
|
|
720
|
+
font-size: 10px;
|
|
721
|
+
text-transform: uppercase;
|
|
722
|
+
letter-spacing: .14em;
|
|
723
|
+
color: var(--text-3);
|
|
724
|
+
display: flex; align-items: center; gap: 8px;
|
|
725
|
+
border-bottom: 1px solid var(--line);
|
|
726
|
+
}
|
|
727
|
+
.tweaks h3 .close {
|
|
728
|
+
margin-left: auto;
|
|
729
|
+
width: 18px; height: 18px;
|
|
730
|
+
display: grid; place-items: center;
|
|
731
|
+
border-radius: 4px;
|
|
732
|
+
color: var(--text-3);
|
|
733
|
+
}
|
|
734
|
+
.tweaks h3 .close:hover { background: var(--surface-3); color: var(--text); }
|
|
735
|
+
.tweaks-body { padding: 8px 14px 14px; display: flex; flex-direction: column; gap: 14px; }
|
|
736
|
+
.tw-group { display: flex; flex-direction: column; gap: 6px; }
|
|
737
|
+
.tw-label {
|
|
738
|
+
font-family: var(--mono);
|
|
739
|
+
font-size: 10px;
|
|
740
|
+
text-transform: uppercase;
|
|
741
|
+
letter-spacing: .08em;
|
|
742
|
+
color: var(--text-3);
|
|
743
|
+
}
|
|
744
|
+
.seg {
|
|
745
|
+
display: grid;
|
|
746
|
+
grid-auto-flow: column;
|
|
747
|
+
grid-auto-columns: 1fr;
|
|
748
|
+
gap: 2px;
|
|
749
|
+
padding: 2px;
|
|
750
|
+
background: var(--surface-3);
|
|
751
|
+
border-radius: 6px;
|
|
752
|
+
border: 1px solid var(--line);
|
|
753
|
+
}
|
|
754
|
+
.seg button {
|
|
755
|
+
padding: 5px 8px;
|
|
756
|
+
font-size: 11px;
|
|
757
|
+
color: var(--text-2);
|
|
758
|
+
border-radius: 4px;
|
|
759
|
+
transition: all .12s var(--ease);
|
|
760
|
+
font-family: var(--sans);
|
|
761
|
+
}
|
|
762
|
+
.seg button[aria-pressed="true"] {
|
|
763
|
+
background: var(--surface-1);
|
|
764
|
+
color: var(--text);
|
|
765
|
+
box-shadow: 0 1px 0 rgba(255,255,255,.04), 0 1px 4px rgba(0,0,0,.4);
|
|
766
|
+
}
|
|
767
|
+
.seg button:not([aria-pressed="true"]):hover { color: var(--text); }
|
|
768
|
+
|
|
769
|
+
.swatches {
|
|
770
|
+
display: grid;
|
|
771
|
+
grid-template-columns: repeat(6, 1fr);
|
|
772
|
+
gap: 6px;
|
|
773
|
+
}
|
|
774
|
+
.swatches button {
|
|
775
|
+
height: 22px;
|
|
776
|
+
border-radius: 4px;
|
|
777
|
+
border: 1px solid var(--line);
|
|
778
|
+
position: relative;
|
|
779
|
+
}
|
|
780
|
+
.swatches button[aria-pressed="true"] {
|
|
781
|
+
border-color: var(--text);
|
|
782
|
+
outline: 2px solid var(--text);
|
|
783
|
+
outline-offset: -3px;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.tw-actions {
|
|
787
|
+
display: flex; gap: 6px;
|
|
788
|
+
padding-top: 4px;
|
|
789
|
+
border-top: 1px solid var(--line);
|
|
790
|
+
margin-top: 4px;
|
|
791
|
+
}
|
|
792
|
+
.tw-actions button {
|
|
793
|
+
flex: 1;
|
|
794
|
+
padding: 6px;
|
|
795
|
+
font-size: 11px;
|
|
796
|
+
border-radius: 6px;
|
|
797
|
+
border: 1px solid var(--line);
|
|
798
|
+
color: var(--text-2);
|
|
799
|
+
background: var(--surface-1);
|
|
800
|
+
transition: all .12s var(--ease);
|
|
801
|
+
font-family: var(--mono);
|
|
802
|
+
}
|
|
803
|
+
.tw-actions button:hover { color: var(--text); background: var(--surface-3); }
|
|
804
|
+
|
|
805
|
+
/* density-driven log padding handled via --pad-tight above */
|
|
806
|
+
|
|
807
|
+
/* ───────────────────────── reduced motion ───────────────────────── */
|
|
808
|
+
@media (prefers-reduced-motion: reduce) {
|
|
809
|
+
*, *::before, *::after {
|
|
810
|
+
animation-duration: .01ms !important;
|
|
811
|
+
animation-iteration-count: 1 !important;
|
|
812
|
+
transition-duration: .01ms !important;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
448
815
|
</style>
|
|
449
816
|
</head>
|
|
450
817
|
<body>
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
818
|
+
|
|
819
|
+
<div class="app">
|
|
820
|
+
|
|
821
|
+
<!-- HEADER -->
|
|
822
|
+
<header class="header">
|
|
823
|
+
<div class="logo" aria-hidden="true">
|
|
824
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
825
|
+
<path d="M4 7h16M4 12h10M4 17h16"/>
|
|
826
|
+
</svg>
|
|
827
|
+
</div>
|
|
828
|
+
<div class="brand">
|
|
829
|
+
<div class="brand-row">
|
|
830
|
+
<span class="brand-name">kit-mcp</span>
|
|
831
|
+
<span class="brand-meta" id="brand-meta">sidecar</span>
|
|
832
|
+
</div>
|
|
833
|
+
<div class="brand-sub">127.0.0.1:7100</div>
|
|
834
|
+
</div>
|
|
835
|
+
<div class="spacer"></div>
|
|
836
|
+
<div class="conn" id="conn" data-state="on" title="Conexão SSE">
|
|
837
|
+
<span class="dot"></span>
|
|
838
|
+
<span id="conn-label">conectado</span>
|
|
839
|
+
</div>
|
|
456
840
|
</header>
|
|
457
841
|
|
|
842
|
+
<!-- TOOLBAR -->
|
|
458
843
|
<div class="toolbar">
|
|
459
|
-
<
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
844
|
+
<label class="search">
|
|
845
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
846
|
+
<circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/>
|
|
847
|
+
</svg>
|
|
848
|
+
<input id="q" type="text" placeholder="buscar tool, runId, caminho…" autocomplete="off" spellcheck="false" />
|
|
849
|
+
<kbd>/</kbd>
|
|
850
|
+
</label>
|
|
851
|
+
<div class="filter-wrap">
|
|
852
|
+
<button class="iconbtn" id="filter-btn" aria-pressed="false" aria-label="Filtros" title="Filtros">
|
|
853
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
854
|
+
<path d="M3 6h18M6 12h12M10 18h4"/>
|
|
855
|
+
</svg>
|
|
856
|
+
</button>
|
|
857
|
+
<div class="filter-pop" id="filter-pop" role="menu">
|
|
858
|
+
<div class="fp-title">Tipos</div>
|
|
859
|
+
<div class="fp-row" data-on="true" data-filter="run.start"><span class="fp-check">✓</span><span class="fp-glyph">▸</span>Iniciado</div>
|
|
860
|
+
<div class="fp-row" data-on="true" data-filter="progress"><span class="fp-check">✓</span><span class="fp-glyph">⟳</span>Em andamento</div>
|
|
861
|
+
<div class="fp-row" data-on="true" data-filter="milestone"><span class="fp-check">✓</span><span class="fp-glyph">★</span>Marco</div>
|
|
862
|
+
<div class="fp-row" data-on="true" data-filter="run.end"><span class="fp-check">✓</span><span class="fp-glyph">✓</span>Finalizado</div>
|
|
863
|
+
<div class="fp-row" data-on="true" data-filter="error"><span class="fp-check">✓</span><span class="fp-glyph">✕</span>Erro</div>
|
|
864
|
+
</div>
|
|
865
|
+
</div>
|
|
866
|
+
<button class="iconbtn" id="pause-btn" aria-pressed="false" aria-label="Pausar stream" title="Pausar stream">
|
|
867
|
+
<svg id="pause-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
868
|
+
<rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/>
|
|
869
|
+
</svg>
|
|
870
|
+
</button>
|
|
871
|
+
<button class="iconbtn" id="tweaks-btn" aria-pressed="false" aria-label="Tweaks" title="Tweaks">
|
|
872
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
873
|
+
<circle cx="12" cy="12" r="3"/>
|
|
874
|
+
<path d="M12 2v3M12 19v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M2 12h3M19 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1"/>
|
|
875
|
+
</svg>
|
|
876
|
+
</button>
|
|
466
877
|
</div>
|
|
467
878
|
|
|
879
|
+
<!-- ACTIVE + LOG -->
|
|
468
880
|
<main>
|
|
469
|
-
<
|
|
470
|
-
|
|
881
|
+
<section id="active-region" class="active-region" data-count="0" aria-live="polite"></section>
|
|
882
|
+
|
|
883
|
+
<div class="section-head" id="log-head" hidden>
|
|
884
|
+
<span class="section-title">Linha do tempo</span>
|
|
885
|
+
<span class="section-count" id="log-count">0 eventos</span>
|
|
471
886
|
</div>
|
|
472
887
|
|
|
473
|
-
<section
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
888
|
+
<section id="timeline" class="timeline"></section>
|
|
889
|
+
|
|
890
|
+
<section id="empty" class="empty">
|
|
891
|
+
<div class="empty-viz" aria-hidden="true">
|
|
892
|
+
<span class="bar"></span><span class="bar"></span><span class="bar"></span>
|
|
893
|
+
<span class="bar"></span><span class="bar"></span><span class="bar"></span>
|
|
894
|
+
<span class="bar"></span><span class="bar"></span><span class="bar"></span>
|
|
895
|
+
<span class="bar"></span><span class="bar"></span><span class="bar"></span>
|
|
896
|
+
<span class="bar"></span>
|
|
897
|
+
</div>
|
|
898
|
+
<h2>Aguardando o primeiro evento</h2>
|
|
899
|
+
<p>O sidecar está escutando em <span style="font-family:var(--mono);color:var(--text-2)">/events</span>. Rode qualquer comando do kit-mcp e ele aparece aqui ao vivo.</p>
|
|
900
|
+
<div class="hint">
|
|
901
|
+
Tente <kbd>kit sync</kbd> · <kbd>kit gates</kbd>
|
|
477
902
|
</div>
|
|
478
|
-
<div id="active-runs-list"></div>
|
|
479
903
|
</section>
|
|
480
|
-
|
|
481
|
-
<ul class="ev-list" id="events" hidden></ul>
|
|
482
|
-
<div class="empty" id="empty">
|
|
483
|
-
<strong>Aguardando primeiro evento…</strong>
|
|
484
|
-
Rode <code>kit sync install</code>, <code>kit reverse-sync apply</code>, ou
|
|
485
|
-
chame uma ferramenta MCP com <code>autoSpawn: true</code> em outra janela.
|
|
486
|
-
</div>
|
|
487
904
|
</main>
|
|
488
905
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
<span id="
|
|
492
|
-
<span
|
|
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
|
-
const TOOL_LABEL = {
|
|
521
|
-
'sync.install': 'Sincronizando kit',
|
|
522
|
-
'sync.watch': 'Vigiando kit (watch)',
|
|
523
|
-
'reverse-sync.apply': 'Importando edições do IDE',
|
|
524
|
-
'gates.run': 'Executando gate',
|
|
525
|
-
'sidecar': 'Servidor sidecar',
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
const STATUS_LABEL = {
|
|
529
|
-
running: 'em execução',
|
|
530
|
-
done: 'concluído',
|
|
531
|
-
error: 'erro',
|
|
532
|
-
};
|
|
906
|
+
<!-- FOOTER -->
|
|
907
|
+
<footer class="footer">
|
|
908
|
+
<span id="evt-count">0 eventos</span>
|
|
909
|
+
<span class="sep">·</span>
|
|
910
|
+
<span>fonte: <span class="live" id="src-label">ao vivo</span></span>
|
|
911
|
+
<span class="sep">·</span>
|
|
912
|
+
<span id="last-seen">aguardando…</span>
|
|
913
|
+
</footer>
|
|
914
|
+
|
|
915
|
+
</div>
|
|
916
|
+
|
|
917
|
+
<!-- TWEAKS -->
|
|
918
|
+
<aside class="tweaks" id="tweaks" role="dialog" aria-label="Tweaks">
|
|
919
|
+
<h3>
|
|
920
|
+
Tweaks
|
|
921
|
+
<button class="close" id="tweaks-close" aria-label="Fechar">
|
|
922
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
|
923
|
+
</button>
|
|
924
|
+
</h3>
|
|
925
|
+
<div class="tweaks-body">
|
|
926
|
+
<div class="tw-group">
|
|
927
|
+
<span class="tw-label">Acento</span>
|
|
928
|
+
<div class="swatches" id="tw-accent">
|
|
929
|
+
<button data-h="130" style="background:oklch(82% 0.18 130)" aria-label="Lima"></button>
|
|
930
|
+
<button data-h="220" style="background:oklch(78% 0.16 220)" aria-label="Azul"></button>
|
|
931
|
+
<button data-h="290" style="background:oklch(78% 0.18 290)" aria-label="Roxo"></button>
|
|
932
|
+
<button data-h="20" style="background:oklch(78% 0.18 20)" aria-label="Laranja"></button>
|
|
933
|
+
<button data-h="340" style="background:oklch(78% 0.18 340)" aria-label="Magenta"></button>
|
|
934
|
+
<button data-h="180" style="background:oklch(80% 0.14 180)" aria-label="Ciano"></button>
|
|
935
|
+
</div>
|
|
936
|
+
</div>
|
|
533
937
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
938
|
+
<div class="tw-group">
|
|
939
|
+
<span class="tw-label">Densidade</span>
|
|
940
|
+
<div class="seg" id="tw-density">
|
|
941
|
+
<button data-v="compact">Compacta</button>
|
|
942
|
+
<button data-v="normal" aria-pressed="true">Normal</button>
|
|
943
|
+
<button data-v="comfy">Confortável</button>
|
|
944
|
+
</div>
|
|
945
|
+
</div>
|
|
539
946
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
updating: 'atualizando',
|
|
549
|
-
syncing: 'sincronizando',
|
|
550
|
-
'sync': 'sincronizando',
|
|
551
|
-
applying: 'aplicando',
|
|
552
|
-
fetching: 'buscando',
|
|
553
|
-
};
|
|
947
|
+
<div class="tw-group">
|
|
948
|
+
<span class="tw-label">Movimento</span>
|
|
949
|
+
<div class="seg" id="tw-motion">
|
|
950
|
+
<button data-v="subtle">Sutil</button>
|
|
951
|
+
<button data-v="medium" aria-pressed="true">Médio</button>
|
|
952
|
+
<button data-v="rich">Rico</button>
|
|
953
|
+
</div>
|
|
954
|
+
</div>
|
|
554
955
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
// .claude/framework/templates/codebase/x.md → template framework
|
|
565
|
-
// CLAUDE.md → manifesto CLAUDE.md
|
|
566
|
-
function humanizePath(p) {
|
|
567
|
-
if (typeof p !== 'string' || !p.length) return '';
|
|
568
|
-
const norm = p.replace(/\\/g, '/');
|
|
569
|
-
|
|
570
|
-
let m;
|
|
571
|
-
if ((m = norm.match(/(?:\.claude|kit)\/agents\/([^/]+)\.md$/))) return `agente ${m[1]}`;
|
|
572
|
-
if ((m = norm.match(/(?:\.claude|kit)\/commands\/([^/]+)\.md$/))) return `comando ${m[1]}`;
|
|
573
|
-
if ((m = norm.match(/(?:\.claude|kit)\/skills\/([^/]+)/))) return `skill ${m[1]}`;
|
|
574
|
-
if ((m = norm.match(/(?:\.claude|kit)\/framework\//))) return 'framework';
|
|
575
|
-
if ((m = norm.match(/(?:\.claude|kit)\/hooks\//))) return 'hooks';
|
|
576
|
-
if (norm === 'CLAUDE.md' || norm.endsWith('/CLAUDE.md')) return 'manifesto CLAUDE.md';
|
|
577
|
-
if ((m = norm.match(/(?:\.claude|kit)\/([^/]+)\/([^/]+\.md)$/))) return `${m[1]} ${m[2].replace(/\.md$/, '')}`;
|
|
578
|
-
return norm; // fall back to the raw path if no rule matched
|
|
579
|
-
}
|
|
956
|
+
<div class="tw-group">
|
|
957
|
+
<span class="tw-label">Cenário (mock)</span>
|
|
958
|
+
<div class="seg" id="tw-scenario">
|
|
959
|
+
<button data-v="sync" aria-pressed="true">Sync</button>
|
|
960
|
+
<button data-v="multi">Multi</button>
|
|
961
|
+
<button data-v="error">Erro</button>
|
|
962
|
+
<button data-v="idle">Idle</button>
|
|
963
|
+
</div>
|
|
964
|
+
</div>
|
|
580
965
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
966
|
+
<div class="tw-actions">
|
|
967
|
+
<button id="tw-replay">▸ replay</button>
|
|
968
|
+
<button id="tw-clear">limpar</button>
|
|
969
|
+
</div>
|
|
970
|
+
</div>
|
|
971
|
+
</aside>
|
|
972
|
+
|
|
973
|
+
<!-- SVG sprite (in-doc, no external) -->
|
|
974
|
+
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
|
|
975
|
+
<defs>
|
|
976
|
+
<symbol id="i-sync" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
977
|
+
<path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M21 3v5h-5"/>
|
|
978
|
+
<path d="M21 12a9 9 0 0 1-15 6.7L3 16"/><path d="M3 21v-5h5"/>
|
|
979
|
+
</symbol>
|
|
980
|
+
<symbol id="i-reverse" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
981
|
+
<path d="M7 7h10l-3-3"/><path d="M17 17H7l3 3"/>
|
|
982
|
+
</symbol>
|
|
983
|
+
<symbol id="i-gates" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
984
|
+
<rect x="4" y="4" width="16" height="16" rx="2"/><path d="M4 10h16M10 4v16"/>
|
|
985
|
+
</symbol>
|
|
986
|
+
<symbol id="i-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
|
|
987
|
+
<path d="M12 3a9 9 0 1 0 9 9" />
|
|
988
|
+
</symbol>
|
|
989
|
+
<symbol id="i-bolt" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
990
|
+
<path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z"/>
|
|
991
|
+
</symbol>
|
|
992
|
+
</defs>
|
|
993
|
+
</svg>
|
|
594
994
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
995
|
+
<script>
|
|
996
|
+
/* ──────────────────────────────────────────────────────────
|
|
997
|
+
kit-mcp sidecar — prototype
|
|
998
|
+
This is a faithful mock of what the production HTML will do.
|
|
999
|
+
In production, replace the MockSource with a real EventSource('/events').
|
|
1000
|
+
────────────────────────────────────────────────────────── */
|
|
1001
|
+
|
|
1002
|
+
/* ---------- humanize helpers (preserved API) ---------- */
|
|
1003
|
+
const TYPE_LABELS = {
|
|
1004
|
+
"run.start": "INICIADO",
|
|
1005
|
+
"run.end": "FINALIZADO",
|
|
1006
|
+
"tool_invocation":"INVOCADO",
|
|
1007
|
+
"progress": "EM ANDAMENTO",
|
|
1008
|
+
"milestone": "MARCO",
|
|
1009
|
+
"error": "ERRO",
|
|
1010
|
+
"shutdown": "ENCERRADO",
|
|
1011
|
+
};
|
|
1012
|
+
const TOOL_LABELS = {
|
|
1013
|
+
"sync.install": "Sincronizando kit",
|
|
1014
|
+
"sync.preview": "Prévia de sincronização",
|
|
1015
|
+
"reverse.scan": "Escaneando agentes",
|
|
1016
|
+
"reverse.merge": "Mesclando alterações",
|
|
1017
|
+
"gates.run": "Executando gates",
|
|
1018
|
+
"gates.lint": "Lint dos agentes",
|
|
1019
|
+
"kit.list": "Listando kit",
|
|
1020
|
+
};
|
|
1021
|
+
const TOOL_FAMILIES = {
|
|
1022
|
+
"sync.install": "sync",
|
|
1023
|
+
"sync.preview": "sync",
|
|
1024
|
+
"reverse.scan": "reverse",
|
|
1025
|
+
"reverse.merge": "reverse",
|
|
1026
|
+
"gates.run": "gates",
|
|
1027
|
+
"gates.lint": "gates",
|
|
1028
|
+
"kit.list": "sync",
|
|
1029
|
+
};
|
|
1030
|
+
function humanizeEventType(t) { return TYPE_LABELS[t] || t.toUpperCase(); }
|
|
1031
|
+
function humanizeTool(t) { return TOOL_LABELS[t] || t; }
|
|
1032
|
+
function humanizePath(p) {
|
|
1033
|
+
if (!p) return "";
|
|
1034
|
+
// collapse leading paths, keep last 2 segments
|
|
1035
|
+
const parts = p.split("/").filter(Boolean);
|
|
1036
|
+
if (parts.length <= 2) return p;
|
|
1037
|
+
return "…/" + parts.slice(-2).join("/");
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/* ---------- relative time ---------- */
|
|
1041
|
+
function relTime(ms) {
|
|
1042
|
+
const s = Math.floor(ms / 1000);
|
|
1043
|
+
if (s < 5) return "agora";
|
|
1044
|
+
if (s < 60) return `há ${s}s`;
|
|
1045
|
+
const m = Math.floor(s / 60);
|
|
1046
|
+
if (m < 60) return `há ${m}m`;
|
|
1047
|
+
const h = Math.floor(m / 60);
|
|
1048
|
+
if (h < 24) return `há ${h}h`;
|
|
1049
|
+
const d = Math.floor(h / 24);
|
|
1050
|
+
return `há ${d}d`;
|
|
1051
|
+
}
|
|
1052
|
+
function clockTime(ts) {
|
|
1053
|
+
const d = new Date(ts);
|
|
1054
|
+
return `${String(d.getHours()).padStart(2,"0")}:${String(d.getMinutes()).padStart(2,"0")}:${String(d.getSeconds()).padStart(2,"0")}`;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/* ---------- state ---------- */
|
|
1058
|
+
const state = {
|
|
1059
|
+
events: [], // newest first
|
|
1060
|
+
runs: new Map(), // runId → { startTs, tool, lastProgress, lastLabel, ended, ok, current, total }
|
|
1061
|
+
filterText: "",
|
|
1062
|
+
// tool_invocation + shutdown não têm chip de filtro, mas devem aparecer por default
|
|
1063
|
+
filters: new Set(["run.start","progress","milestone","run.end","error","tool_invocation","shutdown"]),
|
|
1064
|
+
paused: false,
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
/* ---------- DOM refs ---------- */
|
|
1068
|
+
const $ = (s) => document.querySelector(s);
|
|
1069
|
+
const els = {
|
|
1070
|
+
active: $("#active-region"),
|
|
1071
|
+
timeline: $("#timeline"),
|
|
1072
|
+
empty: $("#empty"),
|
|
1073
|
+
evtCount: $("#evt-count"),
|
|
1074
|
+
logCount: $("#log-count"),
|
|
1075
|
+
logHead: $("#log-head"),
|
|
1076
|
+
lastSeen: $("#last-seen"),
|
|
1077
|
+
conn: $("#conn"),
|
|
1078
|
+
connLabel: $("#conn-label"),
|
|
1079
|
+
q: $("#q"),
|
|
1080
|
+
pauseBtn: $("#pause-btn"),
|
|
1081
|
+
pauseIcon: $("#pause-icon"),
|
|
1082
|
+
filterBtn: $("#filter-btn"),
|
|
1083
|
+
filterPop: $("#filter-pop"),
|
|
1084
|
+
tweaksBtn: $("#tweaks-btn"),
|
|
1085
|
+
tweaks: $("#tweaks"),
|
|
1086
|
+
tweaksClose: $("#tweaks-close"),
|
|
1087
|
+
srcLabel: $("#src-label"),
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
/* ---------- ingest one event ---------- */
|
|
1091
|
+
function ingest(evt) {
|
|
1092
|
+
if (state.paused) return;
|
|
1093
|
+
state.events.unshift(evt);
|
|
1094
|
+
if (state.events.length > 200) state.events.length = 200;
|
|
1095
|
+
|
|
1096
|
+
// run tracking
|
|
1097
|
+
if (evt.runId) {
|
|
1098
|
+
let run = state.runs.get(evt.runId);
|
|
1099
|
+
if (!run) {
|
|
1100
|
+
run = { runId: evt.runId, startTs: evt.ts, tool: null, lastProgress: 0, lastLabel: "", ended: false, ok: true, current: 0, total: 0, lastTs: evt.ts };
|
|
1101
|
+
state.runs.set(evt.runId, run);
|
|
598
1102
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
clearBtn: $('clear-btn'),
|
|
615
|
-
search: $('search'),
|
|
616
|
-
typeFilters: $('type-filters'),
|
|
617
|
-
footerEvents: $('footer-events'),
|
|
618
|
-
footerPaused: $('footer-paused'),
|
|
619
|
-
footerSource: $('footer-source'),
|
|
620
|
-
activeRuns: $('active-runs'),
|
|
621
|
-
activeRunsList: $('active-runs-list'),
|
|
622
|
-
activeRunsCount: $('active-runs-count'),
|
|
623
|
-
};
|
|
624
|
-
|
|
625
|
-
const state = {
|
|
626
|
-
events: [], // currently rendered events (buffered while paused)
|
|
627
|
-
pausedBuffer: [], // events captured while paused
|
|
628
|
-
paused: false,
|
|
629
|
-
autoscroll: true,
|
|
630
|
-
typeFilter: new Set(EVENT_TYPES), // all enabled
|
|
631
|
-
search: '',
|
|
632
|
-
closedAt: null,
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
// ------- DOM helpers --------------------------------------------------
|
|
636
|
-
|
|
637
|
-
function el(tag, props = {}, kids = []) {
|
|
638
|
-
const e = document.createElement(tag);
|
|
639
|
-
for (const [k, v] of Object.entries(props)) {
|
|
640
|
-
if (k === 'class') e.className = v;
|
|
641
|
-
else if (k === 'data') for (const [dk, dv] of Object.entries(v)) e.dataset[dk] = dv;
|
|
642
|
-
else if (k.startsWith('on')) e.addEventListener(k.slice(2).toLowerCase(), v);
|
|
643
|
-
else if (k === 'text') e.textContent = v;
|
|
644
|
-
else if (v !== undefined && v !== null) e.setAttribute(k, v);
|
|
1103
|
+
run.lastTs = evt.ts;
|
|
1104
|
+
if (evt.type === "run.start") {
|
|
1105
|
+
run.tool = evt.payload?.tool;
|
|
1106
|
+
run.startTs = evt.ts;
|
|
1107
|
+
} else if (evt.type === "progress") {
|
|
1108
|
+
if (typeof evt.payload?.percent === "number") run.lastProgress = evt.payload.percent;
|
|
1109
|
+
if (evt.payload?.label) run.lastLabel = evt.payload.label;
|
|
1110
|
+
if (evt.payload?.current) run.current = evt.payload.current;
|
|
1111
|
+
if (evt.payload?.total) run.total = evt.payload.total;
|
|
1112
|
+
} else if (evt.type === "run.end") {
|
|
1113
|
+
run.ended = true;
|
|
1114
|
+
run.ok = !!evt.payload?.ok;
|
|
1115
|
+
run.lastProgress = run.ok ? 100 : run.lastProgress;
|
|
1116
|
+
} else if (evt.type === "error") {
|
|
1117
|
+
// don't end the run on error event alone; matches server semantics
|
|
645
1118
|
}
|
|
646
|
-
for (const k of kids) if (k) e.appendChild(typeof k === 'string' ? document.createTextNode(k) : k);
|
|
647
|
-
return e;
|
|
648
1119
|
}
|
|
649
1120
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
1121
|
+
render();
|
|
1122
|
+
els.lastSeen.textContent = "último: " + clockTime(evt.ts);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/* ---------- render ---------- */
|
|
1126
|
+
function passesFilter(evt) {
|
|
1127
|
+
if (!state.filters.has(evt.type)) return false;
|
|
1128
|
+
if (!state.filterText) return true;
|
|
1129
|
+
const q = state.filterText.toLowerCase();
|
|
1130
|
+
const blob = JSON.stringify(evt).toLowerCase();
|
|
1131
|
+
return blob.includes(q);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function render() {
|
|
1135
|
+
renderActive();
|
|
1136
|
+
renderTimeline();
|
|
1137
|
+
const total = state.events.length;
|
|
1138
|
+
els.evtCount.textContent = `${total} evento${total === 1 ? "" : "s"}`;
|
|
1139
|
+
els.logCount.textContent = `${total} evento${total === 1 ? "" : "s"}`;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function renderActive() {
|
|
1143
|
+
const active = [...state.runs.values()].filter((r) => !r.ended).sort((a, b) => b.startTs - a.startTs);
|
|
1144
|
+
els.active.dataset.count = active.length;
|
|
1145
|
+
|
|
1146
|
+
// remove cards that are no longer active
|
|
1147
|
+
els.active.querySelectorAll(".run-card").forEach((card) => {
|
|
1148
|
+
const id = card.dataset.runid;
|
|
1149
|
+
if (!active.find((r) => r.runId === id)) card.remove();
|
|
1150
|
+
});
|
|
671
1151
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
1152
|
+
// upsert each active run
|
|
1153
|
+
active.forEach((run) => {
|
|
1154
|
+
let card = els.active.querySelector(`[data-runid="${run.runId}"]`);
|
|
1155
|
+
if (!card) {
|
|
1156
|
+
const wrap = document.createElement("div");
|
|
1157
|
+
wrap.innerHTML = activeCardHtml(run);
|
|
1158
|
+
card = wrap.firstElementChild;
|
|
1159
|
+
card.classList.add("enter");
|
|
1160
|
+
card.addEventListener("animationend", () => card.classList.remove("enter"), { once: true });
|
|
1161
|
+
setTimeout(() => card.classList.remove("enter"), 600);
|
|
1162
|
+
els.active.appendChild(card);
|
|
676
1163
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1164
|
+
updateActiveCard(card, run);
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function updateActiveCard(card, run) {
|
|
1169
|
+
const family = TOOL_FAMILIES[run.tool] || "sync";
|
|
1170
|
+
const percent = Math.max(0, Math.min(100, Math.round(run.lastProgress)));
|
|
1171
|
+
const elapsed = Date.now() - run.startTs;
|
|
1172
|
+
const longRunning = elapsed > 30_000;
|
|
1173
|
+
const stepLabel = humanizePath(run.lastLabel) || "iniciando…";
|
|
1174
|
+
const stepCount = run.total ? `${run.current}/${run.total}` : "";
|
|
1175
|
+
|
|
1176
|
+
const set = (sel, fn) => { const n = card.querySelector(sel); if (n) fn(n); };
|
|
1177
|
+
set(".rc-icon", (n) => { n.dataset.tool = family; n.querySelector("use")?.setAttribute("href", `#i-${family}`); });
|
|
1178
|
+
set(".rc-tool", (n) => { n.textContent = run.tool || "—"; });
|
|
1179
|
+
set(".rc-title", (n) => { n.textContent = humanizeTool(run.tool || "—"); });
|
|
1180
|
+
set(".rc-bar-fill", (n) => { n.style.width = percent + "%"; });
|
|
1181
|
+
set(".rc-pct", (n) => { n.textContent = percent + "%"; });
|
|
1182
|
+
set(".rc-step-text", (n) => { if (n.textContent !== stepLabel) n.textContent = stepLabel; });
|
|
1183
|
+
set(".rc-step-count", (n) => { n.textContent = stepCount; });
|
|
1184
|
+
set(".rc-elapsed-val", (n) => { n.textContent = formatElapsed(elapsed); });
|
|
1185
|
+
set(".rc-elapsed .em", (n) => n.classList.toggle("warn", longRunning));
|
|
1186
|
+
set(".rc-runid", (n) => { n.textContent = "id " + run.runId.slice(0, 8); });
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function activeCardHtml(run) {
|
|
1190
|
+
const family = TOOL_FAMILIES[run.tool] || "sync";
|
|
1191
|
+
const iconHref = `#i-${family}`;
|
|
1192
|
+
const title = humanizeTool(run.tool || "—");
|
|
1193
|
+
const stepLabel = humanizePath(run.lastLabel) || "iniciando…";
|
|
1194
|
+
const percent = Math.max(0, Math.min(100, Math.round(run.lastProgress)));
|
|
1195
|
+
const elapsed = Date.now() - run.startTs;
|
|
1196
|
+
const longRunning = elapsed > 30_000;
|
|
1197
|
+
const stepCount = run.total ? `${run.current}/${run.total}` : "";
|
|
1198
|
+
return `
|
|
1199
|
+
<article class="run-card" data-runid="${run.runId}">
|
|
1200
|
+
<div class="rc-head">
|
|
1201
|
+
<div class="rc-icon" data-tool="${family}"><svg><use href="${iconHref}"/></svg></div>
|
|
1202
|
+
<div class="rc-title-block">
|
|
1203
|
+
<div class="rc-tool">${run.tool || "—"}</div>
|
|
1204
|
+
<div class="rc-title">${title}</div>
|
|
1205
|
+
</div>
|
|
1206
|
+
<div class="rc-elapsed">
|
|
1207
|
+
<span class="em ${longRunning ? "warn" : ""}"><span class="rc-elapsed-val">${formatElapsed(elapsed)}</span></span>
|
|
1208
|
+
<small>decorrido</small>
|
|
1209
|
+
</div>
|
|
1210
|
+
</div>
|
|
680
1211
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const summary = el('summary', {}, [
|
|
686
|
-
el('span', { class: 'label', text: eventLabel(evt) }),
|
|
687
|
-
el('span', { class: 'meta', text: eventMeta(evt) }),
|
|
688
|
-
]);
|
|
689
|
-
const pre = el('pre', { text: JSON.stringify(evt.payload ?? null, null, 2) });
|
|
690
|
-
const details = el('details', {}, [summary, pre]);
|
|
691
|
-
const body = el('div', { class: 'ev-body' }, [details]);
|
|
692
|
-
return el('li', { class: 'ev-row', data: { type: evt.type, label: eventLabel(evt).toLowerCase() } }, [time, badge, body]);
|
|
693
|
-
}
|
|
1212
|
+
<div class="rc-progress">
|
|
1213
|
+
<div class="rc-bar"><div class="rc-bar-fill" style="width:${percent}%"></div></div>
|
|
1214
|
+
<div class="rc-pct">${percent}%</div>
|
|
1215
|
+
</div>
|
|
694
1216
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
}
|
|
701
|
-
return true;
|
|
702
|
-
}
|
|
1217
|
+
<div class="rc-step">
|
|
1218
|
+
<span class="glyph"><svg><use href="#i-spin"/></svg></span>
|
|
1219
|
+
<span class="rc-step-text">${escapeHtml(stepLabel)}</span>
|
|
1220
|
+
${stepCount ? `<span class="rc-step-count">${stepCount}</span>` : ""}
|
|
1221
|
+
</div>
|
|
703
1222
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
1223
|
+
<div class="rc-foot">
|
|
1224
|
+
<span class="rc-runid">id ${run.runId.slice(0, 8)}</span>
|
|
1225
|
+
<span class="sep">·</span>
|
|
1226
|
+
<span>${run.tool || ""}</span>
|
|
1227
|
+
</div>
|
|
1228
|
+
</article>
|
|
1229
|
+
`;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function formatElapsed(ms) {
|
|
1233
|
+
const s = Math.floor(ms / 1000);
|
|
1234
|
+
if (s < 60) return `${s}s`;
|
|
1235
|
+
const m = Math.floor(s / 60);
|
|
1236
|
+
const r = s % 60;
|
|
1237
|
+
return `${m}m ${String(r).padStart(2,"0")}s`;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
function renderTimeline() {
|
|
1241
|
+
const visible = state.events.filter(passesFilter);
|
|
1242
|
+
if (state.events.length === 0) {
|
|
1243
|
+
els.empty.style.display = "";
|
|
1244
|
+
els.timeline.innerHTML = "";
|
|
1245
|
+
els.logHead.hidden = true;
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
els.empty.style.display = "none";
|
|
1249
|
+
els.logHead.hidden = false;
|
|
1250
|
+
|
|
1251
|
+
// diff by event ts+type+runId — only animate truly new rows
|
|
1252
|
+
const keyOf = (e) => `${e.ts}|${e.type}|${e.runId || ""}`;
|
|
1253
|
+
const seen = state._seenKeys || (state._seenKeys = new Set());
|
|
1254
|
+
const existing = new Map();
|
|
1255
|
+
els.timeline.querySelectorAll(".tl-row").forEach((r) => existing.set(r.dataset.key, r));
|
|
1256
|
+
|
|
1257
|
+
// Build a desired ordered list. Insert new rows at their correct position
|
|
1258
|
+
// WITHOUT detaching existing rows — that would retrigger CSS animations.
|
|
1259
|
+
let prevNode = null;
|
|
1260
|
+
visible.forEach((evt, idx) => {
|
|
1261
|
+
const key = keyOf(evt);
|
|
1262
|
+
let row = existing.get(key);
|
|
1263
|
+
if (row) {
|
|
1264
|
+
existing.delete(key);
|
|
1265
|
+
// reorder only if not already in the right place
|
|
1266
|
+
const expectedNext = prevNode ? prevNode.nextSibling : els.timeline.firstChild;
|
|
1267
|
+
if (row !== expectedNext) {
|
|
1268
|
+
els.timeline.insertBefore(row, expectedNext);
|
|
1269
|
+
}
|
|
719
1270
|
} else {
|
|
720
|
-
|
|
721
|
-
|
|
1271
|
+
const wrap = document.createElement("div");
|
|
1272
|
+
wrap.innerHTML = rowHtml(evt, idx, visible[idx - 1]);
|
|
1273
|
+
row = wrap.firstElementChild;
|
|
1274
|
+
row.dataset.key = key;
|
|
1275
|
+
if (!seen.has(key)) {
|
|
1276
|
+
row.classList.add("enter", "is-new");
|
|
1277
|
+
seen.add(key);
|
|
1278
|
+
// strip the one-shot class once the animation completes,
|
|
1279
|
+
// so reattachment / reorder won't restart it.
|
|
1280
|
+
row.addEventListener("animationend", () => row.classList.remove("enter"), { once: true });
|
|
1281
|
+
setTimeout(() => row.classList.remove("enter"), 600);
|
|
1282
|
+
}
|
|
1283
|
+
els.timeline.insertBefore(row, prevNode ? prevNode.nextSibling : els.timeline.firstChild);
|
|
722
1284
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1285
|
+
const grouped = !!(visible[idx - 1] && visible[idx - 1].runId === evt.runId);
|
|
1286
|
+
row.dataset.grouped = grouped;
|
|
1287
|
+
prevNode = row;
|
|
1288
|
+
});
|
|
1289
|
+
// remove any leftover rows that no longer match
|
|
1290
|
+
existing.forEach((r) => r.remove());
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
function rowHtml(evt, idx, prev) {
|
|
1294
|
+
const grouped = !!(prev && prev.runId === evt.runId);
|
|
1295
|
+
const ok = evt.payload?.ok;
|
|
1296
|
+
const time = clockTime(evt.ts);
|
|
1297
|
+
const rel = relTime(Date.now() - evt.ts);
|
|
1298
|
+
const badge = humanizeEventType(evt.type);
|
|
1299
|
+
const isNew = (Date.now() - evt.ts) < 1500;
|
|
1300
|
+
let msg = "";
|
|
1301
|
+
switch (evt.type) {
|
|
1302
|
+
case "run.start": {
|
|
1303
|
+
msg = `<strong>${escapeHtml(humanizeTool(evt.payload?.tool))}</strong> <span class="ident">${escapeHtml(evt.payload?.tool || "")}</span>${evt.payload?.target ? ` <span class="arrow">→</span> <span class="ident">${escapeHtml(evt.payload.target)}</span>` : ""}`;
|
|
1304
|
+
break;
|
|
736
1305
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
// happening NOW" and shouldn't be affected by the pause toggle below.
|
|
742
|
-
upsertActiveRun(evt);
|
|
743
|
-
|
|
744
|
-
if (state.paused) {
|
|
745
|
-
state.pausedBuffer.push(evt);
|
|
746
|
-
dom.footerPaused.hidden = false;
|
|
747
|
-
dom.footerPaused.textContent = `pausado: ${state.pausedBuffer.length} em fila`;
|
|
748
|
-
return;
|
|
1306
|
+
case "run.end": {
|
|
1307
|
+
const dur = evt.payload?.duration_ms;
|
|
1308
|
+
msg = `<strong>${escapeHtml(humanizeTool(evt.payload?.tool))}</strong> ${ok ? "concluído" : "falhou"}${dur ? ` <span class="ident">${(dur/1000).toFixed(2)}s</span>` : ""}`;
|
|
1309
|
+
break;
|
|
749
1310
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1311
|
+
case "progress": {
|
|
1312
|
+
const pct = typeof evt.payload?.percent === "number" ? `${Math.round(evt.payload.percent)}%` : "";
|
|
1313
|
+
const lbl = evt.payload?.label || "";
|
|
1314
|
+
const ct = evt.payload?.current && evt.payload?.total ? ` <span class="ident">${evt.payload.current}/${evt.payload.total}</span>` : "";
|
|
1315
|
+
msg = `${pct ? `<strong>${pct}</strong> ` : ""}<span class="path">${escapeHtml(humanizePath(lbl))}</span>${ct}`;
|
|
1316
|
+
break;
|
|
753
1317
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
// Maintain a Map<runId, ActiveRun> that tracks currently-executing tools.
|
|
758
|
-
// Updated from run.start / progress / run.end / error events that share a
|
|
759
|
-
// runId. Renders as cards above the event log so the user sees CURRENT
|
|
760
|
-
// state at a glance instead of having to scan the chronological feed.
|
|
761
|
-
|
|
762
|
-
const activeRuns = new Map(); // runId -> {tool, label, percent, current, total, startedAt, status, pendingRemove}
|
|
763
|
-
const fadeTimers = new Map(); // runId -> setTimeout handle
|
|
764
|
-
|
|
765
|
-
function upsertActiveRun(evt) {
|
|
766
|
-
if (!evt.runId) return;
|
|
767
|
-
const id = evt.runId;
|
|
768
|
-
const p = evt.payload || {};
|
|
769
|
-
let run = activeRuns.get(id);
|
|
770
|
-
|
|
771
|
-
if (evt.type === 'run.start') {
|
|
772
|
-
// Cancel any pending fade-out for a stale entry with the same id (rare).
|
|
773
|
-
if (fadeTimers.has(id)) { clearTimeout(fadeTimers.get(id)); fadeTimers.delete(id); }
|
|
774
|
-
activeRuns.set(id, {
|
|
775
|
-
runId: id,
|
|
776
|
-
tool: p.tool || p.server || 'run',
|
|
777
|
-
label: 'iniciando…',
|
|
778
|
-
percent: 0,
|
|
779
|
-
current: null,
|
|
780
|
-
total: null,
|
|
781
|
-
startedAt: evt.ts,
|
|
782
|
-
status: 'running',
|
|
783
|
-
});
|
|
784
|
-
} else if (evt.type === 'progress' && run && run.status === 'running') {
|
|
785
|
-
if (typeof p.percent === 'number') run.percent = clampPercent(p.percent);
|
|
786
|
-
if (typeof p.current === 'number') run.current = p.current;
|
|
787
|
-
if (typeof p.total === 'number') run.total = p.total;
|
|
788
|
-
if (typeof p.label === 'string' && p.label.length > 0) run.label = humanizeLabel(p.label);
|
|
789
|
-
// If the wrapper sent current/total but no percent, derive it.
|
|
790
|
-
if (typeof p.percent !== 'number' && typeof p.current === 'number' && typeof p.total === 'number' && p.total > 0) {
|
|
791
|
-
run.percent = clampPercent(Math.round((p.current / p.total) * 100));
|
|
792
|
-
}
|
|
793
|
-
} else if (evt.type === 'tool_invocation' && run && run.status === 'running') {
|
|
794
|
-
// tool_invocation events refine the title if it arrived after run.start.
|
|
795
|
-
if (typeof p.tool === 'string') run.tool = p.tool;
|
|
796
|
-
} else if (evt.type === 'run.end' && run) {
|
|
797
|
-
run.status = (p && p.ok === false) ? 'error' : 'done';
|
|
798
|
-
run.percent = run.status === 'done' ? 100 : run.percent;
|
|
799
|
-
run.label = run.status === 'done' ? 'concluído com sucesso' : (p?.message || 'falhou');
|
|
800
|
-
// Fade the card out a few seconds after completion so the user sees
|
|
801
|
-
// the 100% and "done" before it disappears.
|
|
802
|
-
const t = setTimeout(() => {
|
|
803
|
-
activeRuns.delete(id);
|
|
804
|
-
fadeTimers.delete(id);
|
|
805
|
-
renderActiveRuns();
|
|
806
|
-
}, 4000);
|
|
807
|
-
fadeTimers.set(id, t);
|
|
808
|
-
} else if (evt.type === 'error' && run && run.status === 'running') {
|
|
809
|
-
run.status = 'error';
|
|
810
|
-
run.label = p?.message || 'error';
|
|
811
|
-
// Errors stay visible longer (8s) so the user has time to read.
|
|
812
|
-
const t = setTimeout(() => {
|
|
813
|
-
activeRuns.delete(id);
|
|
814
|
-
fadeTimers.delete(id);
|
|
815
|
-
renderActiveRuns();
|
|
816
|
-
}, 8000);
|
|
817
|
-
fadeTimers.set(id, t);
|
|
1318
|
+
case "milestone": {
|
|
1319
|
+
msg = `<strong>${escapeHtml(evt.payload?.name || "")}</strong>`;
|
|
1320
|
+
break;
|
|
818
1321
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
function clampPercent(n) {
|
|
823
|
-
if (!Number.isFinite(n)) return 0;
|
|
824
|
-
return Math.max(0, Math.min(100, n));
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
function fmtElapsed(startTs) {
|
|
828
|
-
const sec = Math.max(0, Math.round((Date.now() - startTs) / 1000));
|
|
829
|
-
if (sec < 60) return `${sec}s`;
|
|
830
|
-
const m = Math.floor(sec / 60);
|
|
831
|
-
const s = sec % 60;
|
|
832
|
-
return `${m}m ${s.toString().padStart(2, '0')}s`;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
function renderActiveRuns() {
|
|
836
|
-
const runs = [...activeRuns.values()].sort((a, b) => a.startedAt - b.startedAt);
|
|
837
|
-
if (runs.length === 0) {
|
|
838
|
-
dom.activeRuns.hidden = true;
|
|
839
|
-
dom.activeRunsList.replaceChildren();
|
|
840
|
-
return;
|
|
1322
|
+
case "error": {
|
|
1323
|
+
msg = `<strong>${escapeHtml(evt.payload?.message || "erro")}</strong>${evt.payload?.code ? ` <span class="ident">${evt.payload.code}</span>` : ""}`;
|
|
1324
|
+
break;
|
|
841
1325
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
// Use a stable key (runId) so we update existing cards in place rather than
|
|
846
|
-
// recreating them (preserves the CSS transition on the progress bar width).
|
|
847
|
-
const existing = new Map();
|
|
848
|
-
for (const child of dom.activeRunsList.children) {
|
|
849
|
-
existing.set(child.dataset.runid, child);
|
|
1326
|
+
case "tool_invocation": {
|
|
1327
|
+
msg = `<span class="ident">${escapeHtml(evt.payload?.tool || "")}</span>`;
|
|
1328
|
+
break;
|
|
850
1329
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
if (activeRuns.size === 0) return;
|
|
887
|
-
for (const card of dom.activeRunsList.children) {
|
|
888
|
-
const id = card.dataset.runid;
|
|
889
|
-
const run = activeRuns.get(id);
|
|
890
|
-
if (run) card.querySelector('.elapsed').textContent = `há ${fmtElapsed(run.startedAt)}`;
|
|
891
|
-
}
|
|
892
|
-
}, 1000);
|
|
893
|
-
|
|
894
|
-
function flushPaused() {
|
|
895
|
-
for (const evt of state.pausedBuffer) pushVisibleEvent(evt);
|
|
896
|
-
state.pausedBuffer.length = 0;
|
|
897
|
-
dom.footerPaused.hidden = true;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
// ------- Filter UI ----------------------------------------------------
|
|
901
|
-
|
|
902
|
-
for (const t of EVENT_TYPES) {
|
|
903
|
-
const cb = el('input', { type: 'checkbox', checked: '' });
|
|
904
|
-
cb.checked = true;
|
|
905
|
-
cb.addEventListener('change', () => {
|
|
906
|
-
if (cb.checked) state.typeFilter.add(t);
|
|
907
|
-
else state.typeFilter.delete(t);
|
|
908
|
-
applyFilter();
|
|
909
|
-
});
|
|
910
|
-
// Show humanized text but keep the data-type as the raw event name so
|
|
911
|
-
// power users (and tests) can still target the underlying value.
|
|
912
|
-
const lbl = el('label', { data: { type: t } }, [cb, document.createTextNode(humanizeEventType(t))]);
|
|
913
|
-
dom.typeFilters.appendChild(lbl);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
dom.search.addEventListener('input', () => {
|
|
917
|
-
state.search = dom.search.value;
|
|
918
|
-
applyFilter();
|
|
1330
|
+
default:
|
|
1331
|
+
msg = "";
|
|
1332
|
+
}
|
|
1333
|
+
return `
|
|
1334
|
+
<div class="tl-row" data-type="${evt.type}" data-ok="${ok}" data-grouped="${grouped}">
|
|
1335
|
+
<div class="tl-time" title="${time}">${rel}</div>
|
|
1336
|
+
<div class="tl-rail"><div class="tl-node"></div></div>
|
|
1337
|
+
<div class="tl-content">
|
|
1338
|
+
<span class="tl-badge">${badge}</span>
|
|
1339
|
+
<span class="tl-msg">${msg}</span>
|
|
1340
|
+
${evt.runId ? `<span class="tl-runid">${evt.runId.slice(0,6)}</span>` : ""}
|
|
1341
|
+
</div>
|
|
1342
|
+
</div>
|
|
1343
|
+
`;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function escapeHtml(s) {
|
|
1347
|
+
return String(s ?? "").replace(/[&<>"']/g, (c) => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/* ---------- elapsed ticker ---------- */
|
|
1351
|
+
setInterval(() => {
|
|
1352
|
+
for (const run of state.runs.values()) {
|
|
1353
|
+
if (run.ended) continue;
|
|
1354
|
+
const el = els.active.querySelector(`[data-runid="${run.runId}"] .rc-elapsed-val`);
|
|
1355
|
+
if (!el) continue;
|
|
1356
|
+
const elapsed = Date.now() - run.startTs;
|
|
1357
|
+
el.textContent = formatElapsed(elapsed);
|
|
1358
|
+
const wrap = el.closest(".em");
|
|
1359
|
+
if (wrap) wrap.classList.toggle("warn", elapsed > 30_000);
|
|
1360
|
+
}
|
|
1361
|
+
// refresh relative time on log rows (cheap: only update visible)
|
|
1362
|
+
els.timeline.querySelectorAll(".tl-time").forEach((node, i) => {
|
|
1363
|
+
const evt = state.events.filter(passesFilter)[i];
|
|
1364
|
+
if (evt) node.textContent = relTime(Date.now() - evt.ts);
|
|
919
1365
|
});
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1366
|
+
}, 1000);
|
|
1367
|
+
|
|
1368
|
+
/* ───────────────────── mock event generator ───────────────────── */
|
|
1369
|
+
let mockTimers = [];
|
|
1370
|
+
function clearMock() {
|
|
1371
|
+
mockTimers.forEach(clearTimeout);
|
|
1372
|
+
mockTimers = [];
|
|
1373
|
+
}
|
|
1374
|
+
function later(ms, fn) { mockTimers.push(setTimeout(fn, ms)); }
|
|
1375
|
+
|
|
1376
|
+
function genRunId() { return Math.random().toString(36).slice(2, 10) + Math.random().toString(36).slice(2, 8); }
|
|
1377
|
+
|
|
1378
|
+
function scenarioSync(delay = 0) {
|
|
1379
|
+
const runId = genRunId();
|
|
1380
|
+
const tool = "sync.install";
|
|
1381
|
+
const start = Date.now() + delay;
|
|
1382
|
+
const target = "claude-code";
|
|
1383
|
+
later(delay, () => ingest({ type: "run.start", ts: start, runId, payload: { tool, target } }));
|
|
1384
|
+
|
|
1385
|
+
const total = 19;
|
|
1386
|
+
const labels = [
|
|
1387
|
+
"lendo agente planner",
|
|
1388
|
+
"analisando dependências",
|
|
1389
|
+
"projetando framework",
|
|
1390
|
+
"compilando templates",
|
|
1391
|
+
"writing .claude/agents/planner.md",
|
|
1392
|
+
"writing .claude/agents/builder.md",
|
|
1393
|
+
"writing .claude/agents/reviewer.md",
|
|
1394
|
+
"writing .claude/commands/sync.md",
|
|
1395
|
+
"validando schema",
|
|
1396
|
+
"merging com kit base",
|
|
1397
|
+
"writing .claude/agents/coordinator.md",
|
|
1398
|
+
"writing .claude/agents/specialist.md",
|
|
1399
|
+
"configurando hooks",
|
|
1400
|
+
"writing .claude/agents/architect.md",
|
|
1401
|
+
"writing .claude/agents/qa.md",
|
|
1402
|
+
"writing .claude/agents/docs.md",
|
|
1403
|
+
"selando manifest",
|
|
1404
|
+
"rodando linter final",
|
|
1405
|
+
"gravando metadata",
|
|
1406
|
+
];
|
|
1407
|
+
for (let i = 0; i < total; i++) {
|
|
1408
|
+
const t = delay + 220 + i * 540;
|
|
1409
|
+
later(t, () => ingest({
|
|
1410
|
+
type: "progress", ts: Date.now(), runId,
|
|
1411
|
+
payload: { percent: Math.round(((i + 1) / total) * 100), label: labels[i], current: i + 1, total, kind: "fs" }
|
|
1412
|
+
}));
|
|
1413
|
+
}
|
|
1414
|
+
later(delay + 220 + total * 540 + 100, () => ingest({
|
|
1415
|
+
type: "milestone", ts: Date.now(), runId, payload: { name: `✓ ${total} agentes projetados` }
|
|
1416
|
+
}));
|
|
1417
|
+
later(delay + 220 + total * 540 + 600, () => ingest({
|
|
1418
|
+
type: "run.end", ts: Date.now(), runId, payload: { tool, ok: true, duration_ms: 220 + total * 540 + 400 }
|
|
1419
|
+
}));
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function scenarioMulti() {
|
|
1423
|
+
scenarioSync(0);
|
|
1424
|
+
// gates run starting in parallel
|
|
1425
|
+
const runId = genRunId();
|
|
1426
|
+
later(800, () => ingest({ type: "run.start", ts: Date.now(), runId, payload: { tool: "gates.run", target: "claude-code" } }));
|
|
1427
|
+
const stages = ["lint", "typecheck", "schema", "smoke"];
|
|
1428
|
+
stages.forEach((s, i) => {
|
|
1429
|
+
later(800 + (i + 1) * 1100, () => ingest({
|
|
1430
|
+
type: "progress", ts: Date.now(), runId,
|
|
1431
|
+
payload: { percent: Math.round(((i + 1) / stages.length) * 100), label: `gate: ${s}`, current: i + 1, total: stages.length, kind: "task" }
|
|
1432
|
+
}));
|
|
926
1433
|
});
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1434
|
+
later(800 + stages.length * 1100 + 300, () => ingest({
|
|
1435
|
+
type: "run.end", ts: Date.now(), runId, payload: { tool: "gates.run", ok: true, duration_ms: stages.length * 1100 }
|
|
1436
|
+
}));
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
function scenarioError() {
|
|
1440
|
+
const runId = genRunId();
|
|
1441
|
+
later(0, () => ingest({ type: "run.start", ts: Date.now(), runId, payload: { tool: "reverse.scan", target: "cursor" } }));
|
|
1442
|
+
later(400, () => ingest({ type: "progress", ts: Date.now(), runId, payload: { percent: 18, label: "lendo .cursor/rules/", current: 2, total: 11, kind: "fs" } }));
|
|
1443
|
+
later(900, () => ingest({ type: "progress", ts: Date.now(), runId, payload: { percent: 38, label: "scanning .cursor/rules/architecture.mdc", current: 4, total: 11, kind: "fs" } }));
|
|
1444
|
+
later(1500, () => ingest({ type: "error", ts: Date.now(), runId, payload: { message: "ENOENT: file not found", code: "ENOENT" } }));
|
|
1445
|
+
later(1700, () => ingest({ type: "run.end", ts: Date.now(), runId, payload: { tool: "reverse.scan", ok: false, duration_ms: 1700 } }));
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function scenarioIdle() {
|
|
1449
|
+
/* nothing */
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
let currentScenario = "sync";
|
|
1453
|
+
function runScenario(name) {
|
|
1454
|
+
currentScenario = name;
|
|
1455
|
+
if (name === "sync") scenarioSync();
|
|
1456
|
+
else if (name === "multi") scenarioMulti();
|
|
1457
|
+
else if (name === "error") scenarioError();
|
|
1458
|
+
else if (name === "idle") scenarioIdle();
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
function clearAll() {
|
|
1462
|
+
clearMock();
|
|
1463
|
+
state.events = [];
|
|
1464
|
+
state.runs.clear();
|
|
1465
|
+
state._seenKeys = new Set();
|
|
1466
|
+
els.active.innerHTML = "";
|
|
1467
|
+
render();
|
|
1468
|
+
els.lastSeen.textContent = "aguardando…";
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/* ───────────────────── tweaks wiring ───────────────────── */
|
|
1472
|
+
els.tweaksBtn.addEventListener("click", () => {
|
|
1473
|
+
const open = els.tweaks.classList.toggle("open");
|
|
1474
|
+
els.tweaksBtn.setAttribute("aria-pressed", open);
|
|
1475
|
+
});
|
|
1476
|
+
els.tweaksClose.addEventListener("click", () => {
|
|
1477
|
+
els.tweaks.classList.remove("open");
|
|
1478
|
+
els.tweaksBtn.setAttribute("aria-pressed", "false");
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
document.querySelectorAll("#tw-accent button").forEach((b) => {
|
|
1482
|
+
b.addEventListener("click", () => {
|
|
1483
|
+
document.querySelectorAll("#tw-accent button").forEach(x => x.setAttribute("aria-pressed","false"));
|
|
1484
|
+
b.setAttribute("aria-pressed","true");
|
|
1485
|
+
document.documentElement.style.setProperty("--accent-h", b.dataset.h);
|
|
931
1486
|
});
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1487
|
+
});
|
|
1488
|
+
document.querySelector('#tw-accent button[data-h="130"]').setAttribute("aria-pressed","true");
|
|
1489
|
+
|
|
1490
|
+
function wireSeg(rootSel, attr, fn) {
|
|
1491
|
+
document.querySelectorAll(`${rootSel} button`).forEach((b) => {
|
|
1492
|
+
b.addEventListener("click", () => {
|
|
1493
|
+
document.querySelectorAll(`${rootSel} button`).forEach(x => x.setAttribute("aria-pressed","false"));
|
|
1494
|
+
b.setAttribute("aria-pressed","true");
|
|
1495
|
+
fn(b.dataset.v);
|
|
1496
|
+
});
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
wireSeg("#tw-density", "v", (v) => document.documentElement.dataset.density = v);
|
|
1500
|
+
wireSeg("#tw-motion", "v", (v) => document.documentElement.dataset.motion = v);
|
|
1501
|
+
wireSeg("#tw-scenario","v", (v) => { clearAll(); runScenario(v); });
|
|
1502
|
+
|
|
1503
|
+
document.documentElement.dataset.density = "normal";
|
|
1504
|
+
document.documentElement.dataset.motion = "medium";
|
|
1505
|
+
|
|
1506
|
+
document.getElementById("tw-replay").addEventListener("click", () => { clearAll(); runScenario(currentScenario); });
|
|
1507
|
+
document.getElementById("tw-clear").addEventListener("click", clearAll);
|
|
1508
|
+
|
|
1509
|
+
/* ───────────────────── toolbar wiring ───────────────────── */
|
|
1510
|
+
els.q.addEventListener("input", (e) => { state.filterText = e.target.value.trim(); render(); });
|
|
1511
|
+
document.addEventListener("keydown", (e) => {
|
|
1512
|
+
if (e.key === "/" && document.activeElement !== els.q) {
|
|
1513
|
+
e.preventDefault();
|
|
1514
|
+
els.q.focus();
|
|
1515
|
+
}
|
|
1516
|
+
if (e.key === "Escape" && document.activeElement === els.q) {
|
|
1517
|
+
els.q.value = ""; state.filterText = ""; els.q.blur(); render();
|
|
1518
|
+
}
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
els.filterBtn.addEventListener("click", (e) => {
|
|
1522
|
+
e.stopPropagation();
|
|
1523
|
+
const open = els.filterPop.classList.toggle("open");
|
|
1524
|
+
els.filterBtn.setAttribute("aria-pressed", open);
|
|
1525
|
+
});
|
|
1526
|
+
document.addEventListener("click", (e) => {
|
|
1527
|
+
if (!els.filterPop.contains(e.target) && e.target !== els.filterBtn) {
|
|
1528
|
+
els.filterPop.classList.remove("open");
|
|
1529
|
+
els.filterBtn.setAttribute("aria-pressed", "false");
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
els.filterPop.querySelectorAll(".fp-row").forEach((row) => {
|
|
1533
|
+
row.addEventListener("click", () => {
|
|
1534
|
+
const f = row.dataset.filter;
|
|
1535
|
+
const on = row.dataset.on === "true";
|
|
1536
|
+
row.dataset.on = on ? "false" : "true";
|
|
1537
|
+
if (on) state.filters.delete(f); else state.filters.add(f);
|
|
1538
|
+
render();
|
|
937
1539
|
});
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1542
|
+
els.pauseBtn.addEventListener("click", () => {
|
|
1543
|
+
state.paused = !state.paused;
|
|
1544
|
+
els.pauseBtn.setAttribute("aria-pressed", state.paused);
|
|
1545
|
+
els.pauseIcon.innerHTML = state.paused
|
|
1546
|
+
? '<polygon points="6,5 19,12 6,19" fill="currentColor"/>'
|
|
1547
|
+
: '<rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/>';
|
|
1548
|
+
els.conn.dataset.state = state.paused ? "off" : "on";
|
|
1549
|
+
els.connLabel.textContent = state.paused ? "pausado" : "conectado";
|
|
1550
|
+
els.srcLabel.textContent = state.paused ? "pausado" : "ao vivo";
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
/* ───────────────────── real source: SSE + /state hydrate ───────────────────── */
|
|
1554
|
+
/* The prototype above keeps mock scenarios available via the tweaks panel. In
|
|
1555
|
+
production we connect to /events for the live feed and to /state to backfill
|
|
1556
|
+
the ring buffer on first paint and after reconnect. */
|
|
1557
|
+
|
|
1558
|
+
let evtSource = null;
|
|
1559
|
+
let lastConnectedAt = 0;
|
|
1560
|
+
|
|
1561
|
+
function applyConnState(s) {
|
|
1562
|
+
// s ∈ { 'connecting', 'open', 'closed', 'paused', 'shutdown' }
|
|
1563
|
+
if (s === "open") {
|
|
1564
|
+
els.conn.dataset.state = "on";
|
|
1565
|
+
els.connLabel.textContent = "conectado";
|
|
1566
|
+
if (els.srcLabel) els.srcLabel.textContent = "ao vivo";
|
|
1567
|
+
} else if (s === "connecting") {
|
|
1568
|
+
els.conn.dataset.state = "off";
|
|
1569
|
+
els.connLabel.textContent = "conectando";
|
|
1570
|
+
if (els.srcLabel) els.srcLabel.textContent = "conectando";
|
|
1571
|
+
} else if (s === "closed") {
|
|
1572
|
+
els.conn.dataset.state = "off";
|
|
1573
|
+
els.connLabel.textContent = "desconectado";
|
|
1574
|
+
if (els.srcLabel) els.srcLabel.textContent = "offline";
|
|
1575
|
+
} else if (s === "shutdown") {
|
|
1576
|
+
els.conn.dataset.state = "off";
|
|
1577
|
+
els.connLabel.textContent = "encerrado";
|
|
1578
|
+
if (els.srcLabel) els.srcLabel.textContent = "encerrado";
|
|
1579
|
+
} else if (s === "paused") {
|
|
1580
|
+
els.conn.dataset.state = "off";
|
|
1581
|
+
els.connLabel.textContent = "pausado";
|
|
1582
|
+
if (els.srcLabel) els.srcLabel.textContent = "pausado";
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
async function hydrateFromState() {
|
|
1587
|
+
try {
|
|
1588
|
+
const res = await fetch("/state", { credentials: "omit" });
|
|
1589
|
+
if (!res.ok) return;
|
|
1590
|
+
const j = await res.json();
|
|
1591
|
+
if (j.port && document.querySelector(".brand-sub")) {
|
|
1592
|
+
document.querySelector(".brand-sub").textContent = "127.0.0.1:" + j.port;
|
|
1593
|
+
}
|
|
1594
|
+
if (j.version && document.getElementById("brand-meta")) {
|
|
1595
|
+
document.getElementById("brand-meta").textContent = "sidecar · v" + j.version;
|
|
1596
|
+
}
|
|
1597
|
+
if (Array.isArray(j.events)) {
|
|
1598
|
+
// Replay through ingest. The dedup key (ts|type|runId) prevents
|
|
1599
|
+
// double-counting if SSE later delivers the same event.
|
|
1600
|
+
for (const evt of j.events) ingest(evt);
|
|
1601
|
+
}
|
|
1602
|
+
} catch (_) { /* fine — SSE may still work */ }
|
|
1603
|
+
}
|
|
938
1604
|
|
|
939
|
-
|
|
1605
|
+
function connectRealSource() {
|
|
1606
|
+
applyConnState("connecting");
|
|
1607
|
+
if (evtSource) try { evtSource.close(); } catch (_) {}
|
|
940
1608
|
|
|
941
|
-
|
|
942
|
-
let closedTimer = null;
|
|
943
|
-
let lastConnectedAt = 0;
|
|
1609
|
+
evtSource = new EventSource("/events");
|
|
944
1610
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
}
|
|
1611
|
+
evtSource.addEventListener("open", () => {
|
|
1612
|
+
lastConnectedAt = Date.now();
|
|
1613
|
+
applyConnState("open");
|
|
1614
|
+
});
|
|
949
1615
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
if (dom.conn.dataset.state === 'CLOSED') {
|
|
955
|
-
dom.banner.hidden = false;
|
|
956
|
-
}
|
|
957
|
-
}, 5000);
|
|
958
|
-
}
|
|
1616
|
+
evtSource.addEventListener("error", () => {
|
|
1617
|
+
// EventSource auto-retries; reflect "closed" until next open fires.
|
|
1618
|
+
applyConnState("closed");
|
|
1619
|
+
});
|
|
959
1620
|
|
|
960
|
-
|
|
1621
|
+
// Listen for each typed event the server emits.
|
|
1622
|
+
const handler = (msg) => {
|
|
961
1623
|
try {
|
|
962
|
-
const
|
|
963
|
-
if (
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
if (Array.isArray(j.events)) {
|
|
967
|
-
for (const evt of j.events) ingestEvent(evt);
|
|
1624
|
+
const data = JSON.parse(msg.data);
|
|
1625
|
+
if (data && data.type === "shutdown") {
|
|
1626
|
+
applyConnState("shutdown");
|
|
1627
|
+
showShutdownBanner();
|
|
968
1628
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
// Re-hydrate from /state in case events arrived while we were dropped,
|
|
1009
|
-
// then reopen the SSE stream.
|
|
1010
|
-
hydrateFromState().then(connect);
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
1013
|
-
|
|
1014
|
-
// ------- Boot ---------------------------------------------------------
|
|
1015
|
-
|
|
1016
|
-
hydrateFromState().then(() => {
|
|
1017
|
-
connect();
|
|
1018
|
-
applyFilter();
|
|
1629
|
+
ingest(data);
|
|
1630
|
+
} catch (_) { /* swallow malformed */ }
|
|
1631
|
+
};
|
|
1632
|
+
["run.start", "run.end", "tool_invocation", "progress", "milestone", "error", "shutdown"].forEach((t) =>
|
|
1633
|
+
evtSource.addEventListener(t, handler)
|
|
1634
|
+
);
|
|
1635
|
+
evtSource.onmessage = handler; // fallback for unnamed
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
/* Background-tab recovery — Chrome throttles timers in inactive tabs and the
|
|
1639
|
+
native EventSource retry can stall. When the tab becomes visible again and
|
|
1640
|
+
we know we're closed, force a fresh hydrate + reconnect. */
|
|
1641
|
+
document.addEventListener("visibilitychange", () => {
|
|
1642
|
+
if (document.visibilityState !== "visible") return;
|
|
1643
|
+
if (els.conn.dataset.state === "off") {
|
|
1644
|
+
hydrateFromState().then(connectRealSource);
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
|
|
1648
|
+
/* ───────────────────── shutdown banner ───────────────────── */
|
|
1649
|
+
function showShutdownBanner() {
|
|
1650
|
+
if (document.getElementById("shutdown-banner")) return;
|
|
1651
|
+
const banner = document.createElement("div");
|
|
1652
|
+
banner.id = "shutdown-banner";
|
|
1653
|
+
banner.innerHTML = `
|
|
1654
|
+
<strong>Sidecar encerrou.</strong>
|
|
1655
|
+
Reabra com <span style="font-family:var(--mono);color:var(--text-2)">kit ui start</span> e atualize esta página.
|
|
1656
|
+
`;
|
|
1657
|
+
Object.assign(banner.style, {
|
|
1658
|
+
margin: "0 0 16px",
|
|
1659
|
+
padding: "12px 14px",
|
|
1660
|
+
border: "1px solid var(--err-soft)",
|
|
1661
|
+
borderRadius: "var(--radius)",
|
|
1662
|
+
background: "color-mix(in srgb, var(--err) 10%, var(--surface-1))",
|
|
1663
|
+
color: "var(--err)",
|
|
1664
|
+
fontSize: "12px",
|
|
1665
|
+
display: "flex",
|
|
1666
|
+
alignItems: "center",
|
|
1667
|
+
gap: "8px",
|
|
1019
1668
|
});
|
|
1669
|
+
const main = document.querySelector("main");
|
|
1670
|
+
if (main) main.insertBefore(banner, main.firstChild);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/* ───────────────────── boot ───────────────────── */
|
|
1674
|
+
/* In production we DON'T auto-run the mock — the real /events stream is the
|
|
1675
|
+
source of truth. The mock scenarios are still wired and accessible through
|
|
1676
|
+
the tweaks panel for demo/dev. */
|
|
1677
|
+
|
|
1678
|
+
(async () => {
|
|
1679
|
+
await hydrateFromState();
|
|
1680
|
+
connectRealSource();
|
|
1681
|
+
})();
|
|
1682
|
+
|
|
1683
|
+
// Detect "we're being served via http(s)" — when not, we're probably the
|
|
1684
|
+
// design preview opened via file://; in that case fall back to the mock.
|
|
1685
|
+
if (location.protocol === "file:") {
|
|
1686
|
+
setTimeout(() => {
|
|
1687
|
+
if (state.events.length === 0) runScenario("sync");
|
|
1688
|
+
}, 200);
|
|
1689
|
+
}
|
|
1020
1690
|
</script>
|
|
1021
1691
|
</body>
|
|
1022
1692
|
</html>
|