claude-controller 0.2.0 → 0.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.
Files changed (68) hide show
  1. package/README.md +2 -2
  2. package/bin/autoloop.sh +382 -0
  3. package/bin/ctl +327 -5
  4. package/bin/native-app.py +5 -2
  5. package/bin/watchdog.sh +357 -0
  6. package/cognitive/__init__.py +14 -0
  7. package/cognitive/__pycache__/__init__.cpython-314.pyc +0 -0
  8. package/cognitive/__pycache__/dispatcher.cpython-314.pyc +0 -0
  9. package/cognitive/__pycache__/evaluator.cpython-314.pyc +0 -0
  10. package/cognitive/__pycache__/goal_engine.cpython-314.pyc +0 -0
  11. package/cognitive/__pycache__/learning.cpython-314.pyc +0 -0
  12. package/cognitive/__pycache__/orchestrator.cpython-314.pyc +0 -0
  13. package/cognitive/__pycache__/planner.cpython-314.pyc +0 -0
  14. package/cognitive/dispatcher.py +192 -0
  15. package/cognitive/evaluator.py +289 -0
  16. package/cognitive/goal_engine.py +232 -0
  17. package/cognitive/learning.py +189 -0
  18. package/cognitive/orchestrator.py +303 -0
  19. package/cognitive/planner.py +207 -0
  20. package/cognitive/prompts/analyst.md +31 -0
  21. package/cognitive/prompts/coder.md +22 -0
  22. package/cognitive/prompts/reviewer.md +33 -0
  23. package/cognitive/prompts/tester.md +21 -0
  24. package/cognitive/prompts/writer.md +25 -0
  25. package/config.sh +6 -1
  26. package/dag/__init__.py +5 -0
  27. package/dag/__pycache__/__init__.cpython-314.pyc +0 -0
  28. package/dag/__pycache__/graph.cpython-314.pyc +0 -0
  29. package/dag/graph.py +222 -0
  30. package/lib/jobs.sh +12 -1
  31. package/package.json +5 -1
  32. package/postinstall.sh +1 -1
  33. package/service/controller.sh +43 -11
  34. package/web/audit.py +122 -0
  35. package/web/checkpoint.py +80 -0
  36. package/web/config.py +2 -5
  37. package/web/handler.py +464 -26
  38. package/web/handler_fs.py +15 -14
  39. package/web/handler_goals.py +203 -0
  40. package/web/handler_jobs.py +165 -42
  41. package/web/handler_memory.py +203 -0
  42. package/web/jobs.py +576 -12
  43. package/web/personas.py +419 -0
  44. package/web/pipeline.py +682 -50
  45. package/web/presets.py +506 -0
  46. package/web/projects.py +58 -4
  47. package/web/static/api.js +90 -3
  48. package/web/static/app.js +8 -0
  49. package/web/static/base.css +51 -12
  50. package/web/static/context.js +14 -4
  51. package/web/static/form.css +3 -2
  52. package/web/static/goals.css +363 -0
  53. package/web/static/goals.js +300 -0
  54. package/web/static/i18n.js +288 -0
  55. package/web/static/index.html +142 -6
  56. package/web/static/jobs.css +951 -4
  57. package/web/static/jobs.js +890 -54
  58. package/web/static/memoryview.js +117 -0
  59. package/web/static/personas.js +228 -0
  60. package/web/static/pipeline.css +308 -1
  61. package/web/static/pipelines.js +249 -14
  62. package/web/static/presets.js +244 -0
  63. package/web/static/send.js +26 -4
  64. package/web/static/settings-style.css +34 -3
  65. package/web/static/settings.js +37 -1
  66. package/web/static/stream.js +242 -19
  67. package/web/static/utils.js +54 -2
  68. package/web/webhook.py +210 -0
@@ -1,5 +1,484 @@
1
1
  /* ── Jobs & Stream ── */
2
2
 
3
+ /* ── Stats Bar ── */
4
+ .stats-bar {
5
+ display: flex;
6
+ align-items: center;
7
+ gap: 12px;
8
+ padding: 8px 18px;
9
+ border-bottom: 1px solid var(--border);
10
+ background: var(--bg);
11
+ font-size: 0.72rem;
12
+ }
13
+
14
+ .stats-period-btns {
15
+ display: flex;
16
+ gap: 2px;
17
+ flex-shrink: 0;
18
+ }
19
+
20
+ .stats-period-btn {
21
+ padding: 3px 8px;
22
+ border-radius: var(--radius);
23
+ font-size: 0.68rem;
24
+ font-weight: 600;
25
+ font-family: var(--font);
26
+ background: transparent;
27
+ color: var(--text-muted);
28
+ border: 1px solid transparent;
29
+ cursor: pointer;
30
+ transition: all 0.15s ease;
31
+ }
32
+
33
+ .stats-period-btn:hover {
34
+ background: var(--surface-hover);
35
+ color: var(--text-secondary);
36
+ }
37
+
38
+ .stats-period-btn.active {
39
+ background: var(--accent-glow);
40
+ color: var(--accent);
41
+ border-color: var(--accent);
42
+ }
43
+
44
+ .stats-metrics {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 16px;
48
+ margin-left: auto;
49
+ font-family: var(--font-mono, monospace);
50
+ font-size: 0.72rem;
51
+ color: var(--text-secondary);
52
+ }
53
+
54
+ .stats-metric {
55
+ white-space: nowrap;
56
+ }
57
+
58
+ .stats-metric.stats-success { color: var(--success, #22c55e); }
59
+ .stats-metric.stats-duration { color: var(--text-muted); }
60
+
61
+ /* ── Project Strip ── */
62
+ .project-strip {
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 6px;
66
+ padding: 10px 0 10px 18px;
67
+ border-bottom: 1px solid var(--border);
68
+ overflow-x: auto;
69
+ -webkit-overflow-scrolling: touch;
70
+ scrollbar-width: none;
71
+ }
72
+ .project-strip::after {
73
+ content: '';
74
+ flex-shrink: 0;
75
+ width: 18px;
76
+ }
77
+ .project-strip::-webkit-scrollbar { display: none; }
78
+ .project-strip:empty { display: none; }
79
+
80
+ .project-chip {
81
+ display: inline-flex;
82
+ align-items: center;
83
+ gap: 6px;
84
+ padding: 6px 14px;
85
+ border-radius: 20px;
86
+ border: 1px solid var(--border);
87
+ background: var(--surface);
88
+ color: var(--text-secondary);
89
+ font-size: 0.74rem;
90
+ font-weight: 500;
91
+ cursor: pointer;
92
+ white-space: nowrap;
93
+ transition: all 0.15s ease;
94
+ flex-shrink: 0;
95
+ line-height: 1;
96
+ }
97
+
98
+ .project-chip:hover {
99
+ background: var(--surface-hover);
100
+ border-color: var(--text-muted);
101
+ }
102
+
103
+ .project-chip.active {
104
+ background: var(--accent-glow);
105
+ border-color: var(--accent);
106
+ color: var(--accent);
107
+ }
108
+
109
+ .pchip-icon {
110
+ flex-shrink: 0;
111
+ opacity: 0.5;
112
+ }
113
+
114
+ .project-chip.active .pchip-icon {
115
+ opacity: 1;
116
+ }
117
+
118
+ .pchip-dot {
119
+ width: 6px;
120
+ height: 6px;
121
+ border-radius: 50%;
122
+ background: var(--blue);
123
+ animation: pulse 1.5s ease-in-out infinite;
124
+ flex-shrink: 0;
125
+ }
126
+
127
+ .pchip-name {
128
+ font-family: var(--font-mono);
129
+ }
130
+
131
+ .pchip-count {
132
+ font-size: 0.66rem;
133
+ font-family: var(--font-mono);
134
+ padding: 2px 7px;
135
+ border-radius: 10px;
136
+ background: var(--bg);
137
+ color: var(--text-muted);
138
+ font-weight: 600;
139
+ min-width: 18px;
140
+ text-align: center;
141
+ line-height: 1;
142
+ display: inline-flex;
143
+ align-items: center;
144
+ justify-content: center;
145
+ }
146
+
147
+ .project-chip.active .pchip-count {
148
+ background: var(--accent);
149
+ color: var(--surface);
150
+ }
151
+
152
+ .pchip-stat {
153
+ font-size: 0.62rem;
154
+ font-family: var(--font-mono);
155
+ font-weight: 700;
156
+ padding: 2px 5px;
157
+ border-radius: 8px;
158
+ min-width: 14px;
159
+ text-align: center;
160
+ line-height: 1;
161
+ display: inline-flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ }
165
+
166
+ .pchip-stat-running {
167
+ background: var(--blue-dim);
168
+ color: var(--blue);
169
+ }
170
+
171
+ .pchip-stat-done {
172
+ background: var(--green-dim);
173
+ color: var(--green);
174
+ }
175
+
176
+ .pchip-stat-failed {
177
+ background: var(--red-dim);
178
+ color: var(--red);
179
+ }
180
+
181
+ /* ── Registered Project Badge ── */
182
+ .project-chip.registered {
183
+ border-style: solid;
184
+ border-width: 1.5px;
185
+ }
186
+
187
+ .pchip-reg {
188
+ width: 4px;
189
+ height: 4px;
190
+ border-radius: 50%;
191
+ background: var(--accent);
192
+ flex-shrink: 0;
193
+ }
194
+
195
+ .project-chip.registered .pchip-icon {
196
+ opacity: 0.8;
197
+ }
198
+
199
+ /* ── Project Detail Panel ── */
200
+ .project-detail {
201
+ padding: 14px 18px 12px;
202
+ border-bottom: 1px solid var(--border);
203
+ background: var(--surface);
204
+ }
205
+ .project-detail:empty { display: none; }
206
+
207
+ .pd-header {
208
+ display: flex;
209
+ flex-direction: column;
210
+ gap: 4px;
211
+ }
212
+
213
+ .pd-title-row {
214
+ display: flex;
215
+ align-items: center;
216
+ gap: 8px;
217
+ }
218
+
219
+ .pd-icon {
220
+ flex-shrink: 0;
221
+ color: var(--accent);
222
+ }
223
+
224
+ .pd-name {
225
+ font-size: 0.88rem;
226
+ font-weight: 600;
227
+ color: var(--text);
228
+ }
229
+
230
+ .pd-badge {
231
+ font-size: 0.58rem;
232
+ font-weight: 700;
233
+ padding: 2px 7px;
234
+ border-radius: 8px;
235
+ background: var(--accent-glow);
236
+ color: var(--accent);
237
+ text-transform: uppercase;
238
+ letter-spacing: 0.04em;
239
+ }
240
+
241
+ .pd-actions {
242
+ margin-left: auto;
243
+ display: flex;
244
+ align-items: center;
245
+ gap: 6px;
246
+ }
247
+
248
+ .pd-close {
249
+ display: flex;
250
+ align-items: center;
251
+ justify-content: center;
252
+ width: 28px;
253
+ height: 28px;
254
+ border-radius: 6px;
255
+ border: 1px solid var(--border);
256
+ background: transparent;
257
+ color: var(--text-muted);
258
+ cursor: pointer;
259
+ transition: all 0.15s ease;
260
+ }
261
+
262
+ .pd-close:hover {
263
+ background: var(--surface-hover);
264
+ color: var(--text);
265
+ border-color: var(--text-muted);
266
+ }
267
+
268
+ .pd-meta {
269
+ font-size: 0.72rem;
270
+ color: var(--text-muted);
271
+ font-family: var(--font-mono);
272
+ padding-left: 24px;
273
+ display: flex;
274
+ align-items: baseline;
275
+ flex-wrap: wrap;
276
+ row-gap: 2px;
277
+ }
278
+
279
+ .pd-sep {
280
+ color: var(--border);
281
+ margin: 0 3px;
282
+ flex-shrink: 0;
283
+ }
284
+
285
+ .pd-path-text {
286
+ color: var(--text-muted);
287
+ overflow: hidden;
288
+ text-overflow: ellipsis;
289
+ white-space: nowrap;
290
+ max-width: min(100%, 360px);
291
+ flex-shrink: 1;
292
+ }
293
+
294
+ .pd-branch {
295
+ color: var(--green);
296
+ white-space: nowrap;
297
+ flex-shrink: 0;
298
+ }
299
+
300
+ .pd-branch::before {
301
+ content: '';
302
+ display: inline-block;
303
+ width: 8px;
304
+ height: 8px;
305
+ margin-right: 3px;
306
+ background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='none' stroke='%2322c55e' stroke-width='2.5' xmlns='http://www.w3.org/2000/svg'%3E%3Cline x1='6' y1='3' x2='6' y2='15'/%3E%3Ccircle cx='18' cy='6' r='3'/%3E%3Ccircle cx='6' cy='18' r='3'/%3E%3Cpath d='M18 9a9 9 0 0 1-9 9'/%3E%3C/svg%3E") no-repeat center / contain;
307
+ vertical-align: middle;
308
+ }
309
+
310
+ .pd-remote {
311
+ color: var(--text-muted);
312
+ opacity: 0.75;
313
+ white-space: nowrap;
314
+ flex-shrink: 0;
315
+ overflow: hidden;
316
+ text-overflow: ellipsis;
317
+ max-width: 200px;
318
+ }
319
+
320
+ .pd-stats {
321
+ display: flex;
322
+ gap: 8px;
323
+ padding: 10px 0 2px 24px;
324
+ flex-wrap: wrap;
325
+ }
326
+
327
+ .pd-stat {
328
+ display: flex;
329
+ flex-direction: column;
330
+ align-items: center;
331
+ padding: 5px 12px;
332
+ border-radius: 8px;
333
+ background: var(--bg);
334
+ min-width: 50px;
335
+ }
336
+
337
+ .pd-stat-val {
338
+ font-size: 0.84rem;
339
+ font-weight: 700;
340
+ color: var(--text);
341
+ font-family: var(--font-mono);
342
+ line-height: 1.2;
343
+ }
344
+
345
+ .pd-stat-label {
346
+ font-size: 0.58rem;
347
+ color: var(--text-muted);
348
+ font-weight: 500;
349
+ margin-top: 1px;
350
+ }
351
+
352
+ .pd-running { background: var(--blue-dim); }
353
+ .pd-running .pd-stat-val { color: var(--blue); }
354
+ .pd-done { background: var(--green-dim); }
355
+ .pd-done .pd-stat-val { color: var(--green); }
356
+ .pd-failed { background: var(--red-dim); }
357
+ .pd-failed .pd-stat-val { color: var(--red); }
358
+ .pd-rate-ok .pd-stat-val { color: var(--green); }
359
+ .pd-rate-warn .pd-stat-val { color: var(--red); }
360
+
361
+ /* ── Job Filter Bar ── */
362
+ .job-filter-bar {
363
+ display: flex;
364
+ align-items: center;
365
+ gap: 10px;
366
+ padding: 8px 18px;
367
+ border-bottom: 1px solid var(--border);
368
+ background: var(--bg);
369
+ }
370
+
371
+ .job-filter-btns {
372
+ display: flex;
373
+ gap: 2px;
374
+ flex-shrink: 0;
375
+ }
376
+
377
+ .job-filter-btn {
378
+ padding: 4px 10px;
379
+ border-radius: var(--radius);
380
+ font-size: 0.72rem;
381
+ font-weight: 600;
382
+ font-family: var(--font);
383
+ background: transparent;
384
+ color: var(--text-muted);
385
+ border: 1px solid transparent;
386
+ transition: all 0.15s ease;
387
+ }
388
+
389
+ .job-filter-btn:hover {
390
+ background: var(--surface-hover);
391
+ color: var(--text-secondary);
392
+ }
393
+
394
+ .job-filter-btn.active {
395
+ background: var(--accent-glow);
396
+ color: var(--accent);
397
+ border-color: var(--accent);
398
+ }
399
+
400
+ .job-filter-btn[data-filter="running"].active { background: var(--blue-dim); color: var(--blue); border-color: var(--blue); }
401
+ .job-filter-btn[data-filter="done"].active { background: var(--green-dim); color: var(--green); border-color: var(--green); }
402
+ .job-filter-btn[data-filter="failed"].active { background: var(--red-dim); color: var(--red); border-color: var(--red); }
403
+
404
+ /* ── Project Filter Dropdown ── */
405
+ .job-project-filter {
406
+ display: flex;
407
+ align-items: center;
408
+ gap: 5px;
409
+ flex-shrink: 0;
410
+ }
411
+
412
+ .job-project-filter svg {
413
+ color: var(--text-muted);
414
+ flex-shrink: 0;
415
+ }
416
+
417
+ .job-project-filter select {
418
+ padding: 4px 24px 4px 8px;
419
+ border-radius: var(--radius);
420
+ border: 1px solid var(--border);
421
+ background: var(--surface);
422
+ color: var(--text-secondary);
423
+ font-size: 0.72rem;
424
+ font-weight: 500;
425
+ font-family: var(--font-mono);
426
+ cursor: pointer;
427
+ outline: none;
428
+ appearance: none;
429
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
430
+ background-repeat: no-repeat;
431
+ background-position: right 6px center;
432
+ max-width: 180px;
433
+ transition: border-color var(--transition);
434
+ }
435
+
436
+ .job-project-filter select:focus {
437
+ border-color: var(--accent);
438
+ }
439
+
440
+ .job-project-filter select:hover {
441
+ background-color: var(--surface-hover);
442
+ }
443
+
444
+ .job-search-wrap {
445
+ display: flex;
446
+ align-items: center;
447
+ gap: 6px;
448
+ flex: 1;
449
+ max-width: 280px;
450
+ padding: 4px 10px;
451
+ border: 1px solid var(--border);
452
+ border-radius: var(--radius);
453
+ background: var(--surface);
454
+ transition: border-color var(--transition);
455
+ }
456
+
457
+ .job-search-wrap:focus-within {
458
+ border-color: var(--accent);
459
+ }
460
+
461
+ .job-search-wrap svg {
462
+ color: var(--text-muted);
463
+ flex-shrink: 0;
464
+ }
465
+
466
+ .job-search-input {
467
+ flex: 1;
468
+ border: none !important;
469
+ background: transparent !important;
470
+ padding: 0 !important;
471
+ font-size: 0.78rem !important;
472
+ color: var(--text) !important;
473
+ outline: none !important;
474
+ box-shadow: none !important;
475
+ min-width: 0;
476
+ }
477
+
478
+ .job-search-input::placeholder {
479
+ color: var(--text-muted);
480
+ }
481
+
3
482
  /* ── Job Table ── */
4
483
  .table-wrap { overflow-x: hidden; }
5
484
 
@@ -76,6 +555,22 @@ thead tr { cursor: default; }
76
555
  color: var(--yellow);
77
556
  }
78
557
 
558
+ .badge-zombie {
559
+ background: var(--red-dim);
560
+ color: var(--red);
561
+ font-size: 0.65rem;
562
+ padding: 2px 6px;
563
+ animation: pulse 1.5s ease-in-out infinite;
564
+ }
565
+
566
+ .badge-dep {
567
+ background: var(--blue-dim, rgba(59, 130, 246, 0.1));
568
+ color: var(--blue, #3b82f6);
569
+ font-size: 0.6rem;
570
+ padding: 2px 5px;
571
+ letter-spacing: -0.02em;
572
+ }
573
+
79
574
  .job-id {
80
575
  font-family: var(--font-mono);
81
576
  font-size: 0.78rem;
@@ -197,7 +692,7 @@ thead tr { cursor: default; }
197
692
  max-height: 400px;
198
693
  overflow-y: auto;
199
694
  padding: 14px 18px;
200
- background: #080a0e;
695
+ background: var(--stream-bg, #080a0e);
201
696
  font-family: var(--font-mono);
202
697
  font-size: 0.78rem;
203
698
  line-height: 1.7;
@@ -216,7 +711,7 @@ thead tr { cursor: default; }
216
711
  }
217
712
 
218
713
  .stream-event-text {
219
- color: #e4e6ed;
714
+ color: var(--text);
220
715
  white-space: pre-wrap;
221
716
  }
222
717
 
@@ -248,7 +743,7 @@ thead tr { cursor: default; }
248
743
  }
249
744
 
250
745
  .stream-tool-input {
251
- color: #d4d4d4;
746
+ color: var(--text-secondary);
252
747
  white-space: pre-wrap;
253
748
  flex: 1;
254
749
  min-width: 0;
@@ -301,6 +796,42 @@ thead tr { cursor: default; }
301
796
  font-size: 0.8rem;
302
797
  }
303
798
 
799
+ .stream-no-output {
800
+ color: var(--text-muted);
801
+ text-align: center;
802
+ padding: 24px;
803
+ font-size: 0.8rem;
804
+ font-style: italic;
805
+ opacity: 0.7;
806
+ }
807
+
808
+ .stream-error-state {
809
+ display: flex;
810
+ flex-direction: column;
811
+ align-items: center;
812
+ gap: 8px;
813
+ padding: 24px;
814
+ color: var(--text-muted);
815
+ font-size: 0.8rem;
816
+ }
817
+ .stream-error-state svg { opacity: 0.5; }
818
+ .stream-error-state .btn { margin-top: 4px; }
819
+
820
+ .stream-poll-warning {
821
+ display: flex;
822
+ align-items: center;
823
+ gap: 6px;
824
+ padding: 6px 12px;
825
+ background: var(--yellow-dim, rgba(234, 179, 8, 0.1));
826
+ color: var(--yellow, #eab308);
827
+ font-size: 0.75rem;
828
+ font-weight: 500;
829
+ border-bottom: 1px solid rgba(234, 179, 8, 0.15);
830
+ animation: pollWarnIn 0.3s ease;
831
+ }
832
+ .stream-poll-warning svg { flex-shrink: 0; opacity: 0.7; }
833
+ @keyframes pollWarnIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
834
+
304
835
  .stream-done-banner {
305
836
  display: flex;
306
837
  align-items: center;
@@ -320,6 +851,75 @@ thead tr { cursor: default; }
320
851
  border-top-color: rgba(248, 113, 113, 0.2);
321
852
  }
322
853
 
854
+ /* ── User Error Card ── */
855
+ .user-error-card {
856
+ margin: 0;
857
+ padding: 12px 18px;
858
+ background: var(--red-dim);
859
+ border-top: 1px solid rgba(248, 113, 113, 0.15);
860
+ font-size: 0.78rem;
861
+ line-height: 1.5;
862
+ }
863
+ .user-error-summary {
864
+ font-weight: 700;
865
+ color: var(--red);
866
+ margin-bottom: 4px;
867
+ }
868
+ .user-error-cause {
869
+ color: var(--text-secondary, var(--text-muted));
870
+ margin-bottom: 6px;
871
+ }
872
+ .user-error-steps {
873
+ margin: 0 0 6px 0;
874
+ padding-left: 18px;
875
+ color: var(--text);
876
+ }
877
+ .user-error-steps li {
878
+ margin-bottom: 2px;
879
+ }
880
+ .user-error-details {
881
+ margin-top: 6px;
882
+ }
883
+ .user-error-details summary {
884
+ cursor: pointer;
885
+ font-size: 0.7rem;
886
+ color: var(--text-muted);
887
+ user-select: none;
888
+ }
889
+ .user-error-details summary:hover {
890
+ color: var(--text);
891
+ }
892
+ .user-error-raw {
893
+ margin-top: 6px;
894
+ padding: 8px 10px;
895
+ background: var(--bg-secondary, rgba(0,0,0,0.15));
896
+ border-radius: var(--radius);
897
+ font-size: 0.68rem;
898
+ color: var(--text-muted);
899
+ white-space: pre-wrap;
900
+ word-break: break-word;
901
+ max-height: 200px;
902
+ overflow-y: auto;
903
+ }
904
+
905
+ .stream-event-result-error .stream-result-icon {
906
+ color: var(--red);
907
+ }
908
+ .stream-event-result-error .stream-result-text {
909
+ color: var(--red);
910
+ }
911
+
912
+ /* ── Job Meta Info (duration) ── */
913
+ .job-meta-info {
914
+ display: inline-block;
915
+ margin-left: 6px;
916
+ font-size: 0.65rem;
917
+ color: var(--text-muted);
918
+ font-weight: 400;
919
+ font-variant-numeric: tabular-nums;
920
+ vertical-align: middle;
921
+ }
922
+
323
923
  /* ── Stream Action Buttons ── */
324
924
  .stream-actions {
325
925
  display: flex;
@@ -493,6 +1093,37 @@ thead tr { cursor: default; }
493
1093
  background: var(--green-dim) !important;
494
1094
  }
495
1095
 
1096
+ /* ── Connection Lost Banner ── */
1097
+ .conn-lost-banner {
1098
+ position: fixed;
1099
+ top: 0;
1100
+ left: 0;
1101
+ right: 0;
1102
+ z-index: 1100;
1103
+ display: flex;
1104
+ align-items: center;
1105
+ justify-content: center;
1106
+ gap: 8px;
1107
+ padding: 8px 16px;
1108
+ background: var(--red-dim);
1109
+ border-bottom: 1px solid var(--red);
1110
+ color: var(--red);
1111
+ font-size: 0.78rem;
1112
+ font-weight: 500;
1113
+ transform: translateY(-100%);
1114
+ opacity: 0;
1115
+ transition: transform 0.3s ease, opacity 0.3s ease;
1116
+ pointer-events: none;
1117
+ }
1118
+
1119
+ .conn-lost-banner.visible {
1120
+ transform: translateY(0);
1121
+ opacity: 1;
1122
+ pointer-events: auto;
1123
+ }
1124
+
1125
+ .conn-lost-banner svg { flex-shrink: 0; }
1126
+
496
1127
  /* ── Toast ── */
497
1128
  .toast-container {
498
1129
  position: fixed;
@@ -505,6 +1136,7 @@ thead tr { cursor: default; }
505
1136
  }
506
1137
 
507
1138
  .toast {
1139
+ --toast-duration: 3000ms;
508
1140
  padding: 10px 16px;
509
1141
  border-radius: var(--radius);
510
1142
  font-size: 0.82rem;
@@ -513,12 +1145,22 @@ thead tr { cursor: default; }
513
1145
  border: 1px solid var(--border);
514
1146
  color: var(--text);
515
1147
  box-shadow: var(--shadow);
516
- animation: toastIn 0.3s ease, toastOut 0.3s ease 2.7s forwards;
1148
+ animation: toastIn 0.3s ease;
517
1149
  display: flex;
518
1150
  align-items: center;
519
1151
  gap: 8px;
1152
+ cursor: pointer;
520
1153
  }
521
1154
 
1155
+ .toast .toast-msg { flex: 1; }
1156
+ .toast .toast-close {
1157
+ font-size: 1.1rem;
1158
+ opacity: 0.4;
1159
+ margin-left: 4px;
1160
+ line-height: 1;
1161
+ }
1162
+ .toast:hover .toast-close { opacity: 0.8; }
1163
+
522
1164
  .toast.success { border-color: var(--green); color: var(--green); }
523
1165
  .toast.error { border-color: var(--red); color: var(--red); }
524
1166
 
@@ -568,8 +1210,255 @@ thead tr { cursor: default; }
568
1210
  flex-shrink: 0;
569
1211
  }
570
1212
 
1213
+ /* ── Job Group View ── */
1214
+ .job-group-row { cursor: pointer; }
1215
+ .job-group-row:hover td { background: var(--surface-hover); }
1216
+
1217
+ .job-group-cell {
1218
+ padding: 12px 18px !important;
1219
+ background: var(--surface) !important;
1220
+ border-bottom: 1px solid var(--border) !important;
1221
+ }
1222
+
1223
+ .job-group-content {
1224
+ display: flex;
1225
+ align-items: center;
1226
+ gap: 10px;
1227
+ }
1228
+
1229
+ .job-group-chevron {
1230
+ transition: transform 0.2s ease;
1231
+ color: var(--text-muted);
1232
+ flex-shrink: 0;
1233
+ }
1234
+
1235
+ .job-group-chevron.collapsed { transform: rotate(-90deg); }
1236
+
1237
+ .job-group-folder {
1238
+ color: var(--accent);
1239
+ flex-shrink: 0;
1240
+ }
1241
+
1242
+ .job-group-name {
1243
+ font-weight: 600;
1244
+ font-size: 0.82rem;
1245
+ color: var(--text);
1246
+ font-family: var(--font-mono);
1247
+ }
1248
+
1249
+ .job-group-count {
1250
+ font-size: 0.68rem;
1251
+ padding: 2px 8px;
1252
+ border-radius: 10px;
1253
+ background: var(--bg);
1254
+ color: var(--text-muted);
1255
+ font-weight: 600;
1256
+ font-family: var(--font-mono);
1257
+ }
1258
+
1259
+ .job-group-stats {
1260
+ display: flex;
1261
+ align-items: center;
1262
+ gap: 6px;
1263
+ margin-left: auto;
1264
+ }
1265
+
1266
+ .grp-stat {
1267
+ display: inline-flex;
1268
+ align-items: center;
1269
+ gap: 4px;
1270
+ font-size: 0.68rem;
1271
+ font-weight: 700;
1272
+ padding: 2px 8px;
1273
+ border-radius: 10px;
1274
+ }
1275
+
1276
+ .grp-stat-running { background: var(--blue-dim); color: var(--blue); }
1277
+ .grp-stat-done { background: var(--green-dim); color: var(--green); }
1278
+ .grp-stat-failed { background: var(--red-dim); color: var(--red); }
1279
+
1280
+ .grp-dot {
1281
+ width: 5px;
1282
+ height: 5px;
1283
+ border-radius: 50%;
1284
+ background: var(--blue);
1285
+ animation: pulse 1.5s ease-in-out infinite;
1286
+ }
1287
+
1288
+ .job-group-meta {
1289
+ display: flex;
1290
+ align-items: center;
1291
+ gap: 8px;
1292
+ margin-left: 8px;
1293
+ padding-left: 8px;
1294
+ border-left: 1px solid var(--border);
1295
+ }
1296
+
1297
+ .grp-meta {
1298
+ font-size: 0.66rem;
1299
+ font-weight: 600;
1300
+ font-family: var(--font-mono);
1301
+ white-space: nowrap;
1302
+ }
1303
+
1304
+ .grp-meta-dur { color: var(--text-muted); }
1305
+ .grp-meta-rate-ok { color: var(--green); }
1306
+ .grp-meta-rate-warn { color: var(--red); }
1307
+
1308
+ .job-group-item td:first-child { padding-left: 36px; }
1309
+
1310
+ #btnViewMode.active {
1311
+ background: var(--accent-glow);
1312
+ color: var(--accent);
1313
+ border-color: var(--accent);
1314
+ }
1315
+
571
1316
  @media (max-width: 900px) {
572
1317
  th, td { padding: 10px 10px; font-size: 0.78rem; }
1318
+ .project-detail { padding: 12px 14px 10px; }
1319
+ .pd-meta { padding-left: 0; }
1320
+ .pd-path-text { max-width: min(100%, 260px); }
1321
+ .pd-stats { padding-left: 0; gap: 6px; }
1322
+ .pd-stat { padding: 4px 10px; min-width: 44px; }
1323
+ }
1324
+
1325
+ /* ── Checkpoint Diff Viewer ── */
1326
+
1327
+ .ckpt-panel {
1328
+ border-top: 1px solid var(--border, #2a2a2a);
1329
+ padding: 12px 0 0;
1330
+ margin-top: 10px;
1331
+ }
1332
+ .ckpt-loading, .ckpt-empty {
1333
+ color: var(--text-muted, #888);
1334
+ font-size: 0.8rem;
1335
+ text-align: center;
1336
+ padding: 16px 0;
1337
+ }
1338
+ .ckpt-hint {
1339
+ color: var(--text-muted, #888);
1340
+ font-size: 0.72rem;
1341
+ margin: 4px 0 8px;
1342
+ }
1343
+ .ckpt-selector {
1344
+ display: flex;
1345
+ align-items: center;
1346
+ gap: 8px;
1347
+ flex-wrap: wrap;
1348
+ }
1349
+ .ckpt-select-group {
1350
+ display: flex;
1351
+ flex-direction: column;
1352
+ gap: 2px;
1353
+ }
1354
+ .ckpt-select-group label {
1355
+ font-size: 0.68rem;
1356
+ color: var(--text-muted, #888);
1357
+ font-weight: 600;
1358
+ text-transform: uppercase;
1359
+ letter-spacing: 0.04em;
1360
+ }
1361
+ .ckpt-select-group select {
1362
+ background: var(--bg-secondary, #1a1a1a);
1363
+ color: var(--text, #e0e0e0);
1364
+ border: 1px solid var(--border, #2a2a2a);
1365
+ border-radius: 4px;
1366
+ padding: 4px 8px;
1367
+ font-size: 0.78rem;
1368
+ font-family: inherit;
1369
+ min-width: 180px;
1370
+ }
1371
+ .ckpt-arrow {
1372
+ color: var(--text-muted, #888);
1373
+ font-size: 1.1rem;
1374
+ margin-top: 14px;
1375
+ }
1376
+
1377
+ /* diff summary */
1378
+ .diff-summary {
1379
+ display: flex;
1380
+ gap: 12px;
1381
+ font-size: 0.78rem;
1382
+ padding: 8px 0;
1383
+ border-bottom: 1px solid var(--border, #2a2a2a);
1384
+ margin-bottom: 8px;
1385
+ }
1386
+ .diff-stat-files { color: var(--text, #e0e0e0); }
1387
+ .diff-stat-add { color: #4ade80; }
1388
+ .diff-stat-del { color: #f87171; }
1389
+
1390
+ /* diff file blocks */
1391
+ .diff-file {
1392
+ margin-bottom: 6px;
1393
+ border: 1px solid var(--border, #2a2a2a);
1394
+ border-radius: 6px;
1395
+ overflow: hidden;
1396
+ }
1397
+ .diff-file.collapsed .diff-file-body { display: none; }
1398
+ .diff-file-header {
1399
+ display: flex;
1400
+ justify-content: space-between;
1401
+ align-items: center;
1402
+ padding: 6px 10px;
1403
+ background: var(--bg-secondary, #1a1a1a);
1404
+ cursor: pointer;
1405
+ font-size: 0.78rem;
1406
+ user-select: none;
1407
+ }
1408
+ .diff-file-header:hover { background: var(--bg-hover, #222); }
1409
+ .diff-file-name {
1410
+ font-family: 'SF Mono', 'Fira Code', monospace;
1411
+ color: var(--text, #e0e0e0);
1412
+ font-weight: 500;
1413
+ }
1414
+ .diff-file-stats {
1415
+ display: flex;
1416
+ gap: 8px;
1417
+ font-size: 0.72rem;
1418
+ font-weight: 600;
1419
+ }
1420
+ .diff-file-body {
1421
+ overflow-x: auto;
1422
+ }
1423
+ .diff-code {
1424
+ margin: 0;
1425
+ padding: 0;
1426
+ font-family: 'SF Mono', 'Fira Code', monospace;
1427
+ font-size: 0.74rem;
1428
+ line-height: 1.45;
1429
+ tab-size: 4;
1430
+ }
1431
+
1432
+ /* diff lines */
1433
+ .diff-line {
1434
+ padding: 0 10px;
1435
+ white-space: pre;
1436
+ min-height: 1.45em;
1437
+ }
1438
+ .diff-add {
1439
+ background: rgba(74, 222, 128, 0.1);
1440
+ color: #4ade80;
1441
+ }
1442
+ .diff-del {
1443
+ background: rgba(248, 113, 113, 0.1);
1444
+ color: #f87171;
1445
+ }
1446
+ .diff-hunk {
1447
+ background: rgba(96, 165, 250, 0.08);
1448
+ color: #60a5fa;
1449
+ font-style: italic;
1450
+ padding-top: 4px;
1451
+ padding-bottom: 4px;
1452
+ margin-top: 2px;
1453
+ }
1454
+ .diff-meta {
1455
+ color: var(--text-muted, #888);
1456
+ font-style: italic;
1457
+ }
1458
+
1459
+ .diff-result {
1460
+ max-height: 500px;
1461
+ overflow-y: auto;
573
1462
  }
574
1463
 
575
1464
  @media (max-width: 600px) {
@@ -577,4 +1466,62 @@ thead tr { cursor: default; }
577
1466
  .card-body { padding: 14px; }
578
1467
  th, td { padding: 8px 6px; font-size: 0.74rem; }
579
1468
  .stream-content { max-height: 300px; }
1469
+ .ckpt-selector { flex-direction: column; align-items: stretch; }
1470
+ .ckpt-arrow { display: none; }
1471
+ .diff-result { max-height: 350px; }
1472
+
1473
+ .project-detail { padding: 10px 12px 8px; }
1474
+ .pd-title-row { flex-wrap: wrap; gap: 6px; }
1475
+ .pd-meta { padding-left: 0; font-size: 0.66rem; }
1476
+ .pd-path-text { max-width: 100%; }
1477
+ .pd-remote { max-width: 150px; }
1478
+ .pd-stats { padding: 8px 0 2px 0; gap: 5px; }
1479
+ .pd-stat { padding: 4px 8px; min-width: 40px; }
1480
+ .pd-stat-val { font-size: 0.78rem; }
1481
+ .pd-stat-label { font-size: 0.54rem; }
1482
+ .pd-actions .btn { font-size: 0.68rem; padding: 4px 8px; }
1483
+ }
1484
+
1485
+ /* ── Pagination ── */
1486
+ .job-pagination {
1487
+ display: flex;
1488
+ align-items: center;
1489
+ justify-content: center;
1490
+ gap: 4px;
1491
+ padding: 10px 0 6px;
1492
+ }
1493
+ .pg-btn {
1494
+ min-width: 32px;
1495
+ height: 32px;
1496
+ border: 1px solid var(--border);
1497
+ border-radius: 6px;
1498
+ background: var(--bg);
1499
+ color: var(--text);
1500
+ font-size: 0.82rem;
1501
+ cursor: pointer;
1502
+ transition: background 0.15s, border-color 0.15s;
1503
+ }
1504
+ .pg-btn:hover:not(:disabled):not(.active) {
1505
+ background: var(--hover);
1506
+ border-color: var(--accent);
1507
+ }
1508
+ .pg-btn.active {
1509
+ background: var(--accent);
1510
+ color: #fff;
1511
+ border-color: var(--accent);
1512
+ font-weight: 600;
1513
+ }
1514
+ .pg-btn:disabled {
1515
+ opacity: 0.35;
1516
+ cursor: default;
1517
+ }
1518
+ .pg-ellipsis {
1519
+ padding: 0 4px;
1520
+ color: var(--text-secondary);
1521
+ font-size: 0.82rem;
1522
+ }
1523
+ .pg-info {
1524
+ margin-left: 10px;
1525
+ font-size: 0.78rem;
1526
+ color: var(--text-secondary);
580
1527
  }