@luanpdd/kit-mcp 1.2.0 → 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 +119 -0
- package/package.json +1 -1
- package/src/ui/static/index.html +1615 -532
package/src/ui/static/index.html
CHANGED
|
@@ -1,609 +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
|
-
--shadow: 0 1px 2px rgba(0,0,0,.04), 0 4px 10px rgba(0,0,0,.04);
|
|
25
|
-
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
26
|
-
--sans: system-ui, -apple-system, "Segoe UI", Roboto, Inter, "Helvetica Neue", sans-serif;
|
|
27
|
-
}
|
|
28
|
-
@media (prefers-color-scheme: dark) {
|
|
29
|
-
:root {
|
|
30
|
-
--bg: #0b1120;
|
|
31
|
-
--bg-elev: #111827;
|
|
32
|
-
--bg-row: #0f172a;
|
|
33
|
-
--bg-row-hover: #1e293b;
|
|
34
|
-
--fg: #e2e8f0;
|
|
35
|
-
--fg-muted: #94a3b8;
|
|
36
|
-
--fg-subtle: #64748b;
|
|
37
|
-
--border: #1f2937;
|
|
38
|
-
--accent: #60a5fa;
|
|
39
|
-
--ok: #34d399;
|
|
40
|
-
--warn: #fbbf24;
|
|
41
|
-
--err: #f87171;
|
|
42
|
-
--info: #818cf8;
|
|
43
|
-
--shadow: 0 1px 2px rgba(0,0,0,.3), 0 4px 10px rgba(0,0,0,.3);
|
|
44
|
-
}
|
|
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);
|
|
45
68
|
}
|
|
69
|
+
}
|
|
46
70
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
font-family: var(--sans);
|
|
51
|
-
font-size: 14px;
|
|
52
|
-
line-height: 1.5;
|
|
53
|
-
color: var(--fg);
|
|
54
|
-
background: var(--bg);
|
|
55
|
-
display: flex;
|
|
56
|
-
flex-direction: column;
|
|
57
|
-
}
|
|
71
|
+
/* density tweak */
|
|
72
|
+
:root[data-density="compact"] { --pad: 10px; --pad-tight: 6px; }
|
|
73
|
+
:root[data-density="comfy"] { --pad: 20px; --pad-tight: 14px; }
|
|
58
74
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
header h1::before {
|
|
78
|
-
content: "◆";
|
|
79
|
-
color: var(--accent);
|
|
80
|
-
margin-right: 6px;
|
|
81
|
-
}
|
|
82
|
-
header .meta {
|
|
83
|
-
color: var(--fg-subtle);
|
|
84
|
-
font-family: var(--mono);
|
|
85
|
-
font-size: 12px;
|
|
86
|
-
}
|
|
87
|
-
header .grow { flex: 1; }
|
|
88
|
-
|
|
89
|
-
.conn-status {
|
|
90
|
-
display: inline-flex;
|
|
91
|
-
align-items: center;
|
|
92
|
-
gap: 6px;
|
|
93
|
-
padding: 3px 10px;
|
|
94
|
-
border-radius: 999px;
|
|
95
|
-
font-family: var(--mono);
|
|
96
|
-
font-size: 11px;
|
|
97
|
-
font-weight: 600;
|
|
98
|
-
border: 1px solid var(--border);
|
|
99
|
-
background: var(--bg-row);
|
|
100
|
-
}
|
|
101
|
-
.conn-status .dot {
|
|
102
|
-
display: inline-block;
|
|
103
|
-
width: 8px;
|
|
104
|
-
height: 8px;
|
|
105
|
-
border-radius: 50%;
|
|
106
|
-
background: var(--fg-subtle);
|
|
107
|
-
}
|
|
108
|
-
.conn-status[data-state="CONNECTING"] .dot { background: var(--warn); animation: pulse 1.4s infinite; }
|
|
109
|
-
.conn-status[data-state="OPEN"] .dot { background: var(--ok); }
|
|
110
|
-
.conn-status[data-state="CLOSED"] .dot { background: var(--err); animation: pulse 1s infinite; }
|
|
111
|
-
@keyframes pulse { 50% { opacity: .35; } }
|
|
112
|
-
|
|
113
|
-
.toolbar {
|
|
114
|
-
display: flex;
|
|
115
|
-
align-items: center;
|
|
116
|
-
gap: 10px;
|
|
117
|
-
padding: 10px 16px;
|
|
118
|
-
border-bottom: 1px solid var(--border);
|
|
119
|
-
background: var(--bg-elev);
|
|
120
|
-
flex-wrap: wrap;
|
|
121
|
-
}
|
|
122
|
-
.toolbar input[type="search"] {
|
|
123
|
-
flex: 1 1 240px;
|
|
124
|
-
min-width: 180px;
|
|
125
|
-
padding: 6px 10px;
|
|
126
|
-
border: 1px solid var(--border);
|
|
127
|
-
border-radius: 6px;
|
|
128
|
-
background: var(--bg-row);
|
|
129
|
-
color: var(--fg);
|
|
130
|
-
font-family: var(--sans);
|
|
131
|
-
font-size: 13px;
|
|
132
|
-
}
|
|
133
|
-
.toolbar input[type="search"]:focus {
|
|
134
|
-
outline: 2px solid var(--accent);
|
|
135
|
-
outline-offset: -1px;
|
|
136
|
-
}
|
|
137
|
-
.filters {
|
|
138
|
-
display: flex;
|
|
139
|
-
flex-wrap: wrap;
|
|
140
|
-
gap: 4px;
|
|
141
|
-
}
|
|
142
|
-
.filters label {
|
|
143
|
-
display: inline-flex;
|
|
144
|
-
align-items: center;
|
|
145
|
-
gap: 4px;
|
|
146
|
-
padding: 3px 8px;
|
|
147
|
-
border-radius: 999px;
|
|
148
|
-
border: 1px solid var(--border);
|
|
149
|
-
background: var(--bg-row);
|
|
150
|
-
cursor: pointer;
|
|
151
|
-
user-select: none;
|
|
152
|
-
font-size: 11px;
|
|
153
|
-
font-family: var(--mono);
|
|
154
|
-
}
|
|
155
|
-
.filters input { display: none; }
|
|
156
|
-
.filters label:has(input:checked) {
|
|
157
|
-
background: var(--accent);
|
|
158
|
-
color: white;
|
|
159
|
-
border-color: var(--accent);
|
|
160
|
-
}
|
|
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); }
|
|
161
93
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
color: var(--fg);
|
|
168
|
-
font-family: var(--sans);
|
|
169
|
-
font-size: 12px;
|
|
170
|
-
cursor: pointer;
|
|
171
|
-
}
|
|
172
|
-
button:hover { background: var(--bg-row-hover); }
|
|
173
|
-
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
174
|
-
button[aria-pressed="true"] { background: var(--accent); color: white; border-color: var(--accent); }
|
|
175
|
-
|
|
176
|
-
main {
|
|
177
|
-
flex: 1;
|
|
178
|
-
overflow: auto;
|
|
179
|
-
padding: 0 16px 24px;
|
|
180
|
-
}
|
|
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); }
|
|
181
99
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
background: var(--bg-row);
|
|
192
|
-
padding: 1px 6px;
|
|
193
|
-
border-radius: 4px;
|
|
194
|
-
font-size: 12px;
|
|
195
|
-
border: 1px solid var(--border);
|
|
196
|
-
}
|
|
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
|
+
}
|
|
197
109
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
padding: 0;
|
|
202
|
-
border: 1px solid var(--border);
|
|
203
|
-
border-radius: 8px;
|
|
204
|
-
overflow: hidden;
|
|
205
|
-
background: var(--bg-elev);
|
|
206
|
-
}
|
|
207
|
-
.ev-row {
|
|
208
|
-
display: grid;
|
|
209
|
-
grid-template-columns: 92px 110px 1fr;
|
|
210
|
-
gap: 12px;
|
|
211
|
-
padding: 8px 12px;
|
|
212
|
-
border-bottom: 1px solid var(--border);
|
|
213
|
-
font-size: 13px;
|
|
214
|
-
align-items: start;
|
|
215
|
-
}
|
|
216
|
-
.ev-row:last-child { border-bottom: 0; }
|
|
217
|
-
.ev-row:hover { background: var(--bg-row-hover); }
|
|
218
|
-
|
|
219
|
-
.ev-time {
|
|
220
|
-
font-family: var(--mono);
|
|
221
|
-
font-size: 11px;
|
|
222
|
-
color: var(--fg-subtle);
|
|
223
|
-
padding-top: 1px;
|
|
224
|
-
}
|
|
110
|
+
@media (max-width: 520px) {
|
|
111
|
+
.app { padding: 12px 14px 0; }
|
|
112
|
+
}
|
|
225
113
|
|
|
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
|
-
|
|
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); }
|
|
277
172
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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; } }
|
|
295
293
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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;
|
|
306
813
|
}
|
|
814
|
+
}
|
|
307
815
|
</style>
|
|
308
816
|
</head>
|
|
309
817
|
<body>
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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>
|
|
315
840
|
</header>
|
|
316
841
|
|
|
842
|
+
<!-- TOOLBAR -->
|
|
317
843
|
<div class="toolbar">
|
|
318
|
-
<
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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>
|
|
325
877
|
</div>
|
|
326
878
|
|
|
879
|
+
<!-- ACTIVE + LOG -->
|
|
327
880
|
<main>
|
|
328
|
-
<
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
<strong>Aguardando primeiro evento…</strong>
|
|
334
|
-
Rode <code>kit sync install</code>, <code>kit reverse-sync apply</code>, ou
|
|
335
|
-
invoke uma tool MCP com <code>autoSpawn: true</code> em outra janela.
|
|
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>
|
|
336
886
|
</div>
|
|
887
|
+
|
|
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>
|
|
902
|
+
</div>
|
|
903
|
+
</section>
|
|
337
904
|
</main>
|
|
338
905
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
<span id="
|
|
342
|
-
<span
|
|
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>
|
|
937
|
+
|
|
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>
|
|
946
|
+
|
|
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>
|
|
955
|
+
|
|
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>
|
|
965
|
+
|
|
966
|
+
<div class="tw-actions">
|
|
967
|
+
<button id="tw-replay">▸ replay</button>
|
|
968
|
+
<button id="tw-clear">limpar</button>
|
|
969
|
+
</div>
|
|
343
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>
|
|
344
994
|
|
|
345
995
|
<script>
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const EVENT_TYPES = ['run.start', 'run.end', 'tool_invocation', 'progress', 'milestone', 'error', 'shutdown'];
|
|
353
|
-
const RING_DISPLAY_MAX = 500;
|
|
354
|
-
|
|
355
|
-
const $ = (id) => document.getElementById(id);
|
|
356
|
-
const dom = {
|
|
357
|
-
conn: $('conn'),
|
|
358
|
-
connText: $('conn-text'),
|
|
359
|
-
metaPort: $('meta-port'),
|
|
360
|
-
list: $('events'),
|
|
361
|
-
empty: $('empty'),
|
|
362
|
-
banner: $('shutdown-banner'),
|
|
363
|
-
pauseBtn: $('pause-btn'),
|
|
364
|
-
autoscrollBtn: $('autoscroll-btn'),
|
|
365
|
-
clearBtn: $('clear-btn'),
|
|
366
|
-
search: $('search'),
|
|
367
|
-
typeFilters: $('type-filters'),
|
|
368
|
-
footerEvents: $('footer-events'),
|
|
369
|
-
footerPaused: $('footer-paused'),
|
|
370
|
-
footerSource: $('footer-source'),
|
|
371
|
-
};
|
|
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
|
+
────────────────────────────────────────────────────────── */
|
|
372
1001
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
+
}
|
|
382
1039
|
|
|
383
|
-
|
|
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
|
+
}
|
|
384
1056
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
for (const k of kids) if (k) e.appendChild(typeof k === 'string' ? document.createTextNode(k) : k);
|
|
395
|
-
return e;
|
|
396
|
-
}
|
|
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
|
+
};
|
|
397
1066
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
+
};
|
|
405
1089
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if (typeof p.tool === 'string') return `tool: ${p.tool}`;
|
|
412
|
-
if (typeof p.percent === 'number') return `${p.percent}%${p.kind ? ' · ' + p.kind : ''}`;
|
|
413
|
-
if (typeof p.message === 'string') return p.message;
|
|
414
|
-
if (typeof p.reason === 'string') return p.reason;
|
|
415
|
-
return evt.type;
|
|
416
|
-
}
|
|
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;
|
|
417
1095
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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);
|
|
1102
|
+
}
|
|
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
|
|
422
1118
|
}
|
|
423
|
-
if (evt.runId) return evt.runId.slice(0, 6);
|
|
424
|
-
return '';
|
|
425
1119
|
}
|
|
426
1120
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
+
}
|
|
439
1133
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
+
});
|
|
1151
|
+
|
|
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);
|
|
445
1163
|
}
|
|
446
|
-
|
|
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>
|
|
1211
|
+
|
|
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>
|
|
1216
|
+
|
|
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>
|
|
1222
|
+
|
|
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;
|
|
447
1247
|
}
|
|
1248
|
+
els.empty.style.display = "none";
|
|
1249
|
+
els.logHead.hidden = false;
|
|
448
1250
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (
|
|
462
|
-
|
|
463
|
-
|
|
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
|
+
}
|
|
464
1270
|
} else {
|
|
465
|
-
|
|
466
|
-
|
|
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);
|
|
467
1284
|
}
|
|
468
|
-
|
|
469
|
-
|
|
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
|
+
}
|
|
470
1292
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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;
|
|
481
1305
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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;
|
|
1310
|
+
}
|
|
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;
|
|
1317
|
+
}
|
|
1318
|
+
case "milestone": {
|
|
1319
|
+
msg = `<strong>${escapeHtml(evt.payload?.name || "")}</strong>`;
|
|
1320
|
+
break;
|
|
490
1321
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
1322
|
+
case "error": {
|
|
1323
|
+
msg = `<strong>${escapeHtml(evt.payload?.message || "erro")}</strong>${evt.payload?.code ? ` <span class="ident">${evt.payload.code}</span>` : ""}`;
|
|
1324
|
+
break;
|
|
494
1325
|
}
|
|
1326
|
+
case "tool_invocation": {
|
|
1327
|
+
msg = `<span class="ident">${escapeHtml(evt.payload?.tool || "")}</span>`;
|
|
1328
|
+
break;
|
|
1329
|
+
}
|
|
1330
|
+
default:
|
|
1331
|
+
msg = "";
|
|
495
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
|
+
}
|
|
496
1349
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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);
|
|
501
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);
|
|
1365
|
+
});
|
|
1366
|
+
}, 1000);
|
|
502
1367
|
|
|
503
|
-
|
|
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)); }
|
|
504
1375
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
+
}));
|
|
515
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
|
+
}
|
|
516
1421
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
+
}));
|
|
520
1433
|
});
|
|
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
|
+
}
|
|
521
1438
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
});
|
|
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
|
+
}
|
|
528
1447
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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);
|
|
532
1486
|
});
|
|
1487
|
+
});
|
|
1488
|
+
document.querySelector('#tw-accent button[data-h="130"]').setAttribute("aria-pressed","true");
|
|
533
1489
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
+
});
|
|
538
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); });
|
|
539
1502
|
|
|
540
|
-
|
|
1503
|
+
document.documentElement.dataset.density = "normal";
|
|
1504
|
+
document.documentElement.dataset.motion = "medium";
|
|
541
1505
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
let lastConnectedAt = 0;
|
|
1506
|
+
document.getElementById("tw-replay").addEventListener("click", () => { clearAll(); runScenario(currentScenario); });
|
|
1507
|
+
document.getElementById("tw-clear").addEventListener("click", clearAll);
|
|
545
1508
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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();
|
|
549
1515
|
}
|
|
1516
|
+
if (e.key === "Escape" && document.activeElement === els.q) {
|
|
1517
|
+
els.q.value = ""; state.filterText = ""; els.q.blur(); render();
|
|
1518
|
+
}
|
|
1519
|
+
});
|
|
550
1520
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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");
|
|
559
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();
|
|
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. */
|
|
560
1557
|
|
|
561
|
-
|
|
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
|
+
}
|
|
1604
|
+
|
|
1605
|
+
function connectRealSource() {
|
|
1606
|
+
applyConnState("connecting");
|
|
1607
|
+
if (evtSource) try { evtSource.close(); } catch (_) {}
|
|
1608
|
+
|
|
1609
|
+
evtSource = new EventSource("/events");
|
|
1610
|
+
|
|
1611
|
+
evtSource.addEventListener("open", () => {
|
|
1612
|
+
lastConnectedAt = Date.now();
|
|
1613
|
+
applyConnState("open");
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
evtSource.addEventListener("error", () => {
|
|
1617
|
+
// EventSource auto-retries; reflect "closed" until next open fires.
|
|
1618
|
+
applyConnState("closed");
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
// Listen for each typed event the server emits.
|
|
1622
|
+
const handler = (msg) => {
|
|
562
1623
|
try {
|
|
563
|
-
const
|
|
564
|
-
if (
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
if (Array.isArray(j.events)) {
|
|
568
|
-
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();
|
|
569
1628
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
+
}
|
|
574
1637
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (closedTimer) { clearTimeout(closedTimer); closedTimer = null; }
|
|
583
|
-
// Don't hide banner if we already received a 'shutdown' event
|
|
584
|
-
};
|
|
585
|
-
evtSource.onerror = () => {
|
|
586
|
-
setConnState('CLOSED');
|
|
587
|
-
scheduleClosedBanner();
|
|
588
|
-
// EventSource will retry automatically (we send retry: 3000 from server)
|
|
589
|
-
};
|
|
590
|
-
// Listen for each known event type so 'event:' lines get routed to the same handler
|
|
591
|
-
const handler = (msg) => {
|
|
592
|
-
try {
|
|
593
|
-
const data = JSON.parse(msg.data);
|
|
594
|
-
ingestEvent(data);
|
|
595
|
-
} catch (_) { /* swallow malformed */ }
|
|
596
|
-
};
|
|
597
|
-
for (const t of EVENT_TYPES) evtSource.addEventListener(t, handler);
|
|
598
|
-
evtSource.onmessage = handler; // fallback for events without type field
|
|
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);
|
|
599
1645
|
}
|
|
1646
|
+
});
|
|
600
1647
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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",
|
|
606
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
|
+
}
|
|
607
1690
|
</script>
|
|
608
1691
|
</body>
|
|
609
1692
|
</html>
|