mimetic-cli 0.1.2 → 0.1.4

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 (50) hide show
  1. package/AGENTS.md +66 -0
  2. package/CONTRIBUTING.md +39 -0
  3. package/README.md +4 -1
  4. package/SECURITY.md +34 -0
  5. package/dist/core/git-state.d.ts +31 -0
  6. package/dist/core/git-state.js +142 -0
  7. package/dist/core/git-state.js.map +1 -0
  8. package/dist/core/index.d.ts +4 -0
  9. package/dist/core/index.js +3 -0
  10. package/dist/core/index.js.map +1 -0
  11. package/dist/core/run-primitives.d.ts +66 -0
  12. package/dist/core/run-primitives.js +120 -0
  13. package/dist/core/run-primitives.js.map +1 -0
  14. package/dist/observer-assets.js +1663 -2180
  15. package/dist/observer-assets.js.map +1 -1
  16. package/dist/observer-data.d.ts +1 -1
  17. package/dist/observer-data.js +5 -1
  18. package/dist/observer-data.js.map +1 -1
  19. package/dist/observer.js +8 -61
  20. package/dist/observer.js.map +1 -1
  21. package/dist/oss-meta-lab.d.ts +50 -0
  22. package/dist/oss-meta-lab.js +454 -27
  23. package/dist/oss-meta-lab.js.map +1 -1
  24. package/dist/program.d.ts +6 -0
  25. package/dist/program.js +75 -8
  26. package/dist/program.js.map +1 -1
  27. package/dist/run.d.ts +19 -6
  28. package/dist/run.js +1263 -9
  29. package/dist/run.js.map +1 -1
  30. package/docs/architecture/github-feedback-loop.md +189 -0
  31. package/docs/architecture/local-codex-tui-actor.md +210 -0
  32. package/docs/architecture/observer.md +109 -0
  33. package/docs/architecture/oss-lab-poc.md +170 -0
  34. package/docs/architecture/project-layout.md +132 -0
  35. package/docs/assets/mimetic-oss-lab-observer.png +0 -0
  36. package/docs/contracts/adapter-fixtures.md +80 -0
  37. package/docs/contracts/core.md +71 -0
  38. package/docs/contracts/feedback.md +131 -0
  39. package/docs/contracts/policy.md +273 -0
  40. package/docs/contracts/run-bundle.md +110 -0
  41. package/docs/contracts/schemas.md +511 -0
  42. package/docs/goals/current.md +163 -0
  43. package/docs/principles/self-driving-harness.md +129 -0
  44. package/docs/product/open-source-install-experience.md +138 -0
  45. package/docs/ramp/README.md +167 -0
  46. package/docs/release/open-source-readiness.md +171 -0
  47. package/docs/release/public-readiness-standard.md +205 -0
  48. package/docs/roadmap/world-class-open-source-v0.md +286 -0
  49. package/package.json +14 -2
  50. package/skills/mimetic-cli/SKILL.md +1 -1
@@ -1,2318 +1,1801 @@
1
+ // Mimetic Observer: client assets (CSS + browser JS).
2
+ //
3
+ // This file is the redesigned Observer surface ported from the Claude Design
4
+ // handoff (HTML/CSS/JS prototype) into the repo's self-contained renderer.
5
+ // `observer.ts` injects `observerCss()` into a <style> and `observerClientJs()`
6
+ // into a <script>, then hydrates from an embedded `observer-data.v1` JSON blob
7
+ // and polls `observer-data.json` (served mode) for live updates.
8
+ //
9
+ // IMPORTANT: `observerClientJs()` runs verbatim in the browser. It is emitted
10
+ // from a TS template literal, so it intentionally avoids backticks, `${`, and
11
+ // backslash escapes; literal UTF-8 characters and `String.fromCharCode` are
12
+ // used instead. Keep it that way when editing.
1
13
  export function observerCss() {
2
14
  return `
3
- :root {
4
- --obs-bg: #0a0a0a;
5
- --obs-bg-1: #0f0f10;
6
- --obs-bg-2: #141416;
7
- --obs-bg-3: #1a1a1c;
8
- --obs-bg-4: #232326;
9
- --obs-line: rgba(255, 255, 255, 0.06);
10
- --obs-line-2: rgba(255, 255, 255, 0.1);
11
- --obs-line-3: rgba(255, 255, 255, 0.18);
12
- --obs-fg-1: #f4f4f3;
13
- --obs-fg-2: #b6b6b1;
14
- --obs-fg-3: #7a7a75;
15
- --obs-fg-4: #4d4d49;
16
- --obs-blue: #3f71fa;
17
- --obs-blue-soft: rgba(63, 113, 250, 0.16);
18
- --obs-blue-line: rgba(63, 113, 250, 0.4);
19
- --obs-sky: #70d8fa;
20
- --obs-amber: #d48806;
21
- --obs-amber-soft: rgba(212, 136, 6, 0.14);
22
- --obs-red: #e0584a;
23
- --obs-red-soft: rgba(224, 88, 74, 0.14);
24
- --obs-green: #38b07a;
25
- --obs-green-soft: rgba(56, 176, 122, 0.14);
26
- --sans: "Geist", "Aptos", "Avenir Next", "Helvetica Neue", system-ui, sans-serif;
27
- --mono: "ABCDiatypeMono", "Geist Mono", "SFMono-Regular", ui-monospace, Consolas, monospace;
28
- --display: "Knapp", "Geist", ui-serif, Georgia, serif;
29
- --ease: cubic-bezier(0.16, 1, 0.3, 1);
30
- }
31
-
32
- * { box-sizing: border-box; }
33
-
34
- html,
35
- body {
36
- margin: 0;
37
- padding: 0;
38
- height: 100%;
39
- overflow: hidden;
40
- }
41
-
42
- body {
43
- background: var(--obs-bg);
44
- color: var(--obs-fg-1);
45
- font-family: var(--sans);
46
- font-size: 13px;
47
- line-height: 1.5;
48
- -webkit-font-smoothing: antialiased;
49
- }
50
-
51
- button {
52
- font-family: inherit;
53
- cursor: pointer;
54
- }
55
-
56
- button:focus-visible,
57
- a:focus-visible,
58
- .tile:focus-visible {
59
- outline: 2px solid var(--obs-blue);
60
- outline-offset: 2px;
61
- }
62
-
63
- a { color: inherit; }
64
-
65
- ::-webkit-scrollbar {
66
- width: 8px;
67
- height: 8px;
68
- }
69
-
70
- ::-webkit-scrollbar-track { background: transparent; }
71
-
72
- ::-webkit-scrollbar-thumb {
73
- background: rgba(255, 255, 255, 0.08);
74
- border-radius: 8px;
75
- }
76
-
77
- ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.16); }
78
-
79
- .obs-eyebrow {
80
- font-family: var(--mono);
81
- font-size: 10px;
82
- letter-spacing: 0.14em;
83
- text-transform: uppercase;
84
- color: var(--obs-fg-3);
85
- }
86
-
87
- .app {
88
- display: flex;
89
- flex-direction: column;
90
- height: 100%;
91
- background: var(--obs-bg);
92
- }
93
-
94
- .rp {
95
- display: flex;
96
- flex-direction: column;
97
- border-bottom: 1px solid var(--obs-line);
98
- background: var(--obs-bg-1);
99
- flex: none;
100
- }
101
-
102
- .rp-bar {
103
- display: flex;
104
- align-items: center;
105
- gap: 12px;
106
- padding: 10px 16px;
107
- flex-wrap: wrap;
108
- row-gap: 8px;
109
- container-type: inline-size;
110
- container-name: rp-bar;
111
- }
112
-
113
- .rp-brand {
114
- display: flex;
115
- align-items: center;
116
- gap: 10px;
117
- flex-shrink: 0;
118
- }
119
-
120
- .rp-brand-mark {
121
- width: 16px;
122
- height: 16px;
123
- border: 1px solid var(--obs-blue);
124
- border-radius: 50%;
125
- display: inline-block;
126
- position: relative;
127
- box-shadow: inset 0 0 0 3px rgba(63, 113, 250, 0.16);
128
- }
129
-
130
- .rp-brand-mark::after {
131
- content: "";
132
- position: absolute;
133
- width: 6px;
134
- height: 6px;
135
- left: 4px;
136
- top: 4px;
137
- border: 1px solid var(--obs-sky);
138
- transform: rotate(45deg);
139
- }
140
-
141
- .rp-brand-text {
142
- font-family: var(--mono);
143
- font-size: 10px;
144
- letter-spacing: 0.16em;
145
- text-transform: uppercase;
146
- color: var(--obs-fg-2);
147
- }
148
-
149
- .rp-divider,
150
- .sub-divider {
151
- width: 1px;
152
- height: 12px;
153
- background: var(--obs-line-2);
154
- flex-shrink: 0;
155
- }
156
-
157
- .rp-current {
158
- display: flex;
159
- align-items: center;
160
- gap: 8px;
161
- min-width: 0;
162
- flex-shrink: 0;
163
- }
164
-
165
- .pulse-dot,
166
- .pip,
167
- .rp-chip-dot,
168
- .sub-filter-dot {
169
- width: 6px;
170
- height: 6px;
171
- border-radius: 50%;
172
- background: currentColor;
173
- flex: none;
174
- }
175
-
176
- .pulse-dot { animation: pulse-dot 1.4s ease-in-out infinite; }
177
-
178
- .rp-current .pulse-dot { color: var(--obs-blue); }
179
-
180
- .rp-current-label {
181
- font-family: var(--mono);
182
- font-size: 10px;
183
- letter-spacing: 0.12em;
184
- text-transform: uppercase;
185
- color: var(--obs-fg-1);
186
- }
187
-
188
- .rp-current-step {
189
- font-family: var(--mono);
190
- font-size: 10px;
191
- letter-spacing: 0.06em;
192
- color: var(--obs-fg-3);
193
- }
194
-
195
- .rp-progress {
196
- flex: 1 1 80px;
197
- height: 3px;
198
- background: var(--obs-line);
199
- border-radius: 2px;
200
- overflow: hidden;
201
- min-width: 60px;
202
- max-width: 320px;
203
- }
204
-
205
- .rp-progress > span {
206
- display: block;
207
- height: 100%;
208
- background: var(--obs-blue);
209
- transition: width 600ms var(--ease);
210
- width: 0%;
211
- }
212
-
213
- .rp-meta {
214
- font-family: var(--mono);
215
- font-size: 10px;
216
- color: var(--obs-fg-3);
217
- white-space: nowrap;
218
- flex-shrink: 0;
219
- }
220
-
221
- .rp-meta strong {
222
- color: var(--obs-fg-1);
223
- font-weight: 500;
224
- }
225
-
226
- .rp-chips {
227
- margin-left: auto;
228
- display: flex;
229
- align-items: center;
230
- gap: 6px;
231
- flex-shrink: 0;
232
- }
233
-
234
- .rp-chip {
235
- display: inline-flex;
236
- align-items: center;
237
- gap: 6px;
238
- padding: 3px 8px;
239
- border: 1px solid var(--obs-line);
240
- border-radius: 2px;
241
- background: transparent;
242
- font-family: var(--mono);
243
- }
244
-
245
- .rp-chip[data-active="true"] { background: var(--obs-bg-2); }
246
-
247
- .rp-chip-label {
248
- font-size: 10px;
249
- letter-spacing: 0.1em;
250
- text-transform: uppercase;
251
- color: var(--obs-fg-3);
252
- }
253
-
254
- .rp-chip-count {
255
- font-size: 11px;
256
- color: var(--obs-fg-1);
257
- font-weight: 500;
258
- }
259
-
260
- .rp-chip[data-dim="true"] { opacity: 0.55; }
261
-
262
- .rp-toggle,
263
- .sub-action,
264
- .sub-filter,
265
- .kind-filter,
266
- .sub-density-btn {
267
- border-radius: 2px;
268
- background: transparent;
269
- color: var(--obs-fg-2);
270
- border: 1px solid var(--obs-line);
271
- font-family: var(--mono);
272
- font-size: 10px;
273
- letter-spacing: 0.1em;
274
- text-transform: uppercase;
275
- }
276
-
277
- .rp-toggle {
278
- padding: 3px 8px;
279
- display: inline-flex;
280
- align-items: center;
281
- gap: 5px;
282
- }
283
-
284
- .rp-stepper {
285
- display: grid;
286
- padding: 12px 20px 14px;
287
- border-top: 1px solid var(--obs-line);
288
- gap: 0;
289
- }
290
-
291
- .rp-stepper[hidden] { display: none; }
292
-
293
- .rp-stepper-cell {
294
- position: relative;
295
- padding-right: 16px;
296
- display: grid;
297
- grid-template-rows: 14px auto auto;
298
- row-gap: 6px;
299
- align-content: start;
300
- }
301
-
302
- .rp-stepper-cell:last-child { padding-right: 0; }
303
-
304
- .rp-stepper-mark {
305
- position: relative;
306
- display: flex;
307
- align-items: center;
308
- height: 14px;
309
- z-index: 1;
310
- }
311
-
312
- .rp-stepper-mark > span {
313
- width: 6px;
314
- height: 6px;
315
- border-radius: 50%;
316
- flex: none;
317
- }
318
-
319
- .rp-stepper-mark[data-state="done"] > span { background: var(--obs-blue); }
320
-
321
- .rp-stepper-mark[data-state="active"] > span {
322
- width: 7px;
323
- height: 7px;
324
- background: var(--obs-sky);
325
- animation: pulse-dot 1.4s ease-in-out infinite;
326
- }
327
-
328
- .rp-stepper-mark[data-state="pending"] > span { background: var(--obs-fg-4); }
329
-
330
- .rp-stepper-label {
331
- font-family: var(--mono);
332
- font-size: 10px;
333
- letter-spacing: 0.12em;
334
- text-transform: uppercase;
335
- color: var(--obs-fg-1);
336
- white-space: nowrap;
337
- }
338
-
339
- .rp-stepper-desc {
340
- font-size: 11px;
341
- color: var(--obs-fg-3);
342
- white-space: nowrap;
343
- overflow: hidden;
344
- text-overflow: ellipsis;
345
- }
346
-
347
- .rp-stepper-conn {
348
- position: absolute;
349
- left: 14px;
350
- right: 0;
351
- top: 6px;
352
- height: 1px;
353
- background: var(--obs-line);
354
- z-index: 0;
355
- }
356
-
357
- .rp-stepper-cell[data-state="done"] .rp-stepper-conn { background: var(--obs-blue); }
358
-
359
- .sub-bar {
360
- display: flex;
361
- align-items: center;
362
- gap: 12px;
363
- padding: 8px 16px;
364
- border-bottom: 1px solid var(--obs-line);
365
- background: var(--obs-bg);
366
- flex: none;
367
- min-height: 41px;
368
- }
369
-
370
- .sub-count {
371
- font-family: var(--mono);
372
- font-size: 11px;
373
- color: var(--obs-fg-3);
374
- }
375
-
376
- .sub-count strong {
377
- color: var(--obs-fg-1);
378
- font-weight: 500;
379
- }
380
-
381
- .sub-filters,
382
- .sub-kind-filters {
383
- display: flex;
384
- gap: 4px;
385
- min-width: 0;
386
- }
387
-
388
- .sub-filter,
389
- .kind-filter {
390
- display: inline-flex;
391
- align-items: center;
392
- gap: 6px;
393
- padding: 3px 9px;
394
- white-space: nowrap;
395
- }
396
-
397
- .sub-filter[aria-pressed="true"],
398
- .kind-filter[aria-pressed="true"],
399
- .sub-density-btn[aria-pressed="true"] {
400
- background: var(--obs-bg-3);
401
- color: var(--obs-fg-1);
402
- }
403
-
404
- .sub-filter-count,
405
- .kind-filter-count { color: var(--obs-fg-3); }
406
-
407
- .sub-spacer {
408
- margin-left: auto;
409
- display: flex;
410
- align-items: center;
411
- gap: 8px;
412
- }
413
-
414
- .sub-density-btn {
415
- width: 26px;
416
- height: 22px;
417
- color: var(--obs-fg-3);
418
- }
419
-
420
- .sub-action {
421
- padding: 4px 9px;
422
- display: inline-flex;
423
- align-items: center;
424
- gap: 6px;
425
- }
426
-
427
- .sub-action[aria-pressed="true"] {
428
- background: var(--obs-amber-soft);
429
- color: var(--obs-amber);
430
- }
431
-
432
- .sub-action[data-mode-toggle="true"][aria-pressed="true"] {
433
- background: var(--obs-blue-soft);
434
- color: #8aa9ff;
435
- }
436
-
437
- .history-panel {
438
- position: fixed;
439
- inset: 0 auto 0 0;
440
- z-index: 30;
441
- width: min(390px, calc(100vw - 32px));
442
- display: flex;
443
- flex-direction: column;
444
- background: rgba(15, 15, 16, 0.98);
445
- border-right: 1px solid var(--obs-line-2);
446
- box-shadow: 24px 0 80px rgba(0, 0, 0, 0.45);
447
- }
448
-
449
- .history-panel[hidden] { display: none; }
450
-
451
- .history-head {
452
- display: flex;
453
- align-items: flex-start;
454
- justify-content: space-between;
455
- gap: 16px;
456
- padding: 16px;
457
- border-bottom: 1px solid var(--obs-line);
458
- }
459
-
460
- .history-head h2 {
461
- margin: 4px 0 0;
462
- font-size: 18px;
463
- line-height: 1.15;
464
- font-weight: 500;
465
- color: var(--obs-fg-1);
466
- }
467
-
468
- .history-close {
469
- width: 28px;
470
- height: 28px;
471
- border: 1px solid var(--obs-line);
472
- border-radius: 2px;
473
- background: transparent;
474
- color: var(--obs-fg-2);
475
- font-size: 18px;
476
- line-height: 1;
477
- }
478
-
479
- .history-current {
480
- padding: 12px 16px;
481
- border-bottom: 1px solid var(--obs-line);
482
- font-family: var(--mono);
483
- font-size: 10px;
484
- color: var(--obs-fg-3);
485
- }
486
-
487
- .history-list {
488
- flex: 1 1 0;
489
- min-height: 0;
490
- overflow: auto;
491
- }
492
-
493
- .history-run {
494
- display: grid;
495
- grid-template-columns: 1fr auto;
496
- gap: 8px 12px;
497
- padding: 12px 16px;
498
- border: 0;
499
- border-bottom: 1px solid var(--obs-line);
500
- background: transparent;
501
- color: inherit;
502
- text-align: left;
503
- text-decoration: none;
504
- }
505
-
506
- .history-run:hover,
507
- .history-run[data-active="true"] { background: var(--obs-bg-3); }
508
-
509
- .history-run[data-active="true"] { box-shadow: inset 2px 0 0 var(--obs-blue); }
510
-
511
- .history-run-id {
512
- font-family: var(--mono);
513
- font-size: 11px;
514
- color: var(--obs-fg-1);
515
- white-space: nowrap;
516
- overflow: hidden;
517
- text-overflow: ellipsis;
518
- }
519
-
520
- .history-run-meta,
521
- .history-run-counts {
522
- font-family: var(--mono);
523
- font-size: 10px;
524
- color: var(--obs-fg-3);
525
- white-space: nowrap;
526
- overflow: hidden;
527
- text-overflow: ellipsis;
528
- }
529
-
530
- .history-run-status {
531
- display: inline-flex;
532
- align-items: center;
533
- gap: 6px;
534
- font-family: var(--mono);
535
- font-size: 10px;
536
- color: var(--obs-fg-2);
537
- text-transform: uppercase;
538
- letter-spacing: 0.1em;
539
- }
540
-
541
- .history-empty {
542
- padding: 16px;
543
- color: var(--obs-fg-3);
544
- }
545
-
546
- .grid-shell {
547
- flex: 1;
548
- padding: 16px;
549
- min-height: 0;
550
- min-width: 0;
551
- overflow: auto;
552
- }
553
-
554
- .grid-shell[hidden] { display: none; }
555
-
556
- .tile-grid {
557
- position: relative;
558
- width: 100%;
559
- }
560
-
561
- .tile {
562
- display: grid;
563
- grid-template-columns: minmax(0, 1fr);
564
- grid-template-rows: auto minmax(0, 1fr) auto;
565
- background: var(--obs-bg-2);
566
- border: 1px solid var(--obs-line);
567
- position: relative;
568
- overflow: hidden;
569
- cursor: pointer;
570
- min-width: 0;
571
- }
572
-
573
- .tile-grid > .tile {
574
- position: absolute;
575
- margin: 0;
576
- }
577
-
578
- .tile:hover { border-color: var(--obs-line-2); }
579
-
580
- .tile[data-selected="true"] {
581
- box-shadow:
582
- 0 0 0 1px var(--obs-blue),
583
- 0 0 0 4px var(--obs-blue-soft);
584
- }
585
-
586
- .tile-head {
587
- display: flex;
588
- align-items: center;
589
- gap: 6px;
590
- padding: 3px 7px;
591
- height: 22px;
592
- border-bottom: 1px solid var(--obs-line);
593
- background: var(--obs-bg-1);
594
- flex: none;
595
- }
596
-
597
- .tile-idx {
598
- font-family: var(--mono);
599
- font-size: 9px;
600
- color: var(--obs-fg-3);
601
- letter-spacing: 0.05em;
602
- }
603
-
604
- .tile-role {
605
- font-family: var(--mono);
606
- font-size: 9px;
607
- letter-spacing: 0.08em;
608
- text-transform: uppercase;
609
- color: var(--obs-green);
610
- flex: none;
611
- }
612
-
613
- .tile-name {
614
- font-size: 11px;
615
- color: var(--obs-fg-1);
616
- white-space: nowrap;
617
- overflow: hidden;
618
- text-overflow: ellipsis;
619
- flex: 1;
620
- min-width: 0;
621
- }
622
-
623
- .tile-view {
624
- font-family: var(--mono);
625
- font-size: 9px;
626
- color: var(--obs-fg-3);
627
- letter-spacing: 0.06em;
628
- text-transform: uppercase;
629
- }
630
-
631
- .tile-stream-shell {
632
- position: relative;
633
- background: #050505;
634
- overflow: hidden;
635
- min-height: 0;
636
- min-width: 0;
637
- height: 100%;
638
- width: 100%;
639
- max-width: 100%;
640
- }
641
-
642
- .tile-stream-shell::after {
643
- content: "";
644
- position: absolute;
645
- inset: 0;
646
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
647
- pointer-events: none;
648
- }
649
-
650
- .media-surface {
651
- position: absolute;
652
- inset: 0;
653
- display: flex;
654
- align-items: center;
655
- justify-content: center;
656
- min-width: 0;
657
- overflow: hidden;
658
- }
659
-
660
- .media-surface > iframe,
661
- .media-surface > img {
662
- display: block;
663
- width: 100%;
664
- height: 100%;
665
- max-width: 100%;
666
- object-fit: contain;
667
- background: #050505;
668
- border: 0;
669
- }
670
-
671
- .tile .media-surface > iframe,
672
- .tile .media-surface > img { pointer-events: none; }
673
-
674
- .terminal-surface,
675
- .live-waiting-surface,
676
- .synthetic-codex,
677
- .placeholder-surface {
678
- width: 100%;
679
- height: 100%;
680
- }
681
-
682
- .terminal-surface {
683
- display: grid;
684
- grid-template-rows: auto minmax(0, 1fr);
685
- background: #050505;
686
- color: var(--obs-fg-2);
687
- font-family: var(--mono);
688
- overflow: hidden;
689
- }
690
-
691
- .terminal-bar {
692
- display: grid;
693
- grid-template-columns: auto minmax(0, 1fr) auto;
694
- gap: 8px;
695
- align-items: center;
696
- padding: 6px 8px;
697
- border-bottom: 1px solid var(--obs-line);
698
- background: rgba(255, 255, 255, 0.03);
699
- }
700
-
701
- .terminal-prompt { color: var(--obs-green); }
702
-
703
- .terminal-title {
704
- min-width: 0;
705
- overflow: hidden;
706
- text-overflow: ellipsis;
707
- white-space: nowrap;
708
- color: var(--obs-fg-1);
709
- font-size: 10px;
710
- }
711
-
712
- .terminal-status {
713
- color: var(--obs-fg-3);
714
- font-size: 9px;
715
- text-transform: uppercase;
716
- }
717
-
718
- .terminal-body {
719
- min-height: 0;
720
- overflow: hidden;
721
- padding: 8px;
722
- font-size: 10px;
723
- line-height: 1.45;
724
- }
725
-
726
- .focus .terminal-body {
727
- overflow: auto;
728
- font-size: 12px;
729
- }
730
-
731
- .terminal-line {
732
- display: grid;
733
- grid-template-columns: 28px minmax(0, 1fr);
734
- gap: 8px;
735
- min-width: 0;
736
- }
737
-
738
- .terminal-line-prefix {
739
- color: var(--obs-fg-4);
740
- text-align: right;
741
- }
742
-
743
- .terminal-line-text {
744
- color: var(--obs-fg-2);
745
- white-space: pre-wrap;
746
- overflow-wrap: anywhere;
747
- }
748
-
749
- .synthetic-codex,
750
- .placeholder-surface {
751
- background: #101010;
752
- padding: 10px;
753
- color: #181b1a;
754
- }
755
-
756
- .codex-frame {
757
- height: 100%;
758
- border-radius: 8px;
759
- overflow: hidden;
760
- background: #f7f8f4;
761
- box-shadow: 0 16px 40px rgba(0, 0, 0, 0.32);
762
- display: grid;
763
- grid-template-rows: auto minmax(0, 1fr);
764
- }
765
-
766
- .codex-chrome {
767
- height: 30px;
768
- display: flex;
769
- align-items: center;
770
- gap: 8px;
771
- padding: 0 10px;
772
- border-bottom: 1px solid #d9ded8;
773
- background: #eef1ed;
774
- font-family: var(--mono);
775
- font-size: 9px;
776
- color: #59645f;
777
- }
778
-
779
- .live-waiting-surface {
780
- position: relative;
781
- display: flex;
782
- align-items: center;
783
- justify-content: center;
784
- overflow: hidden;
785
- background: #020202;
786
- color: var(--obs-fg-2);
787
- font-family: var(--mono);
788
- }
789
-
790
- .live-waiting-surface::before {
791
- content: "";
792
- position: absolute;
793
- inset: 0;
794
- background:
795
- radial-gradient(circle at 50% 42%, rgba(63, 113, 250, 0.12), transparent 34%),
796
- linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px);
797
- background-size: auto, 100% 28px;
798
- opacity: 0.7;
799
- }
800
-
801
- .live-waiting-inner {
802
- position: relative;
803
- z-index: 1;
804
- width: min(360px, calc(100% - 32px));
805
- display: grid;
806
- justify-items: center;
807
- gap: 12px;
808
- text-align: center;
809
- }
810
-
811
- .live-spinner {
812
- width: 34px;
813
- height: 34px;
814
- border: 1px solid rgba(255, 255, 255, 0.18);
815
- border-top-color: var(--obs-blue);
816
- border-radius: 50%;
817
- animation: live-spin 1s linear infinite;
818
- }
819
-
820
- .live-waiting-title {
821
- color: var(--obs-fg-1);
822
- font-size: 12px;
823
- letter-spacing: 0.12em;
824
- text-transform: uppercase;
825
- }
826
-
827
- .live-waiting-url {
828
- max-width: 100%;
829
- color: var(--obs-fg-3);
830
- font-size: 10px;
831
- overflow: hidden;
832
- text-overflow: ellipsis;
833
- white-space: nowrap;
834
- }
835
-
836
- .live-waiting-status {
837
- max-width: 100%;
838
- color: var(--obs-fg-2);
839
- font-size: 10px;
840
- line-height: 1.5;
841
- overflow-wrap: anywhere;
842
- }
843
-
844
- .chrome-dot {
845
- width: 7px;
846
- height: 7px;
847
- border-radius: 50%;
848
- background: #c9d0ca;
849
- }
850
-
851
- .codex-url {
852
- min-width: 0;
853
- overflow: hidden;
854
- text-overflow: ellipsis;
855
- white-space: nowrap;
856
- }
857
-
858
- .codex-body {
859
- min-height: 0;
860
- display: grid;
861
- grid-template-columns: 120px minmax(0, 1fr);
862
- background: #f8f8f5;
863
- }
864
-
865
- .codex-rail {
866
- border-right: 1px solid #dadfda;
867
- padding: 12px;
868
- display: grid;
869
- align-content: start;
870
- gap: 8px;
871
- }
872
-
873
- .codex-thread {
874
- height: 24px;
875
- border-radius: 4px;
876
- background: #e8ece7;
877
- }
878
-
879
- .codex-main {
880
- padding: 16px;
881
- display: grid;
882
- align-content: start;
883
- gap: 12px;
884
- }
885
-
886
- .codex-bubble {
887
- min-height: 38px;
888
- border: 1px solid #d7ddd7;
889
- border-radius: 8px;
890
- background: white;
891
- }
892
-
893
- .codex-bubble.dark {
894
- background: #17201d;
895
- border-color: #17201d;
896
- }
897
-
898
- .placeholder-surface {
899
- display: flex;
900
- align-items: center;
901
- justify-content: center;
902
- }
903
-
904
- .placeholder-box {
905
- max-width: 320px;
906
- padding: 18px;
907
- border: 1px solid var(--obs-line-2);
908
- color: var(--obs-fg-2);
909
- background: var(--obs-bg-1);
910
- font-family: var(--mono);
911
- font-size: 11px;
912
- line-height: 1.45;
913
- }
914
-
915
- .tile-cap {
916
- height: 22px;
917
- display: grid;
918
- grid-template-columns: auto minmax(0, 1fr) 52px;
919
- align-items: center;
920
- gap: 8px;
921
- padding: 0 7px;
922
- border-top: 1px solid var(--obs-line);
923
- background: var(--obs-bg-1);
924
- }
925
-
926
- .tile-cap-eyebrow {
927
- font-family: var(--mono);
928
- font-size: 9px;
929
- letter-spacing: 0.1em;
930
- color: var(--obs-fg-3);
931
- text-transform: uppercase;
932
- }
933
-
934
- .tile-cap-text {
935
- min-width: 0;
936
- overflow: hidden;
937
- text-overflow: ellipsis;
938
- white-space: nowrap;
939
- color: var(--obs-fg-2);
940
- font-size: 11px;
941
- }
942
-
943
- .tile-cap-bar {
944
- height: 2px;
945
- background: var(--obs-line);
946
- overflow: hidden;
947
- }
948
-
949
- .tile-cap-bar > span {
950
- display: block;
951
- height: 100%;
952
- background: var(--obs-blue);
953
- }
954
-
955
- .tile-cap[data-status="failed"] .tile-cap-bar > span,
956
- .tile-cap[data-status="blocked"] .tile-cap-bar > span { background: var(--obs-red); }
957
-
958
- .tile-cap[data-status="complete"] .tile-cap-bar > span,
959
- .tile-cap[data-status="contract_proof_only"] .tile-cap-bar > span { background: var(--obs-green); }
960
-
961
- .pip[data-status="queued"] { color: var(--obs-fg-4); }
962
- .pip[data-status="preparing"] { color: var(--obs-sky); }
963
- .pip[data-status="running"] { color: var(--obs-blue); }
964
- .pip[data-status="complete"] { color: var(--obs-green); }
965
- .pip[data-status="contract_proof_only"] { color: var(--obs-green); }
966
- .pip[data-status="blocked"] { color: var(--obs-amber); }
967
- .pip[data-status="failed"] { color: var(--obs-red); }
968
-
969
- .focus {
970
- display: flex;
971
- flex: 1 1 0;
972
- min-height: 0;
973
- height: 100%;
974
- overflow: hidden;
975
- background: var(--obs-bg);
976
- }
977
-
978
- .focus[hidden] { display: none; }
979
-
980
- .focus-rail {
981
- width: 56px;
982
- flex: none;
983
- border-right: 1px solid var(--obs-line);
984
- background: var(--obs-bg-1);
985
- display: flex;
986
- flex-direction: column;
987
- padding: 10px 0;
988
- gap: 4px;
989
- overflow-y: auto;
990
- }
991
-
992
- .focus-rail-item {
993
- display: flex;
994
- flex-direction: column;
995
- align-items: center;
996
- gap: 3px;
997
- padding: 6px 4px;
998
- background: transparent;
999
- border: 0;
1000
- border-left: 2px solid transparent;
1001
- width: 100%;
1002
- }
1003
-
1004
- .focus-rail-item[data-selected="true"] {
1005
- background: var(--obs-bg-3);
1006
- border-left-color: var(--obs-blue);
1007
- }
1008
-
1009
- .focus-rail-idx {
1010
- font-family: var(--mono);
1011
- font-size: 9px;
1012
- color: var(--obs-fg-3);
1013
- }
1014
-
1015
- .focus-rail-item[data-selected="true"] .focus-rail-idx { color: var(--obs-fg-1); }
1016
-
1017
- .focus-stage {
1018
- flex: 1 1 0;
1019
- display: flex;
1020
- flex-direction: column;
1021
- min-width: 0;
1022
- min-height: 0;
1023
- background: #050505;
1024
- }
1025
-
1026
- .focus-toolbar {
1027
- display: flex;
1028
- align-items: center;
1029
- gap: 16px;
1030
- padding: 10px 16px;
1031
- background: var(--obs-bg-1);
1032
- border-bottom: 1px solid var(--obs-line);
1033
- flex: none;
1034
- }
1035
-
1036
- .focus-back {
1037
- display: inline-flex;
1038
- align-items: center;
1039
- gap: 6px;
1040
- padding: 5px 10px;
1041
- border-radius: 2px;
1042
- background: transparent;
1043
- color: var(--obs-fg-2);
1044
- border: 1px solid var(--obs-line-2);
1045
- font-family: var(--mono);
1046
- font-size: 10px;
1047
- letter-spacing: 0.12em;
1048
- text-transform: uppercase;
1049
- }
1050
-
1051
- .focus-id {
1052
- font-family: var(--mono);
1053
- font-size: 10px;
1054
- color: var(--obs-fg-3);
1055
- letter-spacing: 0.1em;
1056
- text-transform: uppercase;
1057
- }
1058
-
1059
- .focus-persona {
1060
- font-size: 13px;
1061
- color: var(--obs-fg-1);
1062
- }
1063
-
1064
- .focus-status-badge {
1065
- display: inline-flex;
1066
- align-items: center;
1067
- gap: 6px;
1068
- padding: 3px 8px;
1069
- border-radius: 2px;
1070
- font-family: var(--mono);
1071
- font-size: 10px;
1072
- letter-spacing: 0.12em;
1073
- text-transform: uppercase;
1074
- background: rgba(255, 255, 255, 0.06);
1075
- color: var(--obs-fg-1);
1076
- }
15
+ /* ============================================================
16
+ Mimetic Observer · redesign
17
+ Design tokens + shell. Dark is default; [data-theme="light"]
18
+ on <html> flips the palette. All accent/status colors are
19
+ semantic tokens so the whole UI re-themes from one place.
20
+ ============================================================ */
1077
21
 
1078
- .focus-status-badge[data-status="running"],
1079
- .focus-status-badge[data-status="preparing"] {
1080
- background: var(--obs-blue-soft);
1081
- color: #8aa9ff;
1082
- }
1083
-
1084
- .focus-status-badge[data-status="blocked"] {
1085
- background: var(--obs-amber-soft);
1086
- color: #e9b04b;
1087
- }
1088
-
1089
- .focus-status-badge[data-status="failed"] {
1090
- background: var(--obs-red-soft);
1091
- color: #ee8a7e;
1092
- }
1093
-
1094
- .focus-status-badge[data-status="complete"],
1095
- .focus-status-badge[data-status="contract_proof_only"] {
1096
- background: var(--obs-green-soft);
1097
- color: #6dd0a3;
1098
- }
1099
-
1100
- .focus-stats {
1101
- margin-left: auto;
1102
- font-family: var(--mono);
1103
- font-size: 11px;
1104
- color: var(--obs-fg-3);
1105
- }
1106
-
1107
- .focus-stats strong {
1108
- color: var(--obs-fg-1);
1109
- font-weight: 500;
1110
- }
1111
-
1112
- .focus-stage-area {
1113
- flex: 1 1 0;
1114
- min-height: 0;
1115
- min-width: 0;
1116
- padding: 0;
1117
- position: relative;
1118
- display: flex;
1119
- align-items: center;
1120
- justify-content: center;
1121
- overflow: auto;
22
+ :root {
23
+ /* surfaces */
24
+ --bg: #0a0b0d;
25
+ --surface-0: #0d0f12;
26
+ --surface-1: #14171b;
27
+ --surface-2: #1a1e23;
28
+ --surface-3: #232830;
29
+ --stream-void: #050608;
30
+
31
+ /* hairlines */
32
+ --line: rgba(255, 255, 255, 0.065);
33
+ --line-2: rgba(255, 255, 255, 0.11);
34
+ --line-3: rgba(255, 255, 255, 0.18);
35
+
36
+ /* text */
37
+ --text-1: #f2f4f6;
38
+ --text-2: #a6aeb6;
39
+ --text-3: #6d747d;
40
+ --text-4: #464c54;
41
+
42
+ /* brand + status */
43
+ --accent: #4d7cfe;
44
+ --accent-2: #7ea6ff;
45
+ --accent-ink: #cdddff;
46
+ --cyan: #41c8e6;
47
+ --green: #34d399;
48
+ --teal: #2dc3a6;
49
+ --amber: #f0a92b;
50
+ --red: #fb5d52;
51
+ --violet: #a78bfa;
52
+
53
+ --accent-soft: color-mix(in oklab, var(--accent) 18%, transparent);
54
+ --green-soft: color-mix(in oklab, var(--green) 16%, transparent);
55
+ --amber-soft: color-mix(in oklab, var(--amber) 16%, transparent);
56
+ --red-soft: color-mix(in oklab, var(--red) 16%, transparent);
57
+ --cyan-soft: color-mix(in oklab, var(--cyan) 16%, transparent);
58
+
59
+ --radius: 10px;
60
+ --radius-sm: 7px;
61
+ --radius-lg: 16px;
62
+ --radius-pill: 999px;
63
+
64
+ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
65
+ --shadow-2: 0 8px 30px rgba(0, 0, 0, 0.4);
66
+ --shadow-pop: 0 20px 60px rgba(0, 0, 0, 0.55);
67
+
68
+ --sans: "Geist", system-ui, -apple-system, "Segoe UI", sans-serif;
69
+ --mono: "Geist Mono", ui-monospace, "SF Mono", "JetBrains Mono", Consolas, monospace;
70
+
71
+ --ease: cubic-bezier(0.22, 1, 0.36, 1);
72
+ --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
73
+ --header-h: 54px;
74
+ --toolbar-h: 50px;
75
+
76
+ --tile-min: 380px;
77
+ --accent-color: var(--accent); /* overridable by Tweaks */
78
+ }
79
+
80
+ html[data-theme="light"] {
81
+ --bg: #eef0f3;
82
+ --surface-0: #f6f7f9;
83
+ --surface-1: #ffffff;
84
+ --surface-2: #ffffff;
85
+ --surface-3: #eceef2;
86
+ --stream-void: #0b0d10;
87
+ --line: rgba(15, 20, 30, 0.09);
88
+ --line-2: rgba(15, 20, 30, 0.14);
89
+ --line-3: rgba(15, 20, 30, 0.22);
90
+ --text-1: #14171c;
91
+ --text-2: #525a64;
92
+ --text-3: #828a94;
93
+ --text-4: #aeb5bd;
94
+ --shadow-1: 0 1px 2px rgba(20, 28, 40, 0.08);
95
+ --shadow-2: 0 8px 30px rgba(20, 28, 40, 0.12);
96
+ --shadow-pop: 0 20px 60px rgba(20, 28, 40, 0.22);
1122
97
  }
1123
98
 
1124
- .focus-stage-area .tile-stream-shell {
1125
- flex: none;
1126
- width: auto;
1127
- height: auto;
1128
- margin: 0 auto;
1129
- aspect-ratio: var(--stream-aspect, 16 / 9);
1130
- }
99
+ * { box-sizing: border-box; }
1131
100
 
1132
- .focus-side {
1133
- width: 380px;
1134
- flex: none;
1135
- border-left: 1px solid var(--obs-line);
1136
- background: var(--obs-bg-1);
1137
- display: flex;
1138
- flex-direction: column;
1139
- min-height: 0;
101
+ html, body {
102
+ margin: 0;
1140
103
  height: 100%;
1141
104
  overflow: hidden;
1142
105
  }
1143
106
 
1144
- .focus-side-context {
1145
- flex: 0 1 auto;
1146
- max-height: min(46vh, 430px);
1147
- min-height: 0;
1148
- overflow-y: auto;
1149
- border-bottom: 1px solid var(--obs-line);
1150
- }
1151
-
1152
- .focus-side-head { padding: 20px 20px 16px; }
1153
-
1154
- .focus-side-head h2 {
1155
- font-family: var(--display);
1156
- font-size: 22px;
1157
- font-weight: 400;
1158
- margin: 6px 0 0;
1159
- line-height: 1.15;
1160
- color: var(--obs-fg-1);
1161
- }
1162
-
1163
- .focus-side-goal { margin-top: 14px; }
1164
-
1165
- .focus-side-goal-text {
107
+ body {
108
+ background: var(--bg);
109
+ color: var(--text-1);
110
+ font-family: var(--sans);
1166
111
  font-size: 13px;
1167
- color: var(--obs-fg-2);
1168
- margin-top: 4px;
1169
112
  line-height: 1.5;
1170
- overflow-wrap: anywhere;
1171
- }
1172
-
1173
- .focus-side-thinking {
1174
- padding: 14px 20px;
1175
- background: var(--obs-bg-2);
1176
- }
1177
-
1178
- .focus-side-thinking-row {
1179
- display: flex;
1180
- align-items: center;
1181
- gap: 6px;
1182
- margin-bottom: 6px;
113
+ -webkit-font-smoothing: antialiased;
114
+ text-rendering: optimizeLegibility;
1183
115
  }
1184
116
 
1185
- .focus-side-thinking-row .obs-eyebrow { color: var(--obs-blue); }
117
+ button { font-family: inherit; cursor: pointer; color: inherit; background: none; border: none; }
118
+ a { color: inherit; text-decoration: none; }
1186
119
 
1187
- .focus-side-thinking-text {
1188
- font-size: 13px;
1189
- color: var(--obs-fg-1);
1190
- font-style: italic;
1191
- line-height: 1.45;
120
+ :focus-visible {
121
+ outline: 2px solid var(--accent-color);
122
+ outline-offset: 2px;
123
+ border-radius: 4px;
1192
124
  }
1193
125
 
1194
- .focus-tabs {
1195
- display: flex;
1196
- border-bottom: 1px solid var(--obs-line);
1197
- flex: none;
1198
- overflow-x: auto;
1199
- overflow-y: hidden;
1200
- scrollbar-width: thin;
1201
- }
126
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
127
+ ::-webkit-scrollbar-track { background: transparent; }
128
+ ::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 999px; border: 2px solid transparent; background-clip: padding-box; }
129
+ ::-webkit-scrollbar-thumb:hover { background: var(--line-3); background-clip: padding-box; }
1202
130
 
1203
- .focus-tab {
1204
- padding: 10px 12px;
1205
- background: transparent;
1206
- color: var(--obs-fg-3);
1207
- border: 0;
1208
- border-bottom: 1px solid transparent;
131
+ .mono { font-family: var(--mono); font-feature-settings: "tnum" 1, "zero" 1; }
132
+ .eyebrow {
1209
133
  font-family: var(--mono);
1210
- font-size: 10px;
1211
- letter-spacing: 0.12em;
134
+ font-size: 9.5px;
135
+ letter-spacing: 0.16em;
1212
136
  text-transform: uppercase;
1213
- display: inline-flex;
1214
- align-items: center;
1215
- gap: 5px;
1216
- flex: 0 0 auto;
1217
- white-space: nowrap;
1218
- }
1219
-
1220
- .focus-tab[aria-selected="true"] {
1221
- background: var(--obs-bg-2);
1222
- color: var(--obs-fg-1);
1223
- border-bottom-color: var(--obs-blue);
137
+ color: var(--text-3);
1224
138
  }
1225
139
 
1226
- .focus-tab-badge {
1227
- font-size: 9px;
1228
- color: var(--obs-fg-3);
1229
- }
1230
-
1231
- .focus-tabbody {
1232
- flex: 1 1 0;
1233
- min-height: 0;
1234
- overflow-y: auto;
1235
- }
1236
-
1237
- .event-row,
1238
- .artifact-row {
140
+ /* ============================================================ APP SHELL */
141
+ .app {
1239
142
  display: flex;
1240
- gap: 10px;
1241
- padding: 10px 16px;
1242
- border-bottom: 1px solid var(--obs-line);
1243
- }
1244
-
1245
- .event-row-icon {
1246
- font-family: var(--mono);
1247
- font-size: 11px;
1248
- color: var(--obs-fg-3);
1249
- flex: none;
1250
- margin-top: 2px;
1251
- text-transform: uppercase;
1252
- letter-spacing: 0.08em;
143
+ flex-direction: column;
144
+ height: 100vh;
145
+ height: 100dvh;
146
+ position: relative;
1253
147
  }
148
+ .hdr, .toolbar { flex: none; }
149
+ .stage { flex: 1 1 0; }
1254
150
 
1255
- .event-row[data-kind="warn"] .event-row-icon,
1256
- .event-row[data-kind="blocked"] .event-row-icon { color: var(--obs-amber); }
1257
-
1258
- .event-row[data-kind="error"] .event-row-icon,
1259
- .event-row[data-kind="failed"] .event-row-icon { color: var(--obs-red); }
1260
-
1261
- .event-row[data-kind="complete"] .event-row-icon { color: var(--obs-green); }
1262
-
1263
- .event-row-text,
1264
- .artifact-row-text {
1265
- font-size: 12px;
1266
- color: var(--obs-fg-1);
1267
- line-height: 1.4;
1268
- overflow-wrap: anywhere;
151
+ /* top-edge run progress line */
152
+ .runline {
153
+ position: absolute;
154
+ top: 0; left: 0; right: 0;
155
+ height: 2px;
156
+ z-index: 60;
157
+ background: transparent;
158
+ pointer-events: none;
1269
159
  }
1270
-
1271
- .event-row-meta,
1272
- .artifact-row-meta {
1273
- font-family: var(--mono);
1274
- font-size: 10px;
1275
- color: var(--obs-fg-3);
1276
- letter-spacing: 0.08em;
1277
- text-transform: uppercase;
1278
- margin-top: 2px;
160
+ .runline > span {
161
+ display: block;
162
+ height: 100%;
163
+ background: linear-gradient(90deg, var(--accent-color), var(--accent-2));
164
+ box-shadow: 0 0 12px color-mix(in oklab, var(--accent-color) 70%, transparent);
165
+ transition: width 900ms var(--ease-out);
166
+ border-radius: 0 2px 2px 0;
1279
167
  }
168
+ .runline[data-status="failed"] > span { background: linear-gradient(90deg, var(--red), #ff8b82); box-shadow: 0 0 12px var(--red-soft); }
169
+ .runline[data-status="passed"] > span,
170
+ .runline[data-status="complete"] > span { background: linear-gradient(90deg, var(--green), #7ef0c4); box-shadow: 0 0 12px var(--green-soft); }
1280
171
 
1281
- .artifact-row a {
1282
- color: var(--obs-fg-1);
1283
- text-decoration: none;
172
+ /* ============================================================ HEADER */
173
+ .hdr {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 14px;
177
+ height: var(--header-h);
178
+ padding: 0 16px;
179
+ background: var(--surface-0);
180
+ border-bottom: 1px solid var(--line);
181
+ position: relative;
182
+ z-index: 40;
183
+ }
184
+ .hdr-brand { display: flex; align-items: center; gap: 10px; flex: none; }
185
+ .brand-mark {
186
+ width: 26px; height: 26px; border-radius: 8px;
187
+ display: grid; place-items: center;
188
+ background: radial-gradient(circle at 30% 25%, color-mix(in oklab, var(--accent-color) 55%, transparent), transparent 70%), var(--surface-2);
189
+ border: 1px solid var(--line-2);
190
+ position: relative;
191
+ overflow: hidden;
192
+ color: var(--accent-ink);
193
+ }
194
+ .brand-mark svg { width: 16px; height: 16px; }
195
+ .brand-word { font-size: 13px; font-weight: 600; letter-spacing: -0.01em; }
196
+ .brand-word b { color: var(--accent-color); font-weight: 600; }
197
+
198
+ .hdr-run {
199
+ display: flex; flex-direction: column; gap: 1px; min-width: 0;
200
+ padding-left: 14px; margin-left: 2px;
201
+ border-left: 1px solid var(--line);
202
+ }
203
+ .hdr-run-title {
204
+ font-size: 13.5px; font-weight: 560; color: var(--text-1);
205
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 42vw;
206
+ letter-spacing: -0.01em;
207
+ }
208
+ .hdr-run-sub {
209
+ display: flex; align-items: center; gap: 7px;
210
+ font-size: 11px; color: var(--text-3); min-width: 0;
211
+ }
212
+ .hdr-run-sub .dot-sep { width: 2.5px; height: 2.5px; border-radius: 50%; background: var(--text-4); flex: none; }
213
+ .hdr-persona { color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
214
+ .run-chip {
215
+ font-family: var(--mono); font-size: 10px; color: var(--text-3);
216
+ background: var(--surface-2); border: 1px solid var(--line);
217
+ padding: 1px 6px; border-radius: var(--radius-sm); cursor: pointer;
218
+ white-space: nowrap; transition: border-color .15s, color .15s;
219
+ }
220
+ .run-chip:hover { border-color: var(--line-3); color: var(--text-1); }
221
+
222
+ .hdr-spacer { flex: 1; }
223
+
224
+ /* status pill */
225
+ .status-pill {
226
+ display: inline-flex; align-items: center; gap: 8px;
227
+ height: 30px; padding: 0 12px 0 11px;
228
+ border-radius: var(--radius-pill);
229
+ background: var(--surface-2);
230
+ border: 1px solid var(--line-2);
231
+ font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase;
232
+ color: var(--text-1); flex: none;
233
+ }
234
+ .status-pill[data-tone="running"] { background: var(--accent-soft); border-color: color-mix(in oklab, var(--accent) 35%, transparent); color: var(--accent-ink); }
235
+ .status-pill[data-tone="passed"], .status-pill[data-tone="complete"] { background: var(--green-soft); border-color: color-mix(in oklab, var(--green) 35%, transparent); color: color-mix(in oklab, var(--green) 70%, white); }
236
+ .status-pill[data-tone="blocked"] { background: var(--amber-soft); border-color: color-mix(in oklab, var(--amber) 35%, transparent); color: color-mix(in oklab, var(--amber) 80%, white); }
237
+ .status-pill[data-tone="failed"] { background: var(--red-soft); border-color: color-mix(in oklab, var(--red) 35%, transparent); color: color-mix(in oklab, var(--red) 75%, white); }
238
+ .status-pill .pct { color: inherit; opacity: .65; }
239
+
240
+ /* lane counts */
241
+ .lane-counts { display: flex; align-items: center; gap: 2px; flex: none; }
242
+ .lane-count {
243
+ display: inline-flex; align-items: center; gap: 6px;
244
+ padding: 5px 9px; border-radius: var(--radius-sm);
245
+ font-family: var(--mono); font-size: 11px; color: var(--text-2);
246
+ transition: background .15s;
247
+ }
248
+ .lane-count:hover { background: var(--surface-2); }
249
+ .lane-count b { color: var(--text-1); font-weight: 600; }
250
+ .lane-count[data-dim="true"] { opacity: .4; }
251
+
252
+ .icon-btn {
253
+ width: 32px; height: 32px; border-radius: var(--radius-sm);
254
+ display: grid; place-items: center; color: var(--text-2);
255
+ border: 1px solid transparent; transition: background .15s, color .15s, border-color .15s; flex: none;
256
+ }
257
+ .icon-btn:hover { background: var(--surface-2); color: var(--text-1); }
258
+ .icon-btn[aria-pressed="true"] { background: var(--surface-3); color: var(--text-1); border-color: var(--line-2); }
259
+ .icon-btn svg { width: 17px; height: 17px; }
260
+
261
+ .hdr-runs {
262
+ display: inline-flex; align-items: center; gap: 7px;
263
+ height: 32px; padding: 0 12px; border-radius: var(--radius-sm);
264
+ border: 1px solid var(--line-2); color: var(--text-2);
265
+ font-size: 12px; transition: background .15s, color .15s; flex: none;
266
+ }
267
+ .hdr-runs:hover { background: var(--surface-2); color: var(--text-1); }
268
+ .hdr-runs svg { width: 15px; height: 15px; }
269
+
270
+ /* ============================================================ TOOLBAR */
271
+ .toolbar {
272
+ display: flex; align-items: center; gap: 10px;
273
+ height: var(--toolbar-h); padding: 0 16px;
274
+ background: var(--surface-0); border-bottom: 1px solid var(--line);
275
+ position: relative; z-index: 30;
276
+ overflow-x: auto; overflow-y: hidden;
277
+ scrollbar-width: none;
278
+ }
279
+ .toolbar::-webkit-scrollbar { display: none; }
280
+
281
+ .filter-group { display: flex; align-items: center; gap: 3px; flex: none; }
282
+ .chip {
283
+ display: inline-flex; align-items: center; gap: 6px;
284
+ height: 28px; padding: 0 10px; border-radius: var(--radius-pill);
285
+ border: 1px solid var(--line); color: var(--text-2);
286
+ font-size: 11.5px; white-space: nowrap;
287
+ transition: background .15s, color .15s, border-color .15s;
288
+ }
289
+ .chip:hover { border-color: var(--line-2); color: var(--text-1); }
290
+ .chip[aria-pressed="true"] { background: var(--surface-3); color: var(--text-1); border-color: var(--line-2); }
291
+ .chip .chip-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }
292
+ .chip .chip-n { font-family: var(--mono); font-size: 10.5px; color: var(--text-3); }
293
+ .chip[aria-pressed="true"] .chip-n { color: var(--text-2); }
294
+
295
+ .tb-sep { width: 1px; height: 22px; background: var(--line); flex: none; }
296
+ .tb-spacer { flex: 1; min-width: 8px; }
297
+
298
+ /* search */
299
+ .tb-search {
300
+ display: flex; align-items: center; gap: 7px;
301
+ height: 28px; padding: 0 10px; border-radius: var(--radius-sm);
302
+ border: 1px solid var(--line); color: var(--text-3); flex: none;
303
+ min-width: 150px; transition: border-color .15s;
304
+ }
305
+ .tb-search:focus-within { border-color: var(--line-3); }
306
+ .tb-search svg { width: 14px; height: 14px; flex: none; }
307
+ .tb-search input {
308
+ background: none; border: none; color: var(--text-1); font-family: var(--sans);
309
+ font-size: 12px; width: 100%; outline: none;
310
+ }
311
+ .tb-search input::placeholder { color: var(--text-4); }
312
+
313
+ /* segmented control (density, media, view) */
314
+ .seg {
315
+ display: inline-flex; align-items: center; padding: 2px;
316
+ background: var(--surface-1); border: 1px solid var(--line); border-radius: var(--radius-sm); flex: none;
317
+ }
318
+ .seg button {
319
+ display: inline-flex; align-items: center; gap: 6px;
320
+ height: 24px; padding: 0 9px; border-radius: 5px;
321
+ font-size: 11px; color: var(--text-3); white-space: nowrap;
322
+ transition: color .15s, background .15s;
323
+ }
324
+ .seg button svg { width: 14px; height: 14px; }
325
+ .seg button:hover { color: var(--text-1); }
326
+ .seg button[aria-pressed="true"] { background: var(--surface-3); color: var(--text-1); box-shadow: var(--shadow-1); }
327
+ .seg button:disabled { opacity: .4; cursor: not-allowed; }
328
+
329
+ .tb-label { font-size: 10px; letter-spacing: .14em; text-transform: uppercase; color: var(--text-4); font-family: var(--mono); flex: none; }
330
+
331
+ /* ============================================================ GRID */
332
+ .stage { position: relative; min-height: 0; overflow: hidden; }
333
+ .grid-scroll { height: 100%; overflow-y: auto; overflow-x: hidden; padding: 16px; }
334
+ .grid {
335
+ display: grid;
336
+ gap: 14px;
337
+ grid-template-columns: repeat(auto-fill, minmax(min(var(--tile-min), 100%), 1fr));
338
+ align-content: start;
1284
339
  }
1285
340
 
1286
- @container rp-bar (max-width: 1100px) {
1287
- .rp-meta { display: none; }
341
+ .tile {
342
+ display: flex; flex-direction: column;
343
+ background: var(--surface-1);
344
+ border: 1px solid var(--line);
345
+ border-radius: var(--radius);
346
+ overflow: hidden;
347
+ cursor: pointer;
348
+ position: relative;
349
+ transition: border-color .18s var(--ease), transform .18s var(--ease), box-shadow .18s var(--ease);
1288
350
  }
351
+ .tile:hover { border-color: var(--line-3); transform: translateY(-2px); box-shadow: var(--shadow-2); }
352
+ .tile:hover .tile-open { opacity: 1; transform: none; }
353
+ .tile[data-selected="true"] { border-color: color-mix(in oklab, var(--accent) 60%, transparent); box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 45%, transparent); }
1289
354
 
1290
- @keyframes pulse-dot {
1291
- 0%, 100% { transform: scale(1); opacity: 0.65; }
1292
- 50% { transform: scale(1.35); opacity: 1; }
355
+ .tile-head {
356
+ display: flex; align-items: center; gap: 8px;
357
+ padding: 0 10px; height: 32px; flex: none;
358
+ border-bottom: 1px solid var(--line);
359
+ background: var(--surface-1);
360
+ }
361
+ .tile-idx { font-family: var(--mono); font-size: 10px; color: var(--text-4); flex: none; }
362
+ .tile-name { font-size: 12px; font-weight: 520; color: var(--text-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; }
363
+ .kind-badge {
364
+ font-family: var(--mono); font-size: 9px; letter-spacing: .08em; text-transform: uppercase;
365
+ padding: 2px 6px; border-radius: 5px; flex: none;
366
+ background: var(--surface-3); color: var(--text-2);
367
+ }
368
+ .kind-badge[data-kind="ui"], .kind-badge[data-kind="browser"] { color: var(--violet); background: color-mix(in oklab, var(--violet) 14%, transparent); }
369
+ .kind-badge[data-kind="terminal"] { color: var(--green); background: var(--green-soft); }
370
+ .kind-badge[data-kind="tui"] { color: var(--cyan); background: var(--cyan-soft); }
371
+ .kind-badge[data-kind="codex-ui"] { color: var(--accent-2); background: var(--accent-soft); }
372
+ .tile-dims { font-family: var(--mono); font-size: 9px; color: var(--text-4); flex: none; }
373
+ .tile-open {
374
+ width: 22px; height: 22px; border-radius: 5px; display: grid; place-items: center;
375
+ color: var(--text-2); background: var(--surface-2); border: 1px solid var(--line-2);
376
+ opacity: 0; transform: translateX(3px); transition: opacity .15s, transform .15s; flex: none;
377
+ }
378
+ .tile-open svg { width: 13px; height: 13px; }
379
+
380
+ /* status pip */
381
+ .pip { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--text-4); position: relative; }
382
+ .pip[data-status="queued"] { background: var(--text-4); }
383
+ .pip[data-status="preparing"]{ background: var(--cyan); }
384
+ .pip[data-status="running"] { background: var(--accent); }
385
+ .pip[data-status="passed"], .pip[data-status="complete"] { background: var(--green); }
386
+ .pip[data-status="contract_proof_only"] { background: var(--teal); }
387
+ .pip[data-status="blocked"], .pip[data-status="timed_out"] { background: var(--amber); }
388
+ .pip[data-status="failed"] { background: var(--red); }
389
+ .pip[data-live="true"]::after {
390
+ content: ""; position: absolute; inset: -3px; border-radius: 50%;
391
+ background: currentColor; opacity: .35; animation: ping 1.6s var(--ease-out) infinite;
392
+ }
393
+ .pip[data-status="running"][data-live="true"] { color: var(--accent); }
394
+ .pip[data-status="preparing"][data-live="true"] { color: var(--cyan); }
395
+
396
+ .tile-surface {
397
+ position: relative; background: var(--stream-void);
398
+ aspect-ratio: var(--aspect, 16 / 9);
399
+ width: 100%; overflow: hidden; flex: none;
400
+ }
401
+ .tile-foot {
402
+ display: flex; align-items: center; gap: 8px;
403
+ padding: 0 10px; height: 30px; flex: none;
404
+ border-top: 1px solid var(--line); background: var(--surface-1);
405
+ }
406
+ .tile-foot-text { font-size: 11px; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; }
407
+ .tile-foot-text .now-dot { color: var(--accent); margin-right: 6px; }
408
+ .mini-prog { width: 46px; height: 3px; border-radius: 2px; background: var(--line); overflow: hidden; flex: none; }
409
+ .mini-prog > span { display: block; height: 100%; background: var(--accent); transition: width .6s var(--ease-out); }
410
+ .tile-foot[data-status="failed"] .mini-prog > span { background: var(--red); }
411
+ .tile-foot[data-status="blocked"] .mini-prog > span,
412
+ .tile-foot[data-status="timed_out"] .mini-prog > span { background: var(--amber); }
413
+ .tile-foot[data-status="passed"] .mini-prog > span,
414
+ .tile-foot[data-status="complete"] .mini-prog > span,
415
+ .tile-foot[data-status="contract_proof_only"] .mini-prog > span { background: var(--green); }
416
+
417
+ /* ============================================================ STREAM SURFACES */
418
+ .surface-fill { position: absolute; inset: 0; width: 100%; height: 100%; }
419
+
420
+ /* browser/ui mock */
421
+ .bw { position: absolute; inset: 0; display: flex; flex-direction: column; background: #0b0d10; }
422
+ .bw-chrome {
423
+ display: flex; align-items: center; gap: 6px; padding: 0 9px; height: 26px; flex: none;
424
+ background: #16181c; border-bottom: 1px solid rgba(255,255,255,.06);
425
+ }
426
+ .bw-dots { display: flex; gap: 4px; }
427
+ .bw-dots i { width: 7px; height: 7px; border-radius: 50%; background: #353a40; }
428
+ .bw-url {
429
+ flex: 1; height: 16px; border-radius: 4px; background: #0d0f12; border: 1px solid rgba(255,255,255,.06);
430
+ display: flex; align-items: center; padding: 0 7px; gap: 5px;
431
+ font-family: var(--mono); font-size: 8.5px; color: #6f767e; min-width: 0;
432
+ }
433
+ .bw-url svg { width: 8px; height: 8px; flex: none; }
434
+ .bw-url span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
435
+ .bw-viewport { flex: 1; position: relative; overflow: hidden; background: #fbfcfd; }
436
+ .bw-app-wait { position: absolute; inset: 0; display: grid; place-items: center; align-content: center; gap: 8px; background: #fbfcfd; color: #5a626c; text-align: center; padding: 16px; }
437
+ .bw-app-wait .wait-spinner { border-color: rgba(20,28,40,.12); border-top-color: var(--accent); }
438
+ .bw-cursor {
439
+ position: absolute; width: 16px; height: 16px; z-index: 5; pointer-events: none;
440
+ transition: left 1.1s var(--ease), top 1.1s var(--ease);
441
+ filter: drop-shadow(0 1px 2px rgba(0,0,0,.4));
442
+ }
443
+ .bw-click {
444
+ position: absolute; width: 26px; height: 26px; border-radius: 50%; z-index: 4;
445
+ border: 2px solid var(--accent); margin: -13px 0 0 -13px; pointer-events: none;
446
+ animation: clickpulse 1.4s var(--ease-out) infinite;
447
+ }
448
+
449
+ /* the fake app being driven */
450
+ .app-mock { position: absolute; inset: 0; padding: 14px; color: #1a1d22; font-size: 10px; }
451
+ .app-mock .am-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
452
+ .am-logo { display: flex; align-items: center; gap: 6px; font-weight: 700; font-size: 11px; color: #14171c; }
453
+ .am-logo i { width: 14px; height: 14px; border-radius: 4px; background: linear-gradient(135deg, var(--accent), var(--violet)); }
454
+ .am-nav { display: flex; gap: 10px; color: #8a929b; font-size: 9px; }
455
+ .am-h { font-size: 16px; font-weight: 700; letter-spacing: -0.02em; color: #11141a; margin: 6px 0 3px; }
456
+ .am-p { color: #6a727b; line-height: 1.45; max-width: 80%; }
457
+ .am-field { margin-top: 10px; height: 26px; border-radius: 6px; border: 1px solid #e2e6ea; background: #fff; display: flex; align-items: center; padding: 0 9px; color: #9aa0a8; font-size: 9px; }
458
+ .am-field[data-focus="true"] { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 18%, transparent); }
459
+ .am-btn { margin-top: 9px; height: 28px; border-radius: 7px; background: #11141a; color: #fff; display: inline-flex; align-items: center; padding: 0 16px; font-size: 10px; font-weight: 600; }
460
+ .am-btn[data-variant="accent"] { background: var(--accent); }
461
+ .am-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 10px; }
462
+ .am-card { border: 1px solid #e6e9ed; border-radius: 8px; padding: 9px; background: #fff; }
463
+ .am-card .am-ck { height: 5px; width: 40%; background: #e9edf1; border-radius: 3px; margin-bottom: 6px; }
464
+ .am-card .am-cl { height: 4px; width: 80%; background: #f0f3f6; border-radius: 3px; margin-bottom: 4px; }
465
+
466
+ /* terminal */
467
+ .term { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--stream-void); font-family: var(--mono); }
468
+ .term-bar { display: flex; align-items: center; gap: 7px; padding: 0 10px; height: 24px; flex: none; background: rgba(255,255,255,.03); border-bottom: 1px solid var(--line); }
469
+ .term-bar .tprompt { color: var(--green); font-size: 11px; }
470
+ .term-bar .ttitle { flex: 1; font-size: 10px; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
471
+ .term-bar .tstat { font-size: 8.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--text-3); }
472
+ .term-body { flex: 1; min-height: 0; overflow: hidden; padding: 9px 10px; font-size: 11px; line-height: 1.55; display: flex; flex-direction: column; justify-content: flex-end; }
473
+ .focus-stage-area .term-body { justify-content: flex-start; overflow-y: auto; font-size: 12.5px; }
474
+ .term-line { display: flex; gap: 9px; white-space: pre-wrap; overflow-wrap: anywhere; animation: line-in .25s var(--ease-out) both; }
475
+ .term-num { color: var(--text-4); flex: none; width: 18px; text-align: right; user-select: none; }
476
+ .term-txt { color: var(--text-2); }
477
+ .term-txt.ok { color: var(--green); }
478
+ .term-txt.warn { color: var(--amber); }
479
+ .term-txt.err { color: var(--red); }
480
+ .term-txt.cmd { color: var(--text-1); }
481
+ .term-txt.dim { color: var(--text-3); }
482
+ .term-caret { display: inline-block; width: 7px; height: 13px; background: var(--green); vertical-align: middle; animation: blink 1.1s steps(2) infinite; margin-left: 2px; }
483
+
484
+ /* codex agent session */
485
+ .codex { position: absolute; inset: 0; display: flex; flex-direction: column; background: #0c0e11; }
486
+ .codex-bar { display: flex; align-items: center; gap: 7px; padding: 0 10px; height: 26px; flex: none; background: #131519; border-bottom: 1px solid var(--line); font-family: var(--mono); font-size: 9px; color: var(--text-3); }
487
+ .codex-bar .cx-spark { color: var(--accent-2); display: inline-flex; }
488
+ .codex-body { flex: 1; min-height: 0; overflow: hidden; padding: 11px; display: flex; flex-direction: column; gap: 8px; }
489
+ .focus-stage-area .codex-body { overflow-y: auto; }
490
+ .cx-msg { max-width: 86%; padding: 7px 10px; border-radius: 10px; font-size: 10.5px; line-height: 1.45; animation: line-in .3s var(--ease-out) both; }
491
+ .cx-msg.user { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 3px; }
492
+ .cx-msg.agent { align-self: flex-start; background: var(--surface-2); color: var(--text-1); border: 1px solid var(--line); border-bottom-left-radius: 3px; }
493
+ .cx-tool { align-self: flex-start; display: inline-flex; align-items: center; gap: 7px; font-family: var(--mono); font-size: 9.5px; color: var(--text-3); padding: 5px 9px; border-radius: 7px; border: 1px solid var(--line); background: var(--surface-1); }
494
+ .cx-tool .cx-spin { width: 11px; height: 11px; border-radius: 50%; border: 1.5px solid var(--line-3); border-top-color: var(--accent); animation: spin 1s linear infinite; flex: none; }
495
+ .cx-tool svg { width: 12px; height: 12px; color: var(--green); }
496
+
497
+ /* waiting / proof / empty surface */
498
+ .wait { position: absolute; inset: 0; display: grid; place-items: center; background:
499
+ radial-gradient(circle at 50% 38%, color-mix(in oklab, var(--accent) 12%, transparent), transparent 42%),
500
+ repeating-linear-gradient(0deg, transparent 0 26px, color-mix(in oklab, white 3%, transparent) 26px 27px);
501
+ text-align: center; padding: 16px;
502
+ }
503
+ .wait-inner { display: grid; justify-items: center; gap: 11px; max-width: 80%; }
504
+ .wait-spinner { width: 30px; height: 30px; border-radius: 50%; border: 2px solid var(--line-2); border-top-color: var(--accent); animation: spin 1.1s linear infinite; }
505
+ .wait-icon { width: 34px; height: 34px; display: grid; place-items: center; border-radius: 9px; background: var(--surface-2); border: 1px solid var(--line-2); }
506
+ .wait-icon svg { width: 18px; height: 18px; }
507
+ .wait-icon[data-tone="amber"] { color: var(--amber); border-color: color-mix(in oklab, var(--amber) 35%, transparent); }
508
+ .wait-icon[data-tone="red"] { color: var(--red); border-color: color-mix(in oklab, var(--red) 35%, transparent); }
509
+ .wait-icon[data-tone="green"] { color: var(--green); border-color: color-mix(in oklab, var(--green) 35%, transparent); }
510
+ .wait-title { font-family: var(--mono); font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--text-1); }
511
+ .wait-sub { font-size: 11px; color: var(--text-3); line-height: 1.5; }
512
+
513
+ /* tile live tag overlay */
514
+ .live-tag {
515
+ position: absolute; top: 8px; right: 8px; z-index: 6;
516
+ display: inline-flex; align-items: center; gap: 5px;
517
+ padding: 3px 8px; border-radius: var(--radius-pill);
518
+ background: rgba(8,10,13,.7); backdrop-filter: blur(8px);
519
+ border: 1px solid var(--line-2);
520
+ font-family: var(--mono); font-size: 8.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--text-1);
521
+ }
522
+
523
+ /* ============================================================ FOCUS VIEW */
524
+ .focus {
525
+ display: grid;
526
+ grid-template-columns: var(--rail-w, 188px) 1fr var(--side-w, 360px);
527
+ height: 100%; min-height: 0;
1293
528
  }
529
+ .focus[data-rail="collapsed"] { --rail-w: 0px; }
1294
530
 
1295
- @keyframes live-spin {
1296
- to { transform: rotate(360deg); }
531
+ /* breadcrumb bar lives in the stage column header */
532
+ .focus-rail {
533
+ background: var(--surface-0); border-right: 1px solid var(--line);
534
+ display: flex; flex-direction: column; min-height: 0; overflow: hidden;
535
+ transition: width .2s var(--ease);
536
+ }
537
+ .focus-rail-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px 8px; }
538
+ .focus-rail-list { flex: 1; min-height: 0; overflow-y: auto; padding: 0 8px 12px; display: flex; flex-direction: column; gap: 4px; }
539
+ .rail-item {
540
+ display: flex; align-items: center; gap: 9px; padding: 8px 9px; border-radius: var(--radius-sm);
541
+ text-align: left; width: 100%; transition: background .14s; position: relative;
542
+ }
543
+ .rail-item:hover { background: var(--surface-2); }
544
+ .rail-item[data-selected="true"] { background: var(--surface-3); }
545
+ .rail-item[data-selected="true"]::before { content: ""; position: absolute; left: 0; top: 8px; bottom: 8px; width: 2px; border-radius: 2px; background: var(--accent); }
546
+ .rail-thumb { width: 40px; height: 26px; border-radius: 5px; background: var(--stream-void); border: 1px solid var(--line); flex: none; overflow: hidden; position: relative; display: grid; place-items: center; color: var(--text-3); }
547
+ .rail-meta { min-width: 0; flex: 1; }
548
+ .rail-name { font-size: 11.5px; color: var(--text-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
549
+ .rail-sub { font-family: var(--mono); font-size: 9px; color: var(--text-3); display: flex; align-items: center; gap: 5px; }
550
+
551
+ .focus-stage { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bg); }
552
+ .focus-bar {
553
+ display: flex; align-items: center; gap: 12px; padding: 0 14px; height: 46px; flex: none;
554
+ background: var(--surface-0); border-bottom: 1px solid var(--line);
555
+ }
556
+ .crumbs { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--text-3); min-width: 0; }
557
+ .crumbs button { color: var(--text-3); transition: color .15s; white-space: nowrap; }
558
+ .crumbs button:hover { color: var(--text-1); }
559
+ .crumbs .sep { color: var(--text-4); }
560
+ .crumbs .cur { color: var(--text-1); font-weight: 520; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
561
+ .crumbs .crumb-rail-toggle { display: inline-grid; place-items: center; width: 26px; height: 26px; border-radius: 6px; border: 1px solid var(--line-2); }
562
+ .crumbs .crumb-rail-toggle:hover { background: var(--surface-2); }
563
+ .crumbs .crumb-rail-toggle svg { width: 15px; height: 15px; }
564
+ .focus-status { display: inline-flex; align-items: center; gap: 7px; padding: 4px 10px; border-radius: var(--radius-pill); font-family: var(--mono); font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase; background: var(--surface-2); border: 1px solid var(--line-2); }
565
+ .focus-nav { display: flex; align-items: center; gap: 2px; }
566
+ .focus-stepper { font-family: var(--mono); font-size: 10px; color: var(--text-3); padding: 0 4px; }
567
+
568
+ .focus-stage-area { flex: 1; min-height: 0; min-width: 0; display: grid; place-items: center; padding: 22px; overflow: auto; position: relative; }
569
+ .focus-frame {
570
+ position: relative; background: var(--stream-void); border-radius: var(--radius-lg);
571
+ border: 1px solid var(--line-2); box-shadow: var(--shadow-pop); overflow: hidden;
572
+ aspect-ratio: var(--aspect, 16 / 9);
573
+ max-width: 100%; max-height: 100%;
574
+ width: auto; height: 100%;
575
+ }
576
+ @supports not (aspect-ratio: 1) { .focus-frame { height: 100%; width: 100%; } }
577
+
578
+ /* side panel */
579
+ .focus-side { background: var(--surface-0); border-left: 1px solid var(--line); display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
580
+ .side-context { flex: none; padding: 16px; border-bottom: 1px solid var(--line); }
581
+ .side-persona-row { display: flex; align-items: center; gap: 10px; }
582
+ .side-avatar { width: 34px; height: 34px; border-radius: 9px; display: grid; place-items: center; background: var(--surface-2); border: 1px solid var(--line-2); color: var(--accent-2); font-weight: 600; font-size: 13px; flex: none; }
583
+ .side-persona-name { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
584
+ .side-persona-id { font-family: var(--mono); font-size: 10px; color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
585
+ .side-goal { margin-top: 13px; }
586
+ .side-goal-text { font-size: 12.5px; color: var(--text-2); margin-top: 4px; line-height: 1.5; }
587
+ .side-now { margin-top: 13px; padding: 10px 11px; border-radius: var(--radius-sm); background: var(--surface-1); border: 1px solid var(--line); }
588
+ .side-now-row { display: flex; align-items: center; gap: 7px; margin-bottom: 5px; }
589
+ .side-now-text { font-size: 12.5px; color: var(--text-1); line-height: 1.45; }
590
+
591
+ .side-tabs { display: flex; flex: none; padding: 0 8px; gap: 2px; border-bottom: 1px solid var(--line); overflow-x: auto; scrollbar-width: none; }
592
+ .side-tabs::-webkit-scrollbar { display: none; }
593
+ .side-tab { display: inline-flex; align-items: center; gap: 6px; padding: 11px 10px; font-size: 11.5px; color: var(--text-3); border-bottom: 2px solid transparent; white-space: nowrap; transition: color .15s; }
594
+ .side-tab:hover { color: var(--text-1); }
595
+ .side-tab[aria-selected="true"] { color: var(--text-1); border-bottom-color: var(--accent); }
596
+ .side-tab .tab-n { font-family: var(--mono); font-size: 9.5px; padding: 1px 5px; border-radius: 999px; background: var(--surface-3); color: var(--text-2); }
597
+ .side-body { flex: 1; min-height: 0; overflow-y: auto; }
598
+
599
+ .evt { display: grid; grid-template-columns: auto 1fr; gap: 11px; padding: 12px 16px; border-bottom: 1px solid var(--line); }
600
+ .evt-rail { display: flex; flex-direction: column; align-items: center; gap: 4px; }
601
+ .evt-icon { width: 22px; height: 22px; border-radius: 6px; display: grid; place-items: center; background: var(--surface-2); border: 1px solid var(--line); flex: none; }
602
+ .evt-icon svg { width: 12px; height: 12px; color: var(--text-3); }
603
+ .evt[data-level="warn"] .evt-icon { color: var(--amber); border-color: color-mix(in oklab, var(--amber) 30%, transparent); }
604
+ .evt[data-level="warn"] .evt-icon svg { color: var(--amber); }
605
+ .evt[data-level="error"] .evt-icon { color: var(--red); border-color: color-mix(in oklab, var(--red) 30%, transparent); }
606
+ .evt[data-level="error"] .evt-icon svg { color: var(--red); }
607
+ .evt-conn { flex: 1; width: 1px; background: var(--line); min-height: 8px; }
608
+ .evt-text { font-size: 12px; color: var(--text-1); line-height: 1.45; }
609
+ .evt-meta { font-family: var(--mono); font-size: 9.5px; color: var(--text-3); margin-top: 4px; letter-spacing: .04em; display: flex; gap: 7px; flex-wrap: wrap; }
610
+ .evt-type { color: var(--text-2); }
611
+
612
+ .file-row { display: flex; align-items: center; gap: 11px; padding: 11px 16px; border-bottom: 1px solid var(--line); transition: background .14s; }
613
+ .file-row:hover { background: var(--surface-1); }
614
+ .file-ic { width: 28px; height: 28px; border-radius: 7px; display: grid; place-items: center; background: var(--surface-2); border: 1px solid var(--line); color: var(--text-2); flex: none; }
615
+ .file-ic svg { width: 14px; height: 14px; }
616
+ .file-meta { min-width: 0; flex: 1; }
617
+ .file-name { font-size: 12.5px; color: var(--text-1); }
618
+ .file-path { font-family: var(--mono); font-size: 9.5px; color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
619
+ .file-kind { font-family: var(--mono); font-size: 9px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-3); padding: 2px 7px; border-radius: 5px; background: var(--surface-2); flex: none; }
620
+
621
+ .tab-empty { padding: 36px 24px; text-align: center; color: var(--text-3); font-size: 12px; }
622
+
623
+ /* ============================================================ HISTORY DRAWER */
624
+ .scrim { position: fixed; inset: 0; background: rgba(4,6,9,.5); backdrop-filter: blur(2px); z-index: 70; animation: fade-in .2s ease; }
625
+ html[data-theme="light"] .scrim { background: rgba(20,28,40,.32); }
626
+ .drawer {
627
+ position: fixed; top: 0; bottom: 0; right: 0; z-index: 71;
628
+ width: min(420px, 100vw); background: var(--surface-0);
629
+ border-left: 1px solid var(--line-2); box-shadow: var(--shadow-pop);
630
+ display: flex; flex-direction: column; animation: slide-in .28s var(--ease);
631
+ }
632
+ .drawer-head { display: flex; align-items: flex-start; justify-content: space-between; padding: 18px 18px 14px; border-bottom: 1px solid var(--line); }
633
+ .drawer-head h2 { margin: 4px 0 0; font-size: 18px; font-weight: 600; letter-spacing: -0.01em; }
634
+ .drawer-list { flex: 1; min-height: 0; overflow-y: auto; padding: 8px; }
635
+ .run-row { display: grid; grid-template-columns: auto 1fr auto; gap: 4px 11px; align-items: center; padding: 12px; border-radius: var(--radius); transition: background .14s; width: 100%; text-align: left; }
636
+ .run-row:hover { background: var(--surface-1); }
637
+ .run-row[data-active="true"] { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--accent); }
638
+ .run-row .pip { grid-row: span 2; }
639
+ .run-row-id { font-family: var(--mono); font-size: 11.5px; color: var(--text-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
640
+ .run-row-meta { font-family: var(--mono); font-size: 9.5px; color: var(--text-3); }
641
+ .run-row-stat { font-family: var(--mono); font-size: 9px; text-transform: uppercase; letter-spacing: .08em; color: var(--text-2); grid-row: span 2; justify-self: end; }
642
+
643
+ /* ============================================================ POPOVER (run details) */
644
+ .pop { position: absolute; z-index: 55; top: calc(var(--header-h) - 4px); left: 16px; width: 300px;
645
+ background: var(--surface-1); border: 1px solid var(--line-2); border-radius: var(--radius); box-shadow: var(--shadow-pop);
646
+ padding: 6px; animation: pop-in .16s var(--ease-out); }
647
+ .pop-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 9px; border-radius: var(--radius-sm); }
648
+ .pop-row + .pop-row { border-top: 1px solid var(--line); }
649
+ .pop-k { font-size: 11px; color: var(--text-3); }
650
+ .pop-v { font-family: var(--mono); font-size: 11px; color: var(--text-1); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 190px; }
651
+ .pop-v .tag { padding: 1px 6px; border-radius: 5px; background: var(--surface-3); display: inline-flex; align-items: center; gap: 5px; }
652
+ .pop-v .tag[data-tone="green"] { color: var(--green); background: var(--green-soft); }
653
+
654
+ /* ============================================================ TWEAKS POPOVER */
655
+ .tweaks-pop { position: absolute; z-index: 56; top: calc(var(--header-h) - 4px); right: 16px; width: 264px;
656
+ background: var(--surface-1); border: 1px solid var(--line-2); border-radius: var(--radius); box-shadow: var(--shadow-pop);
657
+ padding: 12px; animation: pop-in .16s var(--ease-out); }
658
+ .tw-sec { font-family: var(--mono); font-size: 9px; letter-spacing: .16em; text-transform: uppercase; color: var(--text-4); margin: 10px 2px 7px; }
659
+ .tw-sec:first-child { margin-top: 2px; }
660
+ .tw-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
661
+ .tw-label { font-size: 12px; color: var(--text-2); }
662
+ .tw-seg { display: inline-flex; padding: 2px; background: var(--surface-0); border: 1px solid var(--line); border-radius: var(--radius-sm); }
663
+ .tw-seg button { height: 22px; padding: 0 8px; border-radius: 5px; font-size: 10.5px; color: var(--text-3); text-transform: capitalize; }
664
+ .tw-seg button[aria-pressed="true"] { background: var(--surface-3); color: var(--text-1); }
665
+ .tw-swatches { display: inline-flex; gap: 6px; }
666
+ .tw-swatch { width: 18px; height: 18px; border-radius: 50%; border: 2px solid transparent; }
667
+ .tw-swatch[aria-pressed="true"] { border-color: var(--text-1); }
668
+
669
+ /* ============================================================ EMPTY STATE (no matches) */
670
+ .grid-empty { grid-column: 1 / -1; display: grid; place-items: center; padding: 60px 20px; text-align: center; gap: 12px; color: var(--text-3); }
671
+ .grid-empty .ge-icon { width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center; background: var(--surface-1); border: 1px solid var(--line-2); color: var(--text-3); }
672
+ .grid-empty h3 { margin: 0; font-size: 14px; color: var(--text-1); font-weight: 560; }
673
+ .grid-empty button.linklike { color: var(--accent-2); }
674
+
675
+ /* ============================================================ RESPONSIVE */
676
+ @media (max-width: 1100px) {
677
+ :root { --side-w: 320px; }
678
+ .hdr-run-title { max-width: 32vw; }
679
+ .lane-count .lc-label { display: none; }
680
+ }
681
+ @media (max-width: 860px) {
682
+ .focus { grid-template-columns: 1fr; grid-template-rows: 1fr; }
683
+ .focus-rail { display: none; }
684
+ .focus-side {
685
+ position: fixed; left: 0; right: 0; bottom: 0; top: auto; z-index: 72;
686
+ max-height: 62vh; border-left: none; border-top: 1px solid var(--line-2);
687
+ border-radius: var(--radius-lg) var(--radius-lg) 0 0; box-shadow: var(--shadow-pop);
688
+ transform: translateY(calc(100% - 52px)); transition: transform .3s var(--ease);
689
+ }
690
+ .focus-side[data-sheet="open"] { transform: translateY(0); }
691
+ .sheet-grip { display: flex; }
692
+ }
693
+ .sheet-grip { display: none; align-items: center; justify-content: center; gap: 8px; height: 40px; flex: none; border-bottom: 1px solid var(--line); cursor: grab; }
694
+ .sheet-grip::before { content: ""; width: 36px; height: 4px; border-radius: 2px; background: var(--line-3); }
695
+
696
+ @media (max-width: 720px) {
697
+ :root { --header-h: 50px; }
698
+ .brand-word, .hdr-runs span { display: none; }
699
+ .hdr-run { padding-left: 10px; }
700
+ .hdr-run-title { max-width: 40vw; font-size: 12.5px; }
701
+ .lane-counts { display: none; }
702
+ .run-chip { display: none; }
703
+ .toolbar { gap: 7px; }
704
+ .tb-search { min-width: 0; width: 130px; }
705
+ .tb-label { display: none; }
706
+ .grid-scroll { padding: 12px; }
707
+ .grid { gap: 11px; grid-template-columns: 1fr; }
708
+ .seg.density-seg button span { display: none; }
709
+ }
710
+ @media (max-width: 480px) {
711
+ .seg.media-seg button span, .seg.view-seg button span { display: none; }
712
+ }
713
+
714
+ /* ============================================================ KEYFRAMES */
715
+ @keyframes ping { 0% { transform: scale(.6); opacity: .5; } 100% { transform: scale(2.4); opacity: 0; } }
716
+ @keyframes spin { to { transform: rotate(360deg); } }
717
+ @keyframes blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }
718
+ @keyframes tile-in { from { opacity: 0; transform: translateY(8px) scale(.99); } to { opacity: 1; transform: none; } }
719
+ @keyframes line-in { from { transform: translateY(4px); } to { transform: none; } }
720
+ @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
721
+ @keyframes slide-in { from { transform: translateX(20px); opacity: 0; } to { transform: none; opacity: 1; } }
722
+ @keyframes pop-in { from { transform: translateY(-6px) scale(.98); opacity: 0; } to { transform: none; opacity: 1; } }
723
+ @keyframes clickpulse { 0% { transform: scale(.5); opacity: .9; } 100% { transform: scale(1.5); opacity: 0; } }
724
+ @keyframes console-up { from { opacity: 0; } to { opacity: 1; } }
725
+
726
+ @media (prefers-reduced-motion: reduce) {
727
+ *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
728
+ }
729
+ html[data-motion="reduced"] *, html[data-motion="reduced"] *::before, html[data-motion="reduced"] *::after {
730
+ animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important;
731
+ }
732
+
733
+ /* ============================================================ DROPDOWN FILTERS */
734
+ .dd { position: relative; flex: none; }
735
+ .dd-trigger {
736
+ display: inline-flex; align-items: center; gap: 7px;
737
+ height: 30px; padding: 0 9px 0 11px; border-radius: var(--radius-sm);
738
+ border: 1px solid var(--line-2); color: var(--text-2); font-size: 12px;
739
+ transition: background .15s, color .15s, border-color .15s; white-space: nowrap;
740
+ }
741
+ .dd-trigger:hover { background: var(--surface-2); color: var(--text-1); }
742
+ .dd-trigger[data-active="true"] { background: var(--surface-3); color: var(--text-1); border-color: var(--line-3); }
743
+ .dd-trigger svg:last-child { color: var(--text-3); }
744
+ .dd-trigger-inner { display: inline-flex; align-items: center; gap: 7px; }
745
+ .dd-badge {
746
+ min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
747
+ background: var(--accent-color); color: #fff; font-family: var(--mono); font-size: 9.5px;
748
+ display: inline-grid; place-items: center; font-weight: 600;
749
+ }
750
+ .dd-menu {
751
+ position: fixed; z-index: 90;
752
+ background: var(--surface-1); border: 1px solid var(--line-2); border-radius: var(--radius);
753
+ box-shadow: var(--shadow-pop); padding: 5px; animation: pop-in .14s var(--ease-out);
754
+ }
755
+ .dd-row {
756
+ display: flex; align-items: center; gap: 9px; width: 100%; padding: 8px 9px;
757
+ border-radius: var(--radius-sm); text-align: left; color: var(--text-2); font-size: 12.5px;
758
+ transition: background .12s;
759
+ }
760
+ .dd-row:hover { background: var(--surface-2); color: var(--text-1); }
761
+ .dd-row-all { color: var(--text-1); }
762
+ .dd-row-label { flex: 1; }
763
+ .dd-row-n { color: var(--text-3); font-size: 11px; }
764
+ .dd-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
765
+ .dd-check {
766
+ width: 16px; height: 16px; border-radius: 5px; border: 1px solid var(--line-3);
767
+ display: grid; place-items: center; flex: none; color: #fff; transition: background .12s, border-color .12s;
768
+ }
769
+ .dd-check[data-on="true"] { background: var(--accent-color); border-color: var(--accent-color); }
770
+ .dd-check svg { width: 11px; height: 11px; }
771
+ .dd-sep { height: 1px; background: var(--line); margin: 5px 4px; }
772
+
773
+ .tb-result { font-family: var(--mono); font-size: 11px; color: var(--text-3); flex: none; padding-left: 2px; }
774
+
775
+ /* compact icon-only segmented controls on the right */
776
+ .seg.view-seg button, .seg.media-seg button, .seg.density-seg button { width: 30px; padding: 0; justify-content: center; }
777
+
778
+ /* ============================================================ STATUS INDICATOR (header) */
779
+ .si-badges { display: inline-flex; align-items: center; gap: 4px; flex: none; }
780
+ .si-badge {
781
+ display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 9px;
782
+ border-radius: var(--radius-pill); border: 1px solid transparent; transition: background .15s, border-color .15s;
783
+ }
784
+ .si-badge:hover { background: var(--surface-2); }
785
+ .si-badge[aria-pressed="true"] { background: var(--surface-3); border-color: var(--line-2); }
786
+ .si-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
787
+ .si-n { font-size: 12px; color: var(--text-1); font-weight: 600; }
788
+
789
+ .si-bar { display: inline-flex; align-items: center; gap: 9px; flex: none; min-width: 140px; }
790
+ .si-bar-track { display: flex; height: 8px; flex: 1; border-radius: 999px; overflow: hidden; background: var(--surface-3); gap: 2px; }
791
+ .si-bar-seg { min-width: 4px; cursor: pointer; transition: filter .15s; }
792
+ .si-bar-seg:hover { filter: brightness(1.2); }
793
+ .si-bar-label { font-size: 11px; color: var(--text-2); }
794
+
795
+ .si-ring { display: inline-flex; align-items: center; gap: 8px; padding: 2px 6px 2px 2px; border-radius: var(--radius-pill); transition: background .15s; }
796
+ .si-ring:hover { background: var(--surface-2); }
797
+ .si-ring-meta { display: flex; flex-direction: column; line-height: 1.1; align-items: flex-start; }
798
+ .si-ring-pct { font-size: 12px; color: var(--text-1); font-weight: 600; }
799
+ .si-ring-issue { font-size: 9.5px; }
800
+
801
+ /* ============================================================ FOCUS SIDE COLLAPSE */
802
+ .focus[data-side="collapsed"] { --side-w: 0px; grid-template-columns: var(--rail-w, 188px) 1fr; }
803
+ .focus[data-side="collapsed"][data-rail="collapsed"] { grid-template-columns: 1fr; }
804
+ .focus[data-rail="collapsed"]:not([data-side="collapsed"]) { grid-template-columns: 1fr var(--side-w, 360px); }
805
+
806
+ /* ============================================================ RUN CONSOLE */
807
+ .console {
808
+ display: flex; flex-direction: column; min-height: 0; flex: none;
809
+ background: var(--surface-0); border-top: 1px solid var(--line-2);
810
+ box-shadow: 0 -10px 30px rgba(0,0,0,.25); animation: console-up .26s var(--ease);
811
+ }
812
+ .console-head { display: flex; align-items: center; gap: 11px; padding: 0 12px; height: 38px; flex: none; border-bottom: 1px solid var(--line); }
813
+ .console-title { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text-1); font-weight: 520; }
814
+ .console-title svg { color: var(--text-3); }
815
+ .console-cmd { font-size: 10.5px; color: var(--text-3); background: var(--surface-2); padding: 2px 7px; border-radius: 5px; }
816
+ .console-live { display: inline-flex; align-items: center; gap: 6px; font-family: var(--mono); font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--accent-2); }
817
+ .console-meta { font-size: 10.5px; color: var(--text-3); }
818
+ .console-body { flex: 1; min-height: 0; overflow-y: auto; padding: 8px 12px; font-size: 11.5px; line-height: 1.6; }
819
+ .console-line { display: flex; gap: 12px; }
820
+ .console-t { color: var(--text-4); flex: none; user-select: none; }
821
+ .console-txt { color: var(--text-2); overflow-wrap: anywhere; }
822
+ .console-txt.info { color: var(--text-2); }
823
+ .console-txt.ok { color: var(--green); }
824
+ .console-txt.warn { color: var(--amber); }
825
+ .console-txt.err { color: var(--red); }
826
+ .console-txt.dim { color: var(--text-3); }
827
+
828
+ @media (max-width: 720px) {
829
+ .tb-result { display: none; }
830
+ .dd-trigger-inner span { max-width: 64px; overflow: hidden; text-overflow: ellipsis; }
831
+ .console { height: 200px !important; }
832
+ }
833
+
834
+ /* ============================================================ STATUS BAR (persistent footer) */
835
+ .statusbar {
836
+ display: flex; align-items: center; gap: 12px; flex: none;
837
+ height: 30px; padding: 0 12px;
838
+ background: var(--surface-0); border-top: 1px solid var(--line-2);
839
+ font-size: 11.5px; position: relative; z-index: 25;
840
+ }
841
+ .sb-status { display: inline-flex; align-items: center; gap: 7px; flex: none; }
842
+ .sb-status-label { font-size: 9.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--text-2); }
843
+ .sb-status[data-tone="running"] .sb-status-label { color: var(--accent-2); }
844
+ .sb-status[data-tone="failed"] .sb-status-label { color: var(--red); }
845
+ .sb-status[data-tone="blocked"] .sb-status-label { color: var(--amber); }
846
+ .sb-status[data-tone="complete"] .sb-status-label { color: var(--green); }
847
+ .sb-pct { color: var(--text-3); font-size: 11px; }
848
+ .sb-prog { width: 90px; height: 4px; border-radius: 999px; background: var(--surface-3); overflow: hidden; flex: none; }
849
+ .sb-prog > span { display: block; height: 100%; background: var(--accent-color); transition: width .6s var(--ease-out); }
850
+ .sb-prog > span[data-tone="failed"] { background: var(--red); }
851
+ .sb-prog > span[data-tone="complete"] { background: var(--green); }
852
+ .sb-prog > span[data-tone="blocked"] { background: var(--amber); }
853
+ .sb-counts { display: inline-flex; align-items: center; gap: 8px; flex: none; }
854
+ .sb-count { display: inline-flex; align-items: center; gap: 5px; color: var(--text-2); }
855
+ .sb-count .si-dot { width: 7px; height: 7px; border-radius: 50%; }
856
+ .sb-run { color: var(--text-4); font-size: 10.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
857
+
858
+ .sb-console {
859
+ display: inline-flex; align-items: center; gap: 8px; flex: none; max-width: 52vw;
860
+ height: 30px; padding: 0 4px 0 11px; color: var(--text-2);
861
+ border-left: 1px solid var(--line); transition: color .15s, background .15s;
862
+ }
863
+ .sb-console:hover { color: var(--text-1); background: var(--surface-1); }
864
+ .sb-console[aria-expanded="true"] { color: var(--text-1); background: var(--surface-2); }
865
+ .sb-console svg:first-child { color: var(--text-3); }
866
+ .sb-console-label { font-size: 11.5px; flex: none; }
867
+ .sb-console-peek { display: inline-flex; align-items: center; gap: 8px; min-width: 0; padding-left: 8px; border-left: 1px solid var(--line); }
868
+ .sb-peek-t { color: var(--text-4); flex: none; font-size: 10px; }
869
+ .sb-peek-txt { color: var(--text-3); font-size: 10.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 30vw; }
870
+ .sb-peek-txt.ok { color: color-mix(in oklab, var(--green) 80%, var(--text-2)); }
871
+ .sb-peek-txt.warn { color: color-mix(in oklab, var(--amber) 85%, var(--text-2)); }
872
+ .sb-peek-txt.err { color: color-mix(in oklab, var(--red) 85%, var(--text-2)); }
873
+ .sb-chev { display: inline-grid; place-items: center; color: var(--text-3); transition: transform .2s var(--ease); }
874
+ .sb-chev.open { transform: rotate(180deg); }
875
+ /* when docked above the bar, drop the console's bottom rounding/shadow seam */
876
+ .console + .statusbar { border-top-color: var(--line); }
877
+
878
+ @media (max-width: 720px) {
879
+ .sb-counts, .sb-run, .sb-prog { display: none; }
880
+ .sb-console-label { display: none; }
881
+ .sb-peek-txt { max-width: 44vw; }
882
+ }
883
+ @media (max-width: 480px) {
884
+ .sb-console-peek { display: none; }
1297
885
  }
1298
886
  `;
1299
887
  }
1300
888
  export function observerClientJs() {
1301
889
  return `
1302
890
  (function () {
891
+ "use strict";
892
+
893
+ // ---------------------------------------------------------------- hydrate
1303
894
  var DATA_FILE = "observer-data.json";
1304
- var embedded = JSON.parse(document.getElementById("observer-data").textContent || "null");
1305
- var currentData = embedded;
1306
- var focusedId = focusedIdFromLocation();
1307
- var preferScreenshots = false;
1308
- var mediaPreferenceTouched = false;
1309
- var activeStatus = "all";
1310
- var activeKind = "all";
1311
- var density = Number(readPref("density", 4));
1312
- var stepperOpen = readPref("stepperOpen", false) === true;
1313
- var historyOpen = readPref("historyOpen", false) === true;
895
+ var dataEl = document.getElementById("observer-data");
896
+ var currentData = null;
897
+ try { currentData = JSON.parse((dataEl && dataEl.textContent) || "null"); } catch (e) { currentData = null; }
898
+ if (!currentData || typeof currentData !== "object") currentData = {};
899
+ if (!currentData.run) currentData.run = {};
900
+ if (!currentData.streams) currentData.streams = [];
901
+ if (!currentData.events) currentData.events = [];
902
+
903
+ var app = document.getElementById("app");
1314
904
  var historyIndex = null;
1315
- var activeFocusTab = "events";
1316
905
  var refreshTimer = null;
1317
906
  var historyTimer = null;
907
+ var openDd = null;
908
+ var NL = String.fromCharCode(10);
909
+ var TIMES = String.fromCharCode(215);
1318
910
 
1319
- var RUN_PHASES = [
1320
- { id: "packet", label: "Packet prep", desc: "Reading run bundle" },
1321
- { id: "persona", label: "Persona plan", desc: "Persona and scenario loaded" },
1322
- { id: "streams", label: "Stream mount", desc: "Observer lanes mounted" },
1323
- { id: "evidence", label: "Evidence review", desc: "Artifacts and events linked" },
1324
- { id: "ready", label: "Observer ready", desc: "Operator console live" }
1325
- ];
1326
-
1327
- var GRID_SCALE_STREAM_HEIGHT = { 2: 180, 3: 260, 4: 360, 5: 500 };
1328
-
1329
- function getElement(id) {
1330
- var node = document.getElementById(id);
1331
- if (!node) throw new Error("Missing observer element: " + id);
1332
- return node;
911
+ // ---------------------------------------------------------------- prefs
912
+ function readPref(key, fallback) {
913
+ try { var raw = window.localStorage.getItem("mimetic-observer:" + key); return raw == null ? fallback : JSON.parse(raw); }
914
+ catch (e) { return fallback; }
1333
915
  }
1334
-
1335
- function render() {
1336
- var liveAvailable = hasLiveStreams(currentData);
1337
- if (!mediaPreferenceTouched || !liveAvailable) {
1338
- preferScreenshots = preferredScreenshotMode(currentData);
1339
- }
1340
- document.title = "Mimetic Observer - " + currentData.run.runId;
1341
- renderProgress();
1342
- renderSubBar();
1343
- renderStreams();
1344
- renderFocus();
1345
- renderHistoryPanel();
916
+ function writePref(key, value) {
917
+ try { window.localStorage.setItem("mimetic-observer:" + key, JSON.stringify(value)); } catch (e) {}
1346
918
  }
1347
919
 
1348
- function renderProgress() {
1349
- var counts = countStatuses(currentData.streams);
1350
- var total = currentData.streams.length || 1;
1351
- var phaseIndex = currentData.run.status === "contract_proof_only" || currentData.run.status === "complete" ? 4 : 3;
1352
- var progress = Math.min(1, (counts.complete + counts.proof + counts.running * 0.62 + counts.blocked) / total);
1353
- var pulse = getElement("rp-current-pulse");
1354
- var label = getElement("rp-current-label");
1355
- var step = getElement("rp-current-step");
1356
- var meta = getElement("rp-meta");
1357
- var fill = getElement("rp-progress").querySelector("span");
1358
-
1359
- pulse.style.color = currentData.run.status === "failed" ? "var(--obs-red)" : "var(--obs-blue)";
1360
- label.textContent = currentData.run.status === "contract_proof_only" ? "Proof snapshot" : RUN_PHASES[phaseIndex].label;
1361
- step.textContent = "- step " + String(phaseIndex + 1) + "/" + String(RUN_PHASES.length);
1362
- if (fill) fill.style.width = String(Math.round(progress * 100)) + "%";
1363
-
1364
- meta.replaceChildren();
1365
- appendMeta(meta, currentData.run.runId, true);
1366
- appendMeta(meta, shortTime(currentData.run.createdAt), false);
1367
- appendMeta(meta, currentData.run.mode, false);
1368
- appendMeta(meta, currentData.run.packageName || "local project", false);
1369
- appendMeta(meta, currentData.publicSafety.publishable ? "publishable" : "local evidence", false);
1370
-
1371
- getElement("rp-chips").replaceChildren(
1372
- chip("Live", counts.running, "var(--obs-blue)"),
1373
- chip("Blocked", counts.blocked, "var(--obs-amber)"),
1374
- chip("Error", counts.failed, "var(--obs-red)"),
1375
- chip("Done", counts.complete, "var(--obs-green)"),
1376
- chip("Proof", counts.proof, "var(--obs-green)")
1377
- );
1378
-
1379
- var stepper = getElement("rp-stepper");
1380
- var toggle = getElement("rp-toggle");
1381
- toggle.setAttribute("aria-expanded", stepperOpen ? "true" : "false");
1382
- toggle.textContent = stepperOpen ? "Collapse" : "Phases";
1383
- stepper.hidden = !stepperOpen;
1384
- if (stepperOpen) {
1385
- stepper.style.gridTemplateColumns = "repeat(" + RUN_PHASES.length + ", 1fr)";
1386
- stepper.replaceChildren.apply(stepper, RUN_PHASES.map(function (phase, index) {
1387
- var cell = document.createElement("div");
1388
- var state = index < phaseIndex ? "done" : index === phaseIndex ? "active" : "pending";
1389
- cell.className = "rp-stepper-cell";
1390
- cell.dataset.state = state;
1391
- var mark = document.createElement("div");
1392
- mark.className = "rp-stepper-mark";
1393
- mark.dataset.state = state;
1394
- mark.append(document.createElement("span"));
1395
- var lbl = document.createElement("span");
1396
- lbl.className = "rp-stepper-label";
1397
- lbl.textContent = phase.label;
1398
- var desc = document.createElement("span");
1399
- desc.className = "rp-stepper-desc";
1400
- desc.textContent = phase.desc;
1401
- cell.append(mark, lbl, desc);
1402
- if (index < RUN_PHASES.length - 1) {
1403
- var conn = document.createElement("div");
1404
- conn.className = "rp-stepper-conn";
1405
- cell.append(conn);
1406
- }
1407
- return cell;
1408
- }));
1409
- } else {
1410
- stepper.replaceChildren();
1411
- }
920
+ function focusFromHash() {
921
+ var h = String(window.location.hash || "");
922
+ if (h.indexOf("focus=") < 0) return null;
923
+ try { return decodeURIComponent(h.split("focus=")[1].split("&")[0]); } catch (e) { return null; }
1412
924
  }
1413
925
 
1414
- function appendMeta(root, value, strong) {
1415
- if (!value) return;
1416
- if (root.childNodes.length > 0) root.append(document.createTextNode(" - "));
1417
- if (strong) {
1418
- var node = document.createElement("strong");
1419
- node.textContent = value;
1420
- root.append(node);
1421
- } else {
1422
- root.append(document.createTextNode(value));
1423
- }
926
+ // ---------------------------------------------------------------- state
927
+ var S = {
928
+ view: "grid",
929
+ focusedId: focusFromHash(),
930
+ statusSel: [],
931
+ kindSel: [],
932
+ query: "",
933
+ media: "live",
934
+ historyOpen: false,
935
+ detailsOpen: false,
936
+ tweaksOpen: false,
937
+ consoleOpen: false,
938
+ railCollapsed: !!readPref("railCollapsed", false),
939
+ sideCollapsed: !!readPref("sideCollapsed", false),
940
+ sheetOpen: false,
941
+ tab: "events",
942
+ theme: readPref("theme", "dark"),
943
+ accent: readPref("accent", "#4d7cfe"),
944
+ density: readPref("density", "comfortable"),
945
+ statusViz: readPref("statusViz", "badges"),
946
+ motion: readPref("motion", "full")
947
+ };
948
+
949
+ // ---------------------------------------------------------------- tone / labels
950
+ var TONE = {
951
+ queued: "queued", preparing: "running", running: "running",
952
+ passed: "complete", complete: "complete", contract_proof_only: "complete",
953
+ blocked: "blocked", timed_out: "blocked", failed: "failed"
954
+ };
955
+ var STATUS_LABEL = {
956
+ queued: "Queued", preparing: "Preparing", running: "Running", passed: "Passed",
957
+ complete: "Complete", contract_proof_only: "Proof only", blocked: "Blocked",
958
+ timed_out: "Timed out", failed: "Failed"
959
+ };
960
+ function tone(s) { return TONE[s] || "queued"; }
961
+ function statusLabel(s) { return STATUS_LABEL[s] || s || "Unknown"; }
962
+ function kindGroup(k) { return k === "browser" ? "ui" : k; }
963
+
964
+ // ---------------------------------------------------------------- esc / icons
965
+ function esc(v) {
966
+ return String(v == null ? "" : v)
967
+ .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1424
968
  }
1425
-
1426
- function chip(label, count, color) {
1427
- var node = document.createElement("span");
1428
- node.className = "rp-chip";
1429
- node.dataset.active = String(count > 0);
1430
- node.dataset.dim = String(count === 0);
1431
- node.style.color = color;
1432
- var dot = document.createElement("span");
1433
- dot.className = "rp-chip-dot";
1434
- var lbl = document.createElement("span");
1435
- lbl.className = "rp-chip-label";
1436
- lbl.textContent = label;
1437
- var cnt = document.createElement("span");
1438
- cnt.className = "rp-chip-count";
1439
- cnt.textContent = String(count).padStart(2, "0");
1440
- node.append(dot, lbl, cnt);
1441
- return node;
969
+ function pad2(n) { return (n < 10 ? "0" : "") + n; }
970
+
971
+ var P = ' fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"';
972
+ var ICONS = {
973
+ grid: '<rect x="3" y="3" width="7" height="7" rx="1.4"' + P + '/><rect x="14" y="3" width="7" height="7" rx="1.4"' + P + '/><rect x="3" y="14" width="7" height="7" rx="1.4"' + P + '/><rect x="14" y="14" width="7" height="7" rx="1.4"' + P + '/>',
974
+ focus: '<rect x="3" y="4" width="18" height="16" rx="2"' + P + '/><path d="M3 9h18"' + P + '/>',
975
+ expand: '<path d="M9 4H5a1 1 0 0 0-1 1v4M15 4h4a1 1 0 0 1 1 1v4M9 20H5a1 1 0 0 1-1-1v-4M15 20h4a1 1 0 0 0 1-1v-4"' + P + '/>',
976
+ search: '<circle cx="11" cy="11" r="7"' + P + '/><path d="m20 20-3-3"' + P + '/>',
977
+ clock: '<circle cx="12" cy="12" r="9"' + P + '/><path d="M12 7v5l3 2"' + P + '/>',
978
+ sun: '<circle cx="12" cy="12" r="4"' + P + '/><path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.5 1.5M17.5 17.5 19 19M19 5l-1.5 1.5M6.5 17.5 5 19"' + P + '/>',
979
+ moon: '<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z"' + P + '/>',
980
+ chevL: '<path d="m15 18-6-6 6-6"' + P + '/>',
981
+ chevR: '<path d="m9 18 6-6-6-6"' + P + '/>',
982
+ x: '<path d="M6 6l12 12M18 6 6 18"' + P + '/>',
983
+ sliders: '<path d="M4 6h10M18 6h2M4 12h2M10 12h10M4 18h7M15 18h5"' + P + '/><circle cx="16" cy="6" r="2"' + P + '/><circle cx="8" cy="12" r="2"' + P + '/><circle cx="13" cy="18" r="2"' + P + '/>',
984
+ comfy: '<rect x="3" y="4" width="18" height="7" rx="1.4"' + P + '/><rect x="3" y="13" width="18" height="7" rx="1.4"' + P + '/>',
985
+ compact: '<rect x="3" y="4" width="8" height="7" rx="1.2"' + P + '/><rect x="13" y="4" width="8" height="7" rx="1.2"' + P + '/><rect x="3" y="13" width="8" height="7" rx="1.2"' + P + '/><rect x="13" y="13" width="8" height="7" rx="1.2"' + P + '/>',
986
+ dense: '<rect x="3" y="4" width="5" height="5" rx="1"' + P + '/><rect x="10" y="4" width="5" height="5" rx="1"' + P + '/><rect x="17" y="4" width="4" height="5" rx="1"' + P + '/><rect x="3" y="11" width="5" height="5" rx="1"' + P + '/><rect x="10" y="11" width="5" height="5" rx="1"' + P + '/><rect x="17" y="11" width="4" height="5" rx="1"' + P + '/>',
987
+ image: '<rect x="3" y="4" width="18" height="16" rx="2"' + P + '/><circle cx="9" cy="10" r="1.6"' + P + '/><path d="m4 18 5-4 4 3 3-2 4 3"' + P + '/>',
988
+ live: '<circle cx="12" cy="12" r="3"' + P + '/><path d="M6.5 6.5a8 8 0 0 0 0 11M17.5 6.5a8 8 0 0 1 0 11M4 4a12 12 0 0 0 0 16M20 4a12 12 0 0 1 0 16"' + P + '/>',
989
+ file: '<path d="M14 3v5h5"' + P + '/><path d="M6 3h8l5 5v11a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z"' + P + '/>',
990
+ list: '<path d="M8 6h12M8 12h12M8 18h12M3.5 6h.01M3.5 12h.01M3.5 18h.01"' + P + '/>',
991
+ terminal: '<path d="m6 8 3.5 3L6 14M13 15h5"' + P + '/><rect x="3" y="4" width="18" height="16" rx="2"' + P + '/>',
992
+ spark: '<path d="M12 3v4M12 17v4M3 12h4M17 12h4M6 6l2.5 2.5M15.5 15.5 18 18M18 6l-2.5 2.5M8.5 15.5 6 18"' + P + '/>',
993
+ check: '<path d="m4 12 5 5L20 6"' + P + '/>',
994
+ alert: '<path d="M12 8v5M12 16.5h.01"' + P + '/><path d="M10.3 3.9 2.4 18a2 2 0 0 0 1.7 3h15.8a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"' + P + '/>',
995
+ info: '<circle cx="12" cy="12" r="9"' + P + '/><path d="M12 11v5M12 8h.01"' + P + '/>',
996
+ globe: '<circle cx="12" cy="12" r="9"' + P + '/><path d="M3 12h18M12 3c2.5 2.7 2.5 15.3 0 18M12 3c-2.5 2.7-2.5 15.3 0 18"' + P + '/>',
997
+ lock: '<rect x="4.5" y="10" width="15" height="10" rx="2"' + P + '/><path d="M8 10V7a4 4 0 0 1 8 0v3"' + P + '/>',
998
+ caret: '<path d="m6 9 6 6 6-6"' + P + '/>',
999
+ filter: '<path d="M3 5h18l-7 8.2V20l-4-2.2v-4.6L3 5Z"' + P + '/>',
1000
+ panelRight: '<rect x="3" y="4" width="18" height="16" rx="2"' + P + '/><path d="M15 4v16"' + P + '/>'
1001
+ };
1002
+ function icon(name, size) {
1003
+ var s = size || 18;
1004
+ return '<svg viewBox="0 0 24 24" width="' + s + '" height="' + s + '" aria-hidden="true">' + (ICONS[name] || ICONS.info) + '</svg>';
1442
1005
  }
1443
-
1444
- function renderSubBar() {
1445
- var counts = countStatuses(currentData.streams);
1446
- var kindCounts = countKinds(currentData.streams);
1447
- getElement("sub-mode").textContent = focusedId ? "Watch focus" : "Watch grid";
1448
- getElement("sub-count").textContent = String(currentData.streams.length);
1449
- getElement("sub-filters").replaceChildren(
1450
- statusFilter("all", "All", currentData.streams.length, null),
1451
- statusFilter("running", "Live", counts.running, "var(--obs-blue)"),
1452
- statusFilter("blocked", "Blocked", counts.blocked + counts.failed, "var(--obs-amber)"),
1453
- statusFilter("complete", "Done", counts.complete, "var(--obs-green)"),
1454
- statusFilter("proof", "Proof", counts.proof, "var(--obs-green)")
1455
- );
1456
- getElement("sub-kind-filters").replaceChildren(
1457
- kindFilter("all", "All", currentData.streams.length),
1458
- kindFilter("ui", "UI", kindCounts.ui + kindCounts.browser),
1459
- kindFilter("terminal", "CLI", kindCounts.terminal),
1460
- kindFilter("tui", "TUI", kindCounts.tui),
1461
- kindFilter("codex-ui", "Codex", kindCounts["codex-ui"])
1462
- );
1463
-
1464
- Array.prototype.forEach.call(document.querySelectorAll(".sub-density-btn"), function (button) {
1465
- button.setAttribute("aria-pressed", String(Number(button.dataset.density) === density));
1466
- });
1467
- getElement("streams").dataset.density = String(density);
1468
-
1469
- var mediaToggle = getElement("media-toggle");
1470
- var liveAvailable = hasLiveStreams(currentData);
1471
- mediaToggle.disabled = !liveAvailable;
1472
- mediaToggle.setAttribute("aria-disabled", String(!liveAvailable));
1473
- mediaToggle.textContent = !liveAvailable ? "Replay" : preferScreenshots ? "Screenshot" : "Live";
1474
- mediaToggle.setAttribute("aria-pressed", String(preferScreenshots));
1475
-
1476
- var gridMode = getElement("grid-mode");
1477
- var focusMode = getElement("focus-mode");
1478
- gridMode.hidden = Boolean(focusedId);
1479
- gridMode.setAttribute("aria-pressed", String(!focusedId));
1480
- focusMode.setAttribute("aria-pressed", String(Boolean(focusedId)));
1481
- focusMode.textContent = focusedId ? "Focused" : "Focus";
1482
-
1483
- var historyToggle = getElement("history-toggle");
1484
- var historyDivider = getElement("history-divider");
1485
- var hasHistory = Boolean(historyIndex && historyIndex.runs && historyIndex.runs.length);
1486
- historyToggle.hidden = !hasHistory;
1487
- historyDivider.hidden = !hasHistory;
1488
- historyToggle.setAttribute("aria-expanded", String(historyOpen));
1006
+ function pip(status, live) {
1007
+ return '<span class="pip" data-status="' + esc(status) + '" data-live="' + (live ? "true" : "false") + '"></span>';
1489
1008
  }
1490
1009
 
1491
- function statusFilter(id, label, count, color) {
1492
- var button = document.createElement("button");
1493
- button.className = "sub-filter";
1494
- button.type = "button";
1495
- button.setAttribute("aria-pressed", String(activeStatus === id));
1496
- button.dataset.filter = id;
1497
- if (color) {
1498
- button.style.color = color;
1499
- var dot = document.createElement("span");
1500
- dot.className = "sub-filter-dot";
1501
- button.append(dot);
1502
- }
1503
- var text = document.createElement("span");
1504
- text.textContent = label;
1505
- text.style.color = activeStatus === id ? "var(--obs-fg-1)" : "var(--obs-fg-2)";
1506
- var cnt = document.createElement("span");
1507
- cnt.className = "sub-filter-count";
1508
- cnt.textContent = String(count).padStart(2, "0");
1509
- button.append(text, cnt);
1510
- button.addEventListener("click", function () {
1511
- activeStatus = id;
1512
- renderSubBar();
1513
- renderStreams();
1514
- });
1515
- return button;
1010
+ // ---------------------------------------------------------------- derive (observer-data.v1 -> display)
1011
+ function laneName(s) { return s.label || (s.sim && s.sim.summary) || s.id || "lane"; }
1012
+ function laneRoute(s) { return (s.ui && s.ui.route) || s.url || (s.terminal && s.terminal.title) || ""; }
1013
+ function laneProgress(s) {
1014
+ if (s.sim && typeof s.sim.progress === "number") return Math.max(0, Math.min(100, Math.round(s.sim.progress)));
1015
+ var t = tone(s.status);
1016
+ return (t === "complete" || t === "failed") ? 100 : 0;
1516
1017
  }
1517
-
1518
- function kindFilter(id, label, count) {
1519
- var button = document.createElement("button");
1520
- button.className = "kind-filter";
1521
- button.type = "button";
1522
- button.setAttribute("aria-pressed", String(activeKind === id));
1523
- var text = document.createElement("span");
1524
- text.textContent = label;
1525
- var cnt = document.createElement("span");
1526
- cnt.className = "kind-filter-count";
1527
- cnt.textContent = String(count).padStart(2, "0");
1528
- button.append(text, cnt);
1529
- button.addEventListener("click", function () {
1530
- activeKind = id;
1531
- renderSubBar();
1532
- renderStreams();
1018
+ function laneStep(s) { return (s.sim && s.sim.currentStep) || s.statusLabel || statusLabel(s.status); }
1019
+ function laneSummary(s) { return (s.sim && s.sim.summary) || ""; }
1020
+ function laneEvents(s) { return s.timeline || []; }
1021
+ function laneArtifacts(s) { return s.artifacts || []; }
1022
+
1023
+ function aspectFor(s) {
1024
+ if (s.kind === "terminal" || s.kind === "tui") return "16 / 10";
1025
+ var vp = s.viewport;
1026
+ if (vp && vp.height > vp.width) return vp.width + " / " + vp.height;
1027
+ return "16 / 9";
1028
+ }
1029
+ function dimsFor(s) {
1030
+ if (s.kind === "terminal") return "CLI";
1031
+ if (s.kind === "tui") return "TUI";
1032
+ if (s.kind === "codex-ui") return "CODEX";
1033
+ var vp = s.viewport;
1034
+ if (vp) return vp.width > vp.height ? (vp.width + TIMES + vp.height) : "MOB";
1035
+ return "SIM";
1036
+ }
1037
+ function initials(name) {
1038
+ var parts = String(name || "").replace(/[^a-zA-Z0-9]+/g, " ").trim().split(" ");
1039
+ return (((parts[0] || "")[0] || "") + ((parts[1] || "")[0] || "")).toUpperCase();
1040
+ }
1041
+ function laneCounts(streams) {
1042
+ var c = { running: 0, complete: 0, blocked: 0, failed: 0 };
1043
+ streams.forEach(function (s) {
1044
+ var t = tone(s.status);
1045
+ if (t === "running") c.running += 1;
1046
+ else if (t === "complete") c.complete += 1;
1047
+ else if (t === "blocked") c.blocked += 1;
1048
+ else if (t === "failed") c.failed += 1;
1533
1049
  });
1534
- return button;
1050
+ return c;
1051
+ }
1052
+ function shortTime(v) {
1053
+ if (!v) return "";
1054
+ var d = new Date(v);
1055
+ if (isNaN(d.getTime())) return String(v);
1056
+ return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
1057
+ }
1058
+ function shortStamp(v) {
1059
+ if (!v) return "";
1060
+ var d = new Date(v);
1061
+ if (isNaN(d.getTime())) return String(v);
1062
+ return d.toLocaleString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
1535
1063
  }
1536
1064
 
1537
- function renderStreams() {
1538
- var root = getElement("streams");
1539
- var inFocus = Boolean(focusedId);
1540
- getElement("grid-shell").hidden = inFocus;
1541
- getElement("focus").hidden = !inFocus;
1542
- if (inFocus) return;
1543
- var visible = visibleStreams();
1544
- var idsKey = JSON.stringify(visible.map(function (stream) { return stream.id; }));
1545
- if (root.dataset.idsKey !== idsKey) {
1546
- root.dataset.idsKey = idsKey;
1547
- root.replaceChildren.apply(root, visible.map(function (stream, index) {
1548
- var tile = renderTile(stream, index);
1549
- tile.style.left = "0px";
1550
- tile.style.top = "0px";
1551
- tile.style.width = "100%";
1552
- return tile;
1553
- }));
1554
- } else {
1555
- Array.prototype.forEach.call(root.children, function (child, index) {
1556
- var fresh = renderTile(visible[index], index);
1557
- child.replaceWith(fresh);
1558
- });
1559
- }
1560
- layoutPackedGrid();
1065
+ function hasText(low, list) {
1066
+ for (var i = 0; i < list.length; i += 1) { if (low.indexOf(list[i]) >= 0) return true; }
1067
+ return false;
1068
+ }
1069
+ function classify(line) {
1070
+ var t = String(line == null ? "" : line).trim();
1071
+ if (!t) return "dim";
1072
+ var c0 = t.charAt(0);
1073
+ if (c0 === "$" || c0 === "#" || c0 === ">") return "cmd";
1074
+ var low = t.toLowerCase();
1075
+ if (t.indexOf("FAIL") >= 0 || t.indexOf("✗") >= 0 || t.indexOf("✘") >= 0 ||
1076
+ hasText(low, ["fail", "error", "not found", "missing", "cannot", "denied", "unreachable", "unresolved"])) return "err";
1077
+ if (t.indexOf("✓") >= 0 || t.indexOf("") >= 0 ||
1078
+ hasText(low, ["ok ", " ok", "passed", "granted", "ready", "success", "succeeded", "completed", "emitted", "scaffolded"])) return "ok";
1079
+ if (t.indexOf("⚠") >= 0 || hasText(low, ["warn", "pending", "timeout", "timed out", "retry", "skipped", "best-effort", "blocked"])) return "warn";
1080
+ if ("┌│└├─╭╰╮╯═┐┘".indexOf(c0) >= 0) return "dim";
1081
+ return "";
1082
+ }
1083
+ function termLines(s) {
1084
+ var raw = (s.terminalPlain != null ? s.terminalPlain : (s.terminal ? s.terminal.tail : "")) || "";
1085
+ var arr = String(raw).split(NL);
1086
+ while (arr.length && arr[arr.length - 1].trim() === "") arr.pop();
1087
+ return arr.map(function (line) { return { text: line, cls: classify(line) }; });
1561
1088
  }
1562
1089
 
1563
- function renderTile(stream, index) {
1564
- var tile = document.createElement("article");
1565
- tile.className = "tile";
1566
- tile.dataset.streamId = stream.id;
1567
- tile.dataset.mediaKey = streamMediaKey(stream);
1568
- tile.dataset.selected = String(stream.id === focusedId);
1569
- var aspect = streamAspect(stream);
1570
- tile.dataset.aspect = aspect.kind;
1571
- tile.dataset.aspectWidth = String(aspect.width);
1572
- tile.dataset.aspectHeight = String(aspect.height);
1573
- tile.title = stream.sim.personaId + " - " + stream.label;
1574
- tile.tabIndex = 0;
1575
- tile.setAttribute("role", "button");
1576
- tile.setAttribute("aria-label", "Open stream " + String(index + 1).padStart(2, "0") + ": " + stream.label);
1577
- tile.addEventListener("click", function () { focusStream(stream.id, true); });
1578
- tile.addEventListener("keydown", function (event) {
1579
- if (event.key === "Enter" || event.key === " ") {
1580
- event.preventDefault();
1581
- focusStream(stream.id, true);
1582
- }
1090
+ function consoleLines() {
1091
+ var rows = [];
1092
+ var lc = currentData.run.lifecycle || [];
1093
+ lc.forEach(function (e) { rows.push({ at: e.at, lvl: "info", text: e.message || e.event || "" }); });
1094
+ (currentData.events || []).forEach(function (e) {
1095
+ var prefix = e.simId ? (e.simId + " ") : "";
1096
+ rows.push({ at: e.at, lvl: (e.level === "error" ? "err" : e.level === "warn" ? "warn" : "info"), text: prefix + (e.message || e.type || "") });
1583
1097
  });
1584
- tile.append(renderTileHead(stream, index), renderTileStream(stream, false), renderTileCap(stream));
1585
- return tile;
1098
+ rows.sort(function (a, b) { return String(a.at || "").localeCompare(String(b.at || "")); });
1099
+ if (!rows.length) rows.push({ at: "", lvl: "dim", text: "No orchestrator log lines recorded for this run." });
1100
+ return rows.map(function (r) { return { t: shortTime(r.at), lvl: r.lvl, text: r.text }; });
1586
1101
  }
1587
1102
 
1588
- function renderTileHead(stream, index) {
1589
- var head = document.createElement("header");
1590
- head.className = "tile-head";
1591
- var idx = document.createElement("span");
1592
- idx.className = "tile-idx";
1593
- idx.textContent = String(index + 1).padStart(2, "0");
1594
- var pip = document.createElement("span");
1595
- pip.className = "pip";
1596
- pip.dataset.status = stream.status || "unknown";
1597
- var role = document.createElement("span");
1598
- role.className = "tile-role";
1599
- role.textContent = stream.kindLabel || stream.kind;
1600
- var name = document.createElement("span");
1601
- name.className = "tile-name";
1602
- name.textContent = compactLabel(stream.sim.personaId || stream.label);
1603
- var view = document.createElement("span");
1604
- view.className = "tile-view";
1605
- view.textContent = viewLabel(stream);
1606
- head.append(idx, pip, role, name, view);
1607
- return head;
1103
+ // ---------------------------------------------------------------- overall
1104
+ function overallStatus() {
1105
+ var ss = currentData.streams;
1106
+ if (ss.some(function (s) { return tone(s.status) === "running"; })) return "running";
1107
+ if (ss.some(function (s) { return s.status === "failed"; })) return "failed";
1108
+ if (ss.some(function (s) { return tone(s.status) === "blocked"; })) return "blocked";
1109
+ if (!ss.length) return currentData.run.status || "queued";
1110
+ return "complete";
1608
1111
  }
1609
-
1610
- function renderTileStream(stream, inFocus) {
1611
- var shell = document.createElement("div");
1612
- shell.className = "tile-stream-shell";
1613
- shell.dataset.focus = String(Boolean(inFocus));
1614
- var aspect = streamAspect(stream);
1615
- shell.style.setProperty("--stream-aspect", aspect.width + " / " + aspect.height);
1616
- shell.dataset.streamWidth = String(aspect.width);
1617
- shell.dataset.streamHeight = String(aspect.height);
1618
- var surface = document.createElement("div");
1619
- surface.className = "media-surface";
1620
-
1621
- if (preferScreenshots && stream.ui && stream.ui.screenshotUrl) {
1622
- var img = document.createElement("img");
1623
- img.src = stream.ui.screenshotUrl;
1624
- img.alt = "Screenshot for " + stream.id;
1625
- surface.append(img);
1626
- } else if ((stream.kind === "terminal" || stream.kind === "tui") && stream.terminal) {
1627
- appendTerminal(surface, stream, inFocus);
1628
- } else if ((stream.kind === "ui" || stream.kind === "browser") && stream.embed && stream.embed.url && !preferScreenshots) {
1629
- var iframe = document.createElement("iframe");
1630
- iframe.src = stream.embed.url;
1631
- iframe.title = stream.label;
1632
- iframe.loading = "lazy";
1633
- surface.append(iframe);
1634
- } else if (stream.kind === "codex-ui") {
1635
- appendCodex(surface, stream);
1636
- } else if (stream.kind === "ui" || stream.kind === "browser") {
1637
- appendBrowser(surface, stream);
1638
- } else {
1639
- appendPlaceholder(surface, stream);
1640
- }
1641
-
1642
- shell.append(surface);
1643
- return shell;
1112
+ function overallPct() {
1113
+ var ss = currentData.streams;
1114
+ if (!ss.length) return 0;
1115
+ var sum = 0;
1116
+ ss.forEach(function (s) { sum += laneProgress(s); });
1117
+ return Math.round(sum / ss.length);
1644
1118
  }
1119
+ function hasLive() { return currentData.streams.some(function (s) { return tone(s.status) === "running"; }); }
1120
+
1121
+ function statusCount(id) { return currentData.streams.filter(function (s) { return tone(s.status) === id; }).length; }
1122
+ function kindCount(id) { return currentData.streams.filter(function (s) { return kindGroup(s.kind) === id; }).length; }
1645
1123
 
1646
- function appendTerminal(surface, stream, inFocus) {
1647
- var terminal = document.createElement("div");
1648
- terminal.className = "terminal-surface";
1649
- var bar = document.createElement("div");
1650
- bar.className = "terminal-bar";
1651
- var prompt = document.createElement("span");
1652
- prompt.className = "terminal-prompt";
1653
- prompt.textContent = "$";
1654
- var title = document.createElement("span");
1655
- title.className = "terminal-title";
1656
- title.textContent = stream.terminal.title || stream.label;
1657
- var status = document.createElement("span");
1658
- status.className = "terminal-status";
1659
- status.textContent = stream.statusLabel || stream.status;
1660
- bar.append(prompt, title, status);
1661
- var body = document.createElement("div");
1662
- body.className = "terminal-body";
1663
- var lines = String(stream.terminalPlain || stream.terminal.tail || "").split("\\n").filter(Boolean);
1664
- if (lines.length === 0) lines = ["No terminal transcript recorded yet."];
1665
- var limit = inFocus ? 120 : 36;
1666
- lines.slice(-limit).forEach(function (line, index) {
1667
- var row = document.createElement("div");
1668
- row.className = "terminal-line";
1669
- var prefix = document.createElement("span");
1670
- prefix.className = "terminal-line-prefix";
1671
- prefix.textContent = String(index + 1).padStart(2, "0");
1672
- var text = document.createElement("span");
1673
- text.className = "terminal-line-text";
1674
- text.textContent = line;
1675
- row.append(prefix, text);
1676
- body.append(row);
1124
+ function filteredStreams() {
1125
+ var q = S.query.trim().toLowerCase();
1126
+ return currentData.streams.filter(function (s) {
1127
+ if (S.statusSel.length && S.statusSel.indexOf(tone(s.status)) < 0) return false;
1128
+ if (S.kindSel.length && S.kindSel.indexOf(kindGroup(s.kind)) < 0) return false;
1129
+ if (q && (laneName(s) + " " + (s.kindLabel || "") + " " + laneRoute(s)).toLowerCase().indexOf(q) < 0) return false;
1130
+ return true;
1677
1131
  });
1678
- terminal.append(bar, body);
1679
- surface.append(terminal);
1680
1132
  }
1681
1133
 
1682
- function appendBrowser(surface, stream) {
1683
- var outer = document.createElement("div");
1684
- outer.className = "live-waiting-surface";
1685
- var inner = document.createElement("div");
1686
- inner.className = "live-waiting-inner";
1687
- var spinner = document.createElement("div");
1688
- spinner.className = "live-spinner";
1689
- spinner.setAttribute("aria-hidden", "true");
1690
- var title = document.createElement("div");
1691
- title.className = "live-waiting-title";
1692
- title.textContent = stream.status === "contract_proof_only" ? "Contract proof only" : "Waiting for live desktop";
1693
- var url = document.createElement("div");
1694
- url.className = "live-waiting-url";
1695
- url.textContent = stream.ui && stream.ui.route ? stream.ui.route : stream.label;
1696
- var status = document.createElement("div");
1697
- status.className = "live-waiting-status";
1698
- status.textContent = stream.sim.currentStep || stream.summary || stream.statusLabel || "Live surface not connected yet.";
1699
- inner.append(spinner, title, url, status);
1700
- outer.append(inner);
1701
- surface.append(outer);
1134
+ // ================================================================ SURFACES
1135
+ function liveTag() {
1136
+ return '<span class="live-tag">' + pip("running", true) + ' Live</span>';
1702
1137
  }
1703
-
1704
- function appendCodex(surface, stream) {
1705
- var outer = document.createElement("div");
1706
- outer.className = "synthetic-codex";
1707
- var frame = document.createElement("div");
1708
- frame.className = "codex-frame";
1709
- var chrome = document.createElement("div");
1710
- chrome.className = "codex-chrome";
1711
- chrome.append(dot(), dot(), dot());
1712
- var url = document.createElement("span");
1713
- url.className = "codex-url";
1714
- url.textContent = "codex.app-server/session";
1715
- chrome.append(url);
1716
- var body = document.createElement("div");
1717
- body.className = "codex-body";
1718
- var rail = document.createElement("div");
1719
- rail.className = "codex-rail";
1720
- rail.append(thread(), thread(), thread());
1721
- var main = document.createElement("div");
1722
- main.className = "codex-main";
1723
- main.append(bubble("dark"), bubble(""), bubble(""), bubble("dark"));
1724
- body.append(rail, main);
1725
- frame.append(chrome, body);
1726
- outer.append(frame);
1727
- surface.append(outer);
1138
+ function waitSurface(s) {
1139
+ var st = s.status, inner;
1140
+ if (st === "blocked" || st === "timed_out") {
1141
+ inner = '<div class="wait-icon" data-tone="amber">' + icon("alert") + '</div>'
1142
+ + '<div class="wait-title">' + (st === "timed_out" ? "Lane timed out" : "Lane blocked") + '</div>'
1143
+ + '<div class="wait-sub">' + esc(laneStep(s)) + '</div>';
1144
+ } else if (st === "failed") {
1145
+ inner = '<div class="wait-icon" data-tone="red">' + icon("x") + '</div>'
1146
+ + '<div class="wait-title">Lane failed</div>'
1147
+ + '<div class="wait-sub">' + esc(laneStep(s)) + '</div>';
1148
+ } else if (st === "contract_proof_only") {
1149
+ inner = '<div class="wait-icon" data-tone="green">' + icon("check") + '</div>'
1150
+ + '<div class="wait-title">Contract proof only</div>'
1151
+ + '<div class="wait-sub">' + esc(laneSummary(s) || laneStep(s)) + '</div>';
1152
+ } else {
1153
+ inner = '<div class="wait-spinner"></div>'
1154
+ + '<div class="wait-title">Waiting for substrate</div>'
1155
+ + '<div class="wait-sub">' + esc(laneRoute(s) || laneStep(s)) + '</div>';
1156
+ }
1157
+ return '<div class="wait"><div class="wait-inner">' + inner + '</div></div>';
1728
1158
  }
1729
-
1730
- function appendPlaceholder(surface, stream) {
1731
- var holder = document.createElement("div");
1732
- holder.className = "placeholder-surface";
1733
- var box = document.createElement("div");
1734
- box.className = "placeholder-box";
1735
- box.textContent = stream.summary || "Observer lane awaiting evidence.";
1736
- holder.append(box);
1737
- surface.append(holder);
1159
+ function browserSurface(s) {
1160
+ var live = tone(s.status) === "running";
1161
+ var route = laneRoute(s) || "(local)";
1162
+ var shot = (s.ui && s.ui.screenshotUrl) || (s.embed && s.embed.kind === "screenshot" && s.embed.url);
1163
+ var body;
1164
+ if (shot) body = '<img class="surface-fill" style="object-fit:cover;object-position:top" src="' + esc(shot) + '" alt="viewport screenshot"/>';
1165
+ else body = '<div class="bw-app-wait"><div class="wait-spinner" style="width:24px;height:24px"></div>'
1166
+ + '<div class="mono" style="font-size:9px">' + esc(route) + '</div>'
1167
+ + '<div style="font-size:10px">' + esc(laneStep(s)) + '</div></div>';
1168
+ return '<div class="bw">'
1169
+ + '<div class="bw-chrome"><div class="bw-dots"><i></i><i></i><i></i></div>'
1170
+ + '<div class="bw-url">' + icon("lock", 8) + '<span>' + esc(route) + '</span></div></div>'
1171
+ + '<div class="bw-viewport">' + body + '</div>'
1172
+ + (live ? liveTag() : "")
1173
+ + '</div>';
1738
1174
  }
1739
-
1740
- function renderTileCap(stream) {
1741
- var cap = document.createElement("footer");
1742
- cap.className = "tile-cap";
1743
- cap.dataset.status = stream.status || "unknown";
1744
- var eyebrow = document.createElement("span");
1745
- eyebrow.className = "tile-cap-eyebrow";
1746
- eyebrow.textContent = stream.kindLabel || stream.kind;
1747
- var text = document.createElement("span");
1748
- text.className = "tile-cap-text";
1749
- text.textContent = stream.sim.currentStep || stream.statusLabel || stream.status;
1750
- var bar = document.createElement("div");
1751
- bar.className = "tile-cap-bar";
1752
- var fill = document.createElement("span");
1753
- fill.style.width = String(Math.max(8, Math.min(100, Number(stream.sim.progress || 0)))) + "%";
1754
- bar.append(fill);
1755
- cap.append(eyebrow, text, bar);
1756
- return cap;
1175
+ function terminalSurface(s, focus) {
1176
+ var lines = termLines(s);
1177
+ var live = tone(s.status) === "running";
1178
+ var shown = focus ? lines : lines.slice(-9);
1179
+ var start = lines.length - shown.length;
1180
+ var rows = shown.map(function (ln, i) {
1181
+ return '<div class="term-line">'
1182
+ + (focus ? '<span class="term-num">' + pad2(start + i + 1) + '</span>' : '')
1183
+ + '<span class="term-txt ' + ln.cls + '">' + esc(ln.text || " ") + '</span></div>';
1184
+ }).join("");
1185
+ var title = s.kind === "tui" ? (laneRoute(s) || "codex --tui") : (laneName(s) || laneRoute(s));
1186
+ var stat = s.kind === "tui" ? "PTY" : "SNAPSHOT";
1187
+ return '<div class="term">'
1188
+ + '<div class="term-bar"><span class="tprompt">$</span><span class="ttitle">' + esc(title) + '</span><span class="tstat">' + stat + '</span></div>'
1189
+ + '<div class="term-body">' + rows
1190
+ + (live ? '<div class="term-line"><span class="term-txt cmd">$<span class="term-caret"></span></span></div>' : '')
1191
+ + '</div></div>';
1757
1192
  }
1758
-
1759
- function renderFocus() {
1760
- var focus = getElement("focus");
1761
- document.body.classList.toggle("focused", Boolean(focusedId));
1762
- if (!focusedId) {
1763
- focus.replaceChildren();
1764
- return;
1193
+ function codexSurface(s) {
1194
+ var live = tone(s.status) === "running";
1195
+ var parts = [];
1196
+ var summary = laneSummary(s) || (s.codex && s.codex.contract) || laneStep(s);
1197
+ if (summary) parts.push('<div class="cx-msg agent">' + esc(summary) + '</div>');
1198
+ var receipts = termLines(s);
1199
+ receipts.forEach(function (ln, i) {
1200
+ var pending = live && i === receipts.length - 1;
1201
+ parts.push('<div class="cx-tool">' + (pending ? '<span class="cx-spin"></span>' : icon("check", 12)) + '<span>' + esc(ln.text) + '</span></div>');
1202
+ });
1203
+ if (live) parts.push('<div class="cx-tool"><span class="cx-spin"></span><span>thinking…</span></div>');
1204
+ return '<div class="codex">'
1205
+ + '<div class="codex-bar"><span class="cx-spark">' + icon("spark", 11) + '</span> ' + esc(laneRoute(s) || "codex.app-server · session") + '</div>'
1206
+ + '<div class="codex-body">' + parts.join("") + '</div>'
1207
+ + (live ? liveTag() : "")
1208
+ + '</div>';
1209
+ }
1210
+ function streamSurface(s, focus) {
1211
+ var k = s.kind, st = s.status;
1212
+ var hasTail = !!(s.terminal && s.terminal.tail && String(s.terminal.tail).trim());
1213
+ if ((st === "blocked" || st === "timed_out") && !hasTail) return waitSurface(s);
1214
+ if (k === "ui" || k === "browser") {
1215
+ if (st === "blocked" || st === "timed_out" || st === "failed" || st === "contract_proof_only") return waitSurface(s);
1216
+ return browserSurface(s);
1765
1217
  }
1766
- var stream = currentData.streams.find(function (item) { return item.id === focusedId; });
1767
- if (!stream) {
1768
- focusedId = null;
1769
- focus.replaceChildren();
1770
- return;
1218
+ if (k === "terminal" || k === "tui") return hasTail ? terminalSurface(s, focus) : waitSurface(s);
1219
+ if (k === "codex-ui") {
1220
+ if (st === "contract_proof_only" && !hasTail) return waitSurface(s);
1221
+ return codexSurface(s);
1771
1222
  }
1772
- var rail = document.createElement("aside");
1773
- rail.className = "focus-rail";
1774
- currentData.streams.forEach(function (candidate, index) {
1775
- var item = document.createElement("button");
1776
- item.className = "focus-rail-item";
1777
- item.type = "button";
1778
- item.dataset.selected = String(candidate.id === focusedId);
1779
- item.addEventListener("click", function () { focusStream(candidate.id, true); });
1780
- var idx = document.createElement("span");
1781
- idx.className = "focus-rail-idx";
1782
- idx.textContent = String(index + 1).padStart(2, "0");
1783
- var pip = document.createElement("span");
1784
- pip.className = "pip";
1785
- pip.dataset.status = candidate.status || "unknown";
1786
- item.append(idx, pip);
1787
- rail.append(item);
1788
- });
1789
-
1790
- var stage = document.createElement("section");
1791
- stage.className = "focus-stage";
1792
- var toolbar = document.createElement("header");
1793
- toolbar.className = "focus-toolbar";
1794
- var back = document.createElement("button");
1795
- back.className = "focus-back";
1796
- back.type = "button";
1797
- back.textContent = "Back to grid";
1798
- back.addEventListener("click", exitFocus);
1799
- var id = document.createElement("span");
1800
- id.className = "focus-id";
1801
- id.textContent = stream.id;
1802
- var persona = document.createElement("span");
1803
- persona.className = "focus-persona";
1804
- persona.textContent = stream.sim.personaId + " / " + stream.kindLabel;
1805
- var badge = document.createElement("span");
1806
- badge.className = "focus-status-badge";
1807
- badge.dataset.status = stream.status || "unknown";
1808
- var badgePip = document.createElement("span");
1809
- badgePip.className = "pip";
1810
- badgePip.dataset.status = stream.status || "unknown";
1811
- var badgeText = document.createElement("span");
1812
- badgeText.textContent = stream.statusLabel || stream.status;
1813
- badge.append(badgePip, badgeText);
1814
- var stats = document.createElement("span");
1815
- stats.className = "focus-stats";
1816
- stats.textContent = viewLabel(stream) + " - events " + stream.timeline.length;
1817
- toolbar.append(back, id, persona, badge, stats);
1818
- var area = document.createElement("div");
1819
- area.className = "focus-stage-area";
1820
- area.append(renderTileStream(stream, true));
1821
- stage.append(toolbar, area);
1822
-
1823
- var side = renderFocusSide(stream);
1824
- focus.replaceChildren(rail, stage, side);
1825
- fitFocusMedia();
1223
+ return waitSurface(s);
1826
1224
  }
1827
1225
 
1828
- function renderFocusSide(stream) {
1829
- var side = document.createElement("aside");
1830
- side.className = "focus-side";
1831
- var context = document.createElement("div");
1832
- context.className = "focus-side-context";
1833
- var head = document.createElement("div");
1834
- head.className = "focus-side-head";
1835
- var eyebrow = document.createElement("span");
1836
- eyebrow.className = "obs-eyebrow";
1837
- eyebrow.textContent = "Persona";
1838
- var h2 = document.createElement("h2");
1839
- h2.textContent = currentData.run.persona.name;
1840
- var goal = document.createElement("div");
1841
- goal.className = "focus-side-goal";
1842
- var goalEyebrow = document.createElement("span");
1843
- goalEyebrow.className = "obs-eyebrow";
1844
- goalEyebrow.textContent = "Goal";
1845
- var goalText = document.createElement("div");
1846
- goalText.className = "focus-side-goal-text";
1847
- goalText.textContent = currentData.run.scenario.goal;
1848
- goal.append(goalEyebrow, goalText);
1849
- head.append(eyebrow, h2, goal);
1850
- var think = document.createElement("div");
1851
- think.className = "focus-side-thinking";
1852
- var thinkRow = document.createElement("div");
1853
- thinkRow.className = "focus-side-thinking-row";
1854
- var dotNode = document.createElement("span");
1855
- dotNode.className = "pulse-dot";
1856
- var thinkEyebrow = document.createElement("span");
1857
- thinkEyebrow.className = "obs-eyebrow";
1858
- thinkEyebrow.textContent = "Now";
1859
- thinkRow.append(dotNode, thinkEyebrow);
1860
- var thinkText = document.createElement("div");
1861
- thinkText.className = "focus-side-thinking-text";
1862
- thinkText.textContent = stream.sim.currentStep || stream.summary || stream.statusLabel;
1863
- think.append(thinkRow, thinkText);
1864
- context.append(head, think);
1865
-
1866
- var tabs = document.createElement("div");
1867
- tabs.className = "focus-tabs";
1868
- tabs.setAttribute("role", "tablist");
1869
- var tabBody = document.createElement("div");
1870
- tabBody.className = "focus-tabbody";
1871
- var tabDefs = [
1872
- { id: "events", label: "Events", count: stream.timeline.length },
1873
- { id: "actions", label: "Trace", count: stream.timeline.length },
1874
- { id: "artifacts", label: "Files", count: stream.artifacts.length },
1875
- { id: "logs", label: "Logs", count: stream.terminalPlain ? stream.terminalPlain.split("\\n").length : 0 }
1226
+ // ================================================================ HEADER
1227
+ function statusCountsModel() {
1228
+ var lc = laneCounts(currentData.streams);
1229
+ return [
1230
+ { id: "running", label: "live", color: "var(--accent-2)", count: lc.running },
1231
+ { id: "complete", label: "done", color: "var(--green)", count: lc.complete },
1232
+ { id: "blocked", label: "blocked", color: "var(--amber)", count: lc.blocked },
1233
+ { id: "failed", label: "failed", color: "var(--red)", count: lc.failed }
1876
1234
  ];
1877
- tabDefs.forEach(function (tab) {
1878
- var button = document.createElement("button");
1879
- button.className = "focus-tab";
1880
- button.type = "button";
1881
- button.setAttribute("role", "tab");
1882
- button.setAttribute("aria-selected", String(activeFocusTab === tab.id));
1883
- button.textContent = tab.label + " ";
1884
- var badge = document.createElement("span");
1885
- badge.className = "focus-tab-badge";
1886
- badge.textContent = String(tab.count);
1887
- button.append(badge);
1888
- button.addEventListener("click", function () {
1889
- activeFocusTab = tab.id;
1890
- renderFocus();
1891
- });
1892
- tabs.append(button);
1893
- });
1894
- fillTabBody(tabBody, stream);
1895
- side.append(context, tabs, tabBody);
1896
- return side;
1897
1235
  }
1898
-
1899
- function fillTabBody(tabBody, stream) {
1900
- if (activeFocusTab === "artifacts") {
1901
- tabBody.replaceChildren.apply(tabBody, stream.artifacts.map(function (artifact) {
1902
- var row = document.createElement("div");
1903
- row.className = "artifact-row";
1904
- var icon = document.createElement("span");
1905
- icon.className = "event-row-icon";
1906
- icon.textContent = "file";
1907
- var text = document.createElement("div");
1908
- text.className = "artifact-row-text";
1909
- var link = document.createElement("a");
1910
- link.href = artifact.path;
1911
- link.textContent = artifact.label;
1912
- text.append(link);
1913
- var meta = document.createElement("div");
1914
- meta.className = "artifact-row-meta";
1915
- meta.textContent = artifact.kind + " - " + artifact.path;
1916
- text.append(meta);
1917
- row.append(icon, text);
1918
- return row;
1919
- }));
1920
- return;
1236
+ function buildStatusIndicator(counts, pct) {
1237
+ var shown = counts.filter(function (c) { return c.count > 0; });
1238
+ if (S.statusViz === "bar") {
1239
+ var segs = shown.map(function (c) {
1240
+ return '<span class="si-bar-seg" title="' + c.count + ' ' + c.label + '" data-action="status:' + c.id + '" style="flex-grow:' + c.count + ';background:' + c.color + '"></span>';
1241
+ }).join("");
1242
+ return '<div class="si-bar" role="group" aria-label="Lane status"><div class="si-bar-track">' + segs + '</div><span class="si-bar-label mono">' + pct + '%</span></div>';
1921
1243
  }
1922
- if (activeFocusTab === "logs") {
1923
- var surface = document.createElement("div");
1924
- surface.className = "terminal-surface";
1925
- var body = document.createElement("div");
1926
- body.className = "terminal-body";
1927
- String(stream.terminalPlain || "No log text recorded.").split("\\n").filter(Boolean).forEach(function (line, index) {
1928
- var row = document.createElement("div");
1929
- row.className = "terminal-line";
1930
- var prefix = document.createElement("span");
1931
- prefix.className = "terminal-line-prefix";
1932
- prefix.textContent = String(index + 1).padStart(2, "0");
1933
- var text = document.createElement("span");
1934
- text.className = "terminal-line-text";
1935
- text.textContent = line;
1936
- row.append(prefix, text);
1937
- body.append(row);
1938
- });
1939
- surface.append(body);
1940
- tabBody.replaceChildren(surface);
1941
- return;
1244
+ if (S.statusViz === "ring") {
1245
+ var present = shown;
1246
+ var sum = present.reduce(function (a, c) { return a + c.count; }, 0) || 1;
1247
+ var issues = counts.filter(function (c) { return c.id === "blocked" || c.id === "failed"; }).reduce(function (a, c) { return a + c.count; }, 0);
1248
+ var running = (counts.filter(function (c) { return c.id === "running"; })[0] || {}).count || 0;
1249
+ var R = 9, CIRC = 2 * Math.PI * R, acc = 0;
1250
+ var arcs = present.map(function (c) {
1251
+ var frac = c.count / sum, len = frac * CIRC;
1252
+ var seg = '<circle cx="13" cy="13" r="' + R + '" fill="none" stroke="' + c.color + '" stroke-width="3.5" stroke-dasharray="' + len + ' ' + (CIRC - len) + '" stroke-dashoffset="' + (-acc * CIRC) + '" transform="rotate(-90 13 13)"></circle>';
1253
+ acc += frac;
1254
+ return seg;
1255
+ }).join("");
1256
+ var meta = issues > 0
1257
+ ? '<span class="si-ring-issue mono" style="color:var(--amber)">' + issues + ' ⚠</span>'
1258
+ : running > 0
1259
+ ? '<span class="si-ring-issue mono" style="color:var(--accent-2)">' + running + ' live</span>'
1260
+ : '<span class="si-ring-issue mono" style="color:var(--green)">done</span>';
1261
+ return '<button class="si-ring" data-action="status:' + (issues ? "blocked" : "all") + '" title="' + pct + '% complete">'
1262
+ + '<svg width="26" height="26" viewBox="0 0 26 26"><circle cx="13" cy="13" r="' + R + '" fill="none" stroke="var(--line)" stroke-width="3.5"></circle>' + arcs + '</svg>'
1263
+ + '<span class="si-ring-meta"><span class="si-ring-pct mono">' + pct + '%</span>' + meta + '</span></button>';
1942
1264
  }
1943
- var rows = stream.timeline.length ? stream.timeline : currentData.events.slice(-12);
1944
- tabBody.replaceChildren.apply(tabBody, rows.map(function (event) { return eventRow(event); }));
1265
+ return '<div class="si-badges" role="group" aria-label="Lane status">' + shown.map(function (c) {
1266
+ var glow = c.id === "running" ? ";box-shadow:0 0 0 3px color-mix(in oklab, " + c.color + " 22%, transparent)" : "";
1267
+ return '<button class="si-badge" aria-pressed="' + (S.statusSel.indexOf(c.id) >= 0 ? "true" : "false") + '" title="' + c.count + ' ' + c.label + '" data-action="status:' + c.id + '">'
1268
+ + '<span class="si-dot" style="background:' + c.color + glow + '"></span><span class="si-n mono">' + c.count + '</span></button>';
1269
+ }).join("") + '</div>';
1945
1270
  }
1946
-
1947
- function eventRow(event) {
1948
- var row = document.createElement("div");
1949
- row.className = "event-row";
1950
- row.dataset.kind = event.level || event.type || "info";
1951
- var icon = document.createElement("span");
1952
- icon.className = "event-row-icon";
1953
- icon.textContent = event.level || "info";
1954
- var body = document.createElement("div");
1955
- var text = document.createElement("div");
1956
- text.className = "event-row-text";
1957
- text.textContent = event.message;
1958
- var meta = document.createElement("div");
1959
- meta.className = "event-row-meta";
1960
- meta.textContent = compact([shortTime(event.at), event.type, event.streamId]).join(" - ");
1961
- body.append(text, meta);
1962
- row.append(icon, body);
1963
- return row;
1271
+ function buildHeader() {
1272
+ var run = currentData.run;
1273
+ var overall = overallStatus(), pct = overallPct();
1274
+ var counts = statusCountsModel();
1275
+ var title = (run.scenario && run.scenario.title) || "Mimetic run";
1276
+ var persona = (run.persona && run.persona.name) || "";
1277
+ return '<header class="hdr">'
1278
+ + '<div class="hdr-brand"><span class="brand-mark">' + icon("live", 15) + '</span><span class="brand-word">Mimetic <b>Observer</b></span></div>'
1279
+ + '<div class="hdr-run">'
1280
+ + '<div class="hdr-run-title" title="' + esc(title) + '">' + esc(title) + '</div>'
1281
+ + '<div class="hdr-run-sub"><span class="hdr-persona">' + esc(persona) + '</span><span class="dot-sep"></span>'
1282
+ + '<button class="run-chip mono" data-action="toggle-details" aria-expanded="' + (S.detailsOpen ? "true" : "false") + '">' + esc(run.runId || "run") + '</button></div></div>'
1283
+ + '<div class="hdr-spacer"></div>'
1284
+ + buildStatusIndicator(counts, pct)
1285
+ + '<span class="status-pill" data-tone="' + tone(overall) + '">' + pip(overall, tone(overall) === "running") + statusLabel(overall) + '<span class="pct mono">' + pct + '%</span></span>'
1286
+ + '<button class="hdr-runs" data-action="open-history">' + icon("clock", 15) + '<span>Runs</span></button>'
1287
+ + '<button class="icon-btn" data-action="toggle-tweaks" aria-label="Settings" aria-pressed="' + (S.tweaksOpen ? "true" : "false") + '">' + icon("sliders") + '</button>'
1288
+ + '<button class="icon-btn" data-action="toggle-theme" aria-label="Toggle theme">' + icon(S.theme === "light" ? "moon" : "sun") + '</button>'
1289
+ + '</header>';
1964
1290
  }
1965
1291
 
1966
- function renderHistoryPanel() {
1967
- var panel = getElement("history-panel");
1968
- var current = getElement("history-current");
1969
- var list = getElement("history-list");
1970
- var hasHistory = Boolean(historyIndex && historyIndex.runs && historyIndex.runs.length);
1971
- panel.hidden = !historyOpen || !hasHistory;
1972
- if (!hasHistory) {
1973
- current.replaceChildren();
1974
- list.replaceChildren();
1975
- return;
1976
- }
1977
- current.textContent = "Current " + currentData.run.runId + " - Latest " + (historyIndex.latestRunId || "none");
1978
- list.replaceChildren.apply(list, historyIndex.runs.slice(0, 80).map(function (run) {
1979
- var link = document.createElement("a");
1980
- link.className = "history-run";
1981
- link.href = run.href;
1982
- link.dataset.active = String(run.runId === currentData.run.runId);
1983
- var id = document.createElement("span");
1984
- id.className = "history-run-id";
1985
- id.textContent = run.runId;
1986
- var status = document.createElement("span");
1987
- status.className = "history-run-status";
1988
- var pip = document.createElement("span");
1989
- pip.className = "pip";
1990
- pip.dataset.status = run.status || "unknown";
1991
- var statusText = document.createElement("span");
1992
- statusText.textContent = run.status || "unknown";
1993
- status.append(pip, statusText);
1994
- var meta = document.createElement("span");
1995
- meta.className = "history-run-meta";
1996
- meta.textContent = compact([run.mode, run.createdAt ? shortTime(run.createdAt) : null]).join(" - ");
1997
- var counts = document.createElement("span");
1998
- counts.className = "history-run-counts";
1999
- counts.textContent = String(run.streamCount || 0) + " streams";
2000
- link.append(id, status, meta, counts);
2001
- return link;
2002
- }));
1292
+ // ================================================================ TOOLBAR
1293
+ var STATUS_OPTS = [
1294
+ { id: "running", label: "Live", color: "var(--accent-2)" },
1295
+ { id: "complete", label: "Done", color: "var(--green)" },
1296
+ { id: "blocked", label: "Blocked", color: "var(--amber)" },
1297
+ { id: "failed", label: "Failed", color: "var(--red)" }
1298
+ ];
1299
+ var KIND_OPTS = [
1300
+ { id: "ui", label: "Browser" },
1301
+ { id: "terminal", label: "CLI" },
1302
+ { id: "tui", label: "TUI" },
1303
+ { id: "codex-ui", label: "Codex" }
1304
+ ];
1305
+ function optLabel(opts, id) {
1306
+ for (var i = 0; i < opts.length; i += 1) { if (opts[i].id === id) return opts[i].label; }
1307
+ return id;
2003
1308
  }
2004
-
2005
- function layoutPackedGrid() {
2006
- var shell = getElement("grid-shell");
2007
- var root = getElement("streams");
2008
- var tiles = Array.prototype.slice.call(root.querySelectorAll(".tile"));
2009
- if (tiles.length === 0) {
2010
- root.style.height = "0px";
2011
- return;
2012
- }
2013
- var styles = getComputedStyle(shell);
2014
- var padX = parseFloat(styles.paddingLeft || "16") + parseFloat(styles.paddingRight || "16");
2015
- var innerW = Math.max(0, shell.clientWidth - padX);
2016
- var gap = 8;
2017
- var headerH = 22;
2018
- var captionH = 22;
2019
- var rowStreamH = GRID_SCALE_STREAM_HEIGHT[density] || GRID_SCALE_STREAM_HEIGHT[4];
2020
- var x = 0;
2021
- var y = 0;
2022
- var rowH = 0;
2023
- tiles.forEach(function (tile) {
2024
- var aspectW = Number(tile.dataset.aspectWidth) || 16;
2025
- var aspectH = Number(tile.dataset.aspectHeight) || 9;
2026
- var ratio = aspectW / aspectH || 16 / 9;
2027
- var streamH = rowStreamH;
2028
- var tileW = Math.round(streamH * ratio);
2029
- if (tileW > innerW) {
2030
- tileW = Math.round(innerW);
2031
- streamH = tileW / ratio;
2032
- }
2033
- var tileH = Math.round(streamH + headerH + captionH);
2034
- if (x > 0 && x + tileW > innerW) {
2035
- x = 0;
2036
- y = y + rowH + gap;
2037
- rowH = 0;
2038
- }
2039
- tile.style.left = String(x) + "px";
2040
- tile.style.top = String(y) + "px";
2041
- tile.style.width = String(tileW) + "px";
2042
- tile.style.height = String(tileH) + "px";
2043
- x = x + tileW + gap;
2044
- rowH = Math.max(rowH, tileH);
2045
- });
2046
- root.style.height = String(y + rowH) + "px";
1309
+ function ddTrigger(kind, label, iconName, sel, opts) {
1310
+ var allOn = sel.length === 0;
1311
+ var summary = allOn ? label : (sel.length === 1 ? optLabel(opts, sel[0]) : (sel.length + " selected"));
1312
+ return '<div class="dd"><button id="dd-trigger-' + kind + '" class="dd-trigger" data-action="dd:' + kind + '" data-active="' + (allOn ? "false" : "true") + '" aria-haspopup="true" aria-expanded="' + (openDd === kind ? "true" : "false") + '">'
1313
+ + '<span class="dd-trigger-inner">' + (iconName ? icon(iconName, 14) : "") + '<span>' + esc(summary) + '</span></span>'
1314
+ + (allOn ? "" : '<span class="dd-badge">' + sel.length + '</span>')
1315
+ + icon("caret", 13) + '</button></div>';
2047
1316
  }
2048
-
2049
- function fitFocusMedia() {
2050
- requestAnimationFrame(function () {
2051
- Array.prototype.forEach.call(document.querySelectorAll(".focus-stage-area .tile-stream-shell"), function (shell) {
2052
- var area = shell.parentElement;
2053
- if (!area) return;
2054
- var width = Number(shell.dataset.streamWidth) || 16;
2055
- var height = Number(shell.dataset.streamHeight) || 9;
2056
- var rect = area.getBoundingClientRect();
2057
- if (rect.width <= 0 || rect.height <= 0) return;
2058
- var scale = Math.min(rect.width / width, rect.height / height);
2059
- shell.style.width = String(Math.max(1, Math.floor(width * scale))) + "px";
2060
- shell.style.height = String(Math.max(1, Math.floor(height * scale))) + "px";
2061
- });
2062
- });
1317
+ function buildToolbar() {
1318
+ var total = currentData.streams.length;
1319
+ var shown = filteredStreams().length;
1320
+ return '<div class="toolbar">'
1321
+ + ddTrigger("status", "Status", "filter", S.statusSel, STATUS_OPTS)
1322
+ + ddTrigger("kind", "Kind", null, S.kindSel, KIND_OPTS)
1323
+ + '<label class="tb-search">' + icon("search", 14)
1324
+ + '<input data-role="search" type="text" placeholder="Filter lanes…" value="' + esc(S.query) + '" aria-label="Filter lanes by name or persona"/></label>'
1325
+ + '<span class="tb-result mono">' + shown + ' / ' + total + '</span>'
1326
+ + '<div class="tb-spacer"></div>'
1327
+ + '<div class="seg view-seg" role="group" aria-label="View mode">'
1328
+ + '<button aria-pressed="' + (S.view === "grid" ? "true" : "false") + '" title="Grid view" data-action="view:grid">' + icon("grid", 15) + '</button>'
1329
+ + '<button aria-pressed="' + (S.view === "focus" ? "true" : "false") + '" title="Focus view" data-action="view:focus">' + icon("focus", 15) + '</button></div>'
1330
+ + '<div class="seg media-seg" role="group" aria-label="Media mode">'
1331
+ + '<button aria-pressed="' + (S.media === "live" ? "true" : "false") + '"' + (hasLive() ? "" : " disabled") + ' title="Live streams" data-action="media:live">' + icon("live", 15) + '</button>'
1332
+ + '<button aria-pressed="' + (S.media === "screenshot" ? "true" : "false") + '" title="Screenshots" data-action="media:screenshot">' + icon("image", 15) + '</button></div>'
1333
+ + '<div class="seg density-seg" role="group" aria-label="Tile density">'
1334
+ + '<button aria-pressed="' + (S.density === "comfortable" ? "true" : "false") + '" title="Comfortable" data-action="density:comfortable">' + icon("comfy", 15) + '</button>'
1335
+ + '<button aria-pressed="' + (S.density === "compact" ? "true" : "false") + '" title="Compact" data-action="density:compact">' + icon("compact", 15) + '</button>'
1336
+ + '<button aria-pressed="' + (S.density === "dense" ? "true" : "false") + '" title="Dense" data-action="density:dense">' + icon("dense", 15) + '</button></div>'
1337
+ + '</div>';
2063
1338
  }
2064
-
2065
- function visibleStreams() {
2066
- return currentData.streams.filter(function (stream) {
2067
- if (activeKind !== "all") {
2068
- if (activeKind === "ui" && stream.kind !== "ui" && stream.kind !== "browser") return false;
2069
- if (activeKind !== "ui" && stream.kind !== activeKind) return false;
2070
- }
2071
- if (activeStatus === "all") return true;
2072
- if (activeStatus === "running") return stream.status === "running" || stream.status === "preparing";
2073
- if (activeStatus === "blocked") return stream.status === "blocked" || stream.status === "failed";
2074
- if (activeStatus === "complete") return stream.status === "complete";
2075
- if (activeStatus === "proof") return stream.status === "contract_proof_only";
2076
- return true;
2077
- });
1339
+ function buildDdMenu() {
1340
+ if (!openDd) return "";
1341
+ var kind = openDd;
1342
+ var label = kind === "status" ? "Status" : "Kind";
1343
+ var opts = kind === "status" ? STATUS_OPTS : KIND_OPTS;
1344
+ var sel = kind === "status" ? S.statusSel : S.kindSel;
1345
+ var cnt = kind === "status" ? statusCount : kindCount;
1346
+ var allOn = sel.length === 0;
1347
+ var rows = '<button class="dd-row dd-row-all" data-action="dd-all:' + kind + '"><span class="dd-check" data-on="' + (allOn ? "true" : "false") + '">' + (allOn ? icon("check", 11) : "") + '</span><span class="dd-row-label">All ' + label.toLowerCase() + '</span></button><div class="dd-sep"></div>';
1348
+ rows += opts.map(function (o) {
1349
+ var on = sel.indexOf(o.id) >= 0;
1350
+ return '<button class="dd-row" data-action="dd-row:' + kind + ':' + o.id + '"><span class="dd-check" data-on="' + (on ? "true" : "false") + '">' + (on ? icon("check", 11) : "") + '</span>'
1351
+ + (o.color ? '<span class="dd-dot" style="background:' + o.color + '"></span>' : "")
1352
+ + '<span class="dd-row-label">' + o.label + '</span><span class="dd-row-n mono">' + cnt(o.id) + '</span></button>';
1353
+ }).join("");
1354
+ return '<div class="dd-menu" id="dd-menu" data-dd="' + kind + '" role="menu" style="visibility:hidden">' + rows + '</div>';
2078
1355
  }
2079
1356
 
2080
- function countStatuses(streams) {
2081
- return streams.reduce(function (acc, stream) {
2082
- if (stream.status === "running" || stream.status === "preparing") acc.running += 1;
2083
- else if (stream.status === "blocked") acc.blocked += 1;
2084
- else if (stream.status === "failed") acc.failed += 1;
2085
- else if (stream.status === "complete") acc.complete += 1;
2086
- else if (stream.status === "contract_proof_only") acc.proof += 1;
2087
- else acc.queued += 1;
2088
- return acc;
2089
- }, { running: 0, blocked: 0, failed: 0, complete: 0, proof: 0, queued: 0 });
1357
+ // ================================================================ GRID
1358
+ var TILE_MIN = { comfortable: "440px", compact: "330px", dense: "240px" };
1359
+ function buildTile(s, i) {
1360
+ var live = tone(s.status) === "running";
1361
+ return '<article class="tile" data-selected="' + (s.id === S.focusedId ? "true" : "false") + '" tabindex="0" role="button" data-action="open:' + esc(s.id) + '" aria-label="Open lane ' + pad2(i + 1) + ': ' + esc(laneName(s)) + '">'
1362
+ + '<header class="tile-head"><span class="tile-idx mono">' + pad2(i + 1) + '</span>' + pip(s.status, live)
1363
+ + '<span class="tile-name" title="' + esc(laneName(s)) + '">' + esc(laneName(s)) + '</span>'
1364
+ + '<span class="kind-badge" data-kind="' + esc(s.kind) + '">' + esc(s.kindLabel || s.kind) + '</span>'
1365
+ + '<span class="tile-dims mono">' + esc(dimsFor(s)) + '</span>'
1366
+ + '<span class="tile-open">' + icon("expand", 13) + '</span></header>'
1367
+ + '<div class="tile-surface" style="--aspect:' + aspectFor(s) + '">' + streamSurface(s, false) + '</div>'
1368
+ + '<footer class="tile-foot" data-status="' + esc(s.status) + '"><span class="tile-foot-text">' + (live ? '<span class="now-dot">▸</span>' : '') + esc(laneStep(s)) + '</span>'
1369
+ + '<span class="mini-prog"><span style="width:' + laneProgress(s) + '%"></span></span></footer>'
1370
+ + '</article>';
2090
1371
  }
2091
-
2092
- function countKinds(streams) {
2093
- return streams.reduce(function (acc, stream) {
2094
- acc[stream.kind] = (acc[stream.kind] || 0) + 1;
2095
- return acc;
2096
- }, { ui: 0, browser: 0, terminal: 0, tui: 0, "codex-ui": 0, artifact: 0, summary: 0 });
1372
+ function buildGrid() {
1373
+ var streams = filteredStreams();
1374
+ if (!streams.length) {
1375
+ var msg = S.query ? ('Nothing matches "' + esc(S.query) + '".') : "Try a different status or kind.";
1376
+ return '<div class="grid-scroll"><div class="grid"><div class="grid-empty">'
1377
+ + '<div class="ge-icon">' + icon("search", 20) + '</div><h3>No lanes match your filters</h3>'
1378
+ + '<div>' + msg + ' <button class="linklike" data-action="clear-filters">Clear filters</button></div></div></div></div>';
1379
+ }
1380
+ var tiles = streams.map(function (s, i) { return buildTile(s, i); }).join("");
1381
+ return '<div class="grid-scroll"><div class="grid" style="--tile-min:' + TILE_MIN[S.density] + '">' + tiles + '</div></div>';
2097
1382
  }
2098
1383
 
2099
- function streamAspect(stream) {
2100
- if (stream.kind === "terminal" || stream.kind === "tui") return { kind: "terminal", width: 16, height: 10 };
2101
- var vp = stream.viewport || {};
2102
- var width = Number(vp.width) || (stream.kind === "codex-ui" ? 16 : 16);
2103
- var height = Number(vp.height) || (stream.kind === "codex-ui" ? 10 : 9);
2104
- return { kind: height > width ? "mobile" : "desktop", width: width, height: height };
1384
+ // ================================================================ FOCUS
1385
+ function focusIndex() {
1386
+ var ss = currentData.streams;
1387
+ var idx = -1;
1388
+ for (var i = 0; i < ss.length; i += 1) { if (ss[i].id === S.focusedId) { idx = i; break; } }
1389
+ return idx < 0 ? 0 : idx;
2105
1390
  }
2106
-
2107
- function streamMediaKey(stream) {
2108
- return [stream.kind, stream.status, stream.updatedAt, preferScreenshots ? "screenshot" : "live"].join(":");
1391
+ function railIcon(kind) {
1392
+ if (kind === "terminal" || kind === "tui") return "terminal";
1393
+ if (kind === "codex-ui") return "spark";
1394
+ return "globe";
2109
1395
  }
2110
-
2111
- function viewLabel(stream) {
2112
- var vp = stream.viewport || {};
2113
- if (stream.kind === "terminal") return "CLI";
2114
- if (stream.kind === "tui") return "TUI";
2115
- if (stream.kind === "codex-ui") return "CODEX";
2116
- if (vp.width && vp.height) return Number(vp.height) > Number(vp.width) ? "MOB" : "DSK";
2117
- return "SIM";
1396
+ function buildSideBody(s) {
1397
+ if (S.tab === "events") {
1398
+ var evs = laneEvents(s);
1399
+ if (!evs.length) return '<div class="tab-empty">No lifecycle events recorded yet.</div>';
1400
+ return evs.map(function (ev, i) {
1401
+ var ic = (ev.level === "error" || ev.level === "warn") ? "alert" : "info";
1402
+ return '<div class="evt" data-level="' + esc(ev.level) + '"><div class="evt-rail"><span class="evt-icon">' + icon(ic, 12) + '</span>' + (i === evs.length - 1 ? '' : '<span class="evt-conn"></span>') + '</div>'
1403
+ + '<div><div class="evt-text">' + esc(ev.message) + '</div><div class="evt-meta"><span class="evt-type mono">' + esc(ev.type) + '</span><span>· ' + esc(shortTime(ev.at) || "just now") + '</span></div></div></div>';
1404
+ }).join("");
1405
+ }
1406
+ if (S.tab === "files") {
1407
+ var arts = laneArtifacts(s);
1408
+ if (!arts.length) return '<div class="tab-empty">No evidence artifacts linked.</div>';
1409
+ return arts.map(function (a) {
1410
+ return '<a class="file-row" href="../' + esc(a.path) + '"><span class="file-ic">' + icon("file", 14) + '</span>'
1411
+ + '<span class="file-meta"><div class="file-name">' + esc(a.label) + '</div><div class="file-path mono">' + esc(a.path) + '</div></span>'
1412
+ + '<span class="file-kind">' + esc(a.kind) + '</span></a>';
1413
+ }).join("");
1414
+ }
1415
+ var lines = termLines(s);
1416
+ if (!lines.length) lines = [{ text: "No log text recorded for this lane.", cls: "dim" }];
1417
+ var rows = lines.map(function (ln, i) {
1418
+ return '<div class="term-line"><span class="term-num">' + pad2(i + 1) + '</span><span class="term-txt ' + ln.cls + '">' + esc(ln.text || " ") + '</span></div>';
1419
+ }).join("");
1420
+ return '<div class="term" style="position:static;height:100%"><div class="term-body" style="overflow-y:auto">' + rows + '</div></div>';
2118
1421
  }
2119
-
2120
- function preferredScreenshotMode(data) {
2121
- return !hasLiveStreams(data) && data.streams.some(function (stream) { return Boolean(stream.ui && stream.ui.screenshotUrl); });
1422
+ function buildFocus() {
1423
+ var ss = currentData.streams;
1424
+ if (!ss.length) return '<div class="grid-empty"><h3>No lanes in this run.</h3></div>';
1425
+ var idx = focusIndex();
1426
+ var s = ss[idx];
1427
+ var run = currentData.run;
1428
+ var live = tone(s.status) === "running";
1429
+ var tabs = [
1430
+ { id: "events", label: "Events", n: laneEvents(s).length },
1431
+ { id: "files", label: "Files", n: laneArtifacts(s).length },
1432
+ { id: "logs", label: "Logs", n: termLines(s).length }
1433
+ ];
1434
+ var rail = '<aside class="focus-rail" aria-label="Lanes"><div class="focus-rail-head"><span class="eyebrow">' + ss.length + ' lanes</span>'
1435
+ + '<button class="icon-btn" style="width:26px;height:26px" data-action="toggle-rail" aria-label="Collapse lane rail">' + icon("chevL", 15) + '</button></div>'
1436
+ + '<div class="focus-rail-list">' + ss.map(function (r) {
1437
+ return '<button class="rail-item" data-selected="' + (r.id === s.id ? "true" : "false") + '" data-action="select:' + esc(r.id) + '">'
1438
+ + '<span class="rail-thumb">' + icon(railIcon(r.kind), 13) + '</span>'
1439
+ + '<span class="rail-meta"><span class="rail-name">' + esc(laneName(r)) + '</span><span class="rail-sub">' + pip(r.status, tone(r.status) === "running") + ' ' + esc(r.kindLabel || r.kind) + '</span></span></button>';
1440
+ }).join("") + '</div></aside>';
1441
+
1442
+ var stage = '<section class="focus-stage"><header class="focus-bar"><div class="crumbs">'
1443
+ + (S.railCollapsed ? '<button class="crumb-rail-toggle" data-action="toggle-rail" aria-label="Show lane rail">' + icon("list", 15) + '</button>' : '')
1444
+ + '<button data-action="exit-focus">Grid</button><span class="sep">/</span>'
1445
+ + '<span class="mono" style="color:var(--text-3)">' + pad2(idx + 1) + '</span>'
1446
+ + '<span class="cur" title="' + esc(laneName(s)) + '">' + esc(laneName(s)) + '</span></div>'
1447
+ + '<div class="tb-spacer"></div>'
1448
+ + '<span class="focus-status" data-tone="' + tone(s.status) + '" style="color:' + statusColor(s.status) + '">' + pip(s.status, live) + ' ' + statusLabel(s.status) + '</span>'
1449
+ + '<div class="focus-nav"><button class="icon-btn" data-action="nav:-1" aria-label="Previous lane">' + icon("chevL") + '</button>'
1450
+ + '<span class="focus-stepper mono">' + (idx + 1) + ' / ' + ss.length + '</span>'
1451
+ + '<button class="icon-btn" data-action="nav:1" aria-label="Next lane">' + icon("chevR") + '</button></div>'
1452
+ + '<button class="icon-btn" data-action="toggle-side" aria-pressed="' + (S.sideCollapsed ? "false" : "true") + '" aria-label="Toggle details panel">' + icon("panelRight") + '</button>'
1453
+ + '<button class="icon-btn" data-action="exit-focus" aria-label="Close focus (Esc)">' + icon("x") + '</button></header>'
1454
+ + '<div class="focus-stage-area"><div class="focus-frame" style="--aspect:' + aspectFor(s) + '">' + streamSurface(s, true) + '</div></div></section>';
1455
+
1456
+ var side = "";
1457
+ if (!S.sideCollapsed) {
1458
+ side = '<aside class="focus-side" data-sheet="' + (S.sheetOpen ? "open" : "closed") + '">'
1459
+ + '<button class="sheet-grip" data-action="toggle-sheet" aria-label="Toggle details"></button>'
1460
+ + '<div class="side-context"><div class="side-persona-row"><span class="side-avatar mono">' + esc(initials((run.persona && run.persona.name) || "")) + '</span>'
1461
+ + '<div style="min-width:0"><div class="side-persona-name">' + esc((run.persona && run.persona.name) || "Persona") + '</div><div class="side-persona-id mono">attempting · ' + esc(laneName(s)) + '</div></div></div>'
1462
+ + '<div class="side-goal"><span class="eyebrow">Goal</span><div class="side-goal-text">' + esc((run.scenario && run.scenario.goal) || laneSummary(s)) + '</div></div>'
1463
+ + '<div class="side-now"><div class="side-now-row">' + pip(s.status, live) + '<span class="eyebrow" style="color:' + (live ? "var(--accent-2)" : "var(--text-3)") + '">' + (live ? "Now" : "Last step") + '</span></div>'
1464
+ + '<div class="side-now-text">' + esc(laneStep(s)) + '</div></div></div>'
1465
+ + '<div class="side-tabs" role="tablist">' + tabs.map(function (t) {
1466
+ return '<button class="side-tab" role="tab" aria-selected="' + (S.tab === t.id ? "true" : "false") + '" data-action="tab:' + t.id + '">' + t.label + '<span class="tab-n">' + t.n + '</span></button>';
1467
+ }).join("") + '</div>'
1468
+ + '<div class="side-body">' + buildSideBody(s) + '</div></aside>';
1469
+ }
1470
+ return '<div class="focus" data-rail="' + (S.railCollapsed ? "collapsed" : "open") + '" data-side="' + (S.sideCollapsed ? "collapsed" : "open") + '">' + rail + stage + side + '</div>';
2122
1471
  }
2123
-
2124
- function hasLiveStreams(data) {
2125
- return data.streams.some(function (stream) {
2126
- return Boolean(stream.embed && stream.embed.url) || stream.transport === "sse" || stream.transport === "app-server" || stream.transport === "pty";
2127
- });
1472
+ function statusColor(st) {
1473
+ var t = tone(st);
1474
+ return t === "running" ? "var(--accent-2)" : t === "complete" ? "var(--green)" : t === "blocked" ? "var(--amber)" : st === "failed" ? "var(--red)" : "var(--text-2)";
2128
1475
  }
2129
1476
 
2130
- function focusStream(id, show) {
2131
- focusedId = show ? knownFocusedId(id) : null;
2132
- writeFocusLocation(focusedId);
2133
- render();
1477
+ // ================================================================ CONSOLE + STATUSBAR
1478
+ function buildConsole() {
1479
+ var lines = consoleLines();
1480
+ var rows = lines.map(function (ln) {
1481
+ return '<div class="console-line"><span class="console-t">' + esc(ln.t) + '</span><span class="console-txt ' + ln.lvl + '">' + esc(ln.text) + '</span></div>';
1482
+ }).join("");
1483
+ var caretT = lines.length ? lines[lines.length - 1].t : "";
1484
+ return '<section class="console" style="height:230px" role="dialog" aria-label="Run console">'
1485
+ + '<header class="console-head"><span class="console-title">' + icon("terminal", 14) + ' Run console <span class="mono console-cmd">mimetic watch</span></span>'
1486
+ + (hasLive() ? '<span class="console-live">' + pip("running", true) + ' tailing</span>' : '')
1487
+ + '<div class="tb-spacer"></div><span class="console-meta mono">' + lines.length + ' lines</span>'
1488
+ + '<button class="icon-btn" style="width:28px;height:28px" data-action="toggle-console" aria-label="Close console">' + icon("x", 15) + '</button></header>'
1489
+ + '<div class="console-body mono" id="console-body">' + rows
1490
+ + '<div class="console-line"><span class="console-t">' + esc(caretT) + '</span><span class="console-txt"><span class="term-caret"></span></span></div></div></section>';
2134
1491
  }
2135
-
2136
- function exitFocus() {
2137
- focusStream(null, false);
1492
+ function buildStatusBar() {
1493
+ var overall = overallStatus(), pct = overallPct();
1494
+ var run = currentData.run;
1495
+ var counts = statusCountsModel().filter(function (c) { return c.count > 0; });
1496
+ var last = consoleLines();
1497
+ var lastLine = last[last.length - 1];
1498
+ var peek = (!S.consoleOpen && lastLine) ? ('<span class="sb-console-peek mono"><span class="sb-peek-t">' + esc(lastLine.t) + '</span><span class="sb-peek-txt ' + lastLine.lvl + '">' + esc(lastLine.text) + '</span></span>') : "";
1499
+ return '<footer class="statusbar">'
1500
+ + '<span class="sb-status" data-tone="' + tone(overall) + '">' + pip(overall, tone(overall) === "running") + '<span class="sb-status-label mono">' + statusLabel(overall) + '</span><span class="sb-pct mono">' + pct + '%</span></span>'
1501
+ + '<span class="sb-prog"><span style="width:' + pct + '%" data-tone="' + tone(overall) + '"></span></span>'
1502
+ + '<span class="sb-counts">' + counts.map(function (c) { return '<span class="sb-count" title="' + c.count + ' ' + c.label + '"><span class="si-dot" style="background:' + c.color + '"></span><span class="mono">' + c.count + '</span></span>'; }).join("") + '</span>'
1503
+ + '<span class="sb-run mono">' + esc((run.mode || "") + " · " + (run.runId || "")) + '</span>'
1504
+ + '<div class="tb-spacer"></div>'
1505
+ + '<button class="sb-console" aria-expanded="' + (S.consoleOpen ? "true" : "false") + '" data-action="toggle-console" title="Run console (backtick)">' + icon("terminal", 14)
1506
+ + '<span class="sb-console-label">Run console</span>' + peek
1507
+ + '<span class="sb-chev' + (S.consoleOpen ? " open" : "") + '">' + icon("caret", 13) + '</span></button>'
1508
+ + '</footer>';
2138
1509
  }
2139
1510
 
2140
- function focusedIdFromLocation() {
2141
- var match = (window.location.hash || "").match(/^#focus=(.+)$/);
2142
- if (!match) return null;
2143
- try { return decodeURIComponent(match[1]); } catch (_error) { return null; }
1511
+ // ================================================================ DRAWER + POPOVERS
1512
+ function buildPopover() {
1513
+ var run = currentData.run;
1514
+ return '<div class="pop" role="dialog" aria-label="Run details">'
1515
+ + popRow("Run id", esc(run.runId || ""))
1516
+ + popRow("Started", esc(shortStamp(run.createdAt)))
1517
+ + popRow("Mode", '<span class="tag">' + esc(run.mode || "") + '</span>')
1518
+ + popRow("Package", esc(run.packageName || "(none)"))
1519
+ + popRow("Evidence", '<span class="tag" data-tone="green">' + icon("lock", 10) + ' local-only</span>')
1520
+ + '</div>';
2144
1521
  }
2145
-
2146
- function writeFocusLocation(id) {
2147
- var url = new URL(window.location.href);
2148
- url.hash = id ? "focus=" + encodeURIComponent(id) : "";
2149
- if (url.href === window.location.href) return;
2150
- window.history.replaceState({ focusedId: id }, "", url);
1522
+ function popRow(k, v) { return '<div class="pop-row"><span class="pop-k">' + k + '</span><span class="pop-v">' + v + '</span></div>'; }
1523
+
1524
+ function buildTweaks() {
1525
+ return '<div class="tweaks-pop" role="dialog" aria-label="Settings">'
1526
+ + '<div class="tw-sec">Appearance</div>'
1527
+ + twSeg("Theme", "theme", S.theme, [["dark", "dark"], ["light", "light"]])
1528
+ + '<div class="tw-row"><span class="tw-label">Accent</span><span class="tw-swatches">'
1529
+ + ["#4d7cfe", "#7c5cff", "#34d399", "#f0a92b", "#fb5d52"].map(function (col) {
1530
+ return '<button class="tw-swatch" aria-pressed="' + (S.accent === col ? "true" : "false") + '" data-action="tweak:accent:' + col + '" style="background:' + col + '" title="' + col + '"></button>';
1531
+ }).join("") + '</span></div>'
1532
+ + '<div class="tw-sec">Layout</div>'
1533
+ + twSeg("Density", "density", S.density, [["comfortable", "comfy"], ["compact", "compact"], ["dense", "dense"]])
1534
+ + twSeg("Status", "statusViz", S.statusViz, [["badges", "badges"], ["bar", "bar"], ["ring", "ring"]])
1535
+ + '<div class="tw-sec">Motion</div>'
1536
+ + twSeg("Motion", "motion", S.motion, [["full", "full"], ["reduced", "reduced"]])
1537
+ + '</div>';
2151
1538
  }
2152
-
2153
- function syncFocusFromLocation() {
2154
- var next = knownFocusedId(focusedIdFromLocation());
2155
- if (next === focusedId) return;
2156
- focusedId = next;
2157
- render();
1539
+ function twSeg(label, key, value, opts) {
1540
+ return '<div class="tw-row"><span class="tw-label">' + label + '</span><span class="tw-seg">'
1541
+ + opts.map(function (o) { return '<button aria-pressed="' + (value === o[0] ? "true" : "false") + '" data-action="tweak:' + key + ':' + o[0] + '">' + o[1] + '</button>'; }).join("")
1542
+ + '</span></div>';
2158
1543
  }
2159
1544
 
2160
- function knownFocusedId(id) {
2161
- if (id && currentData.streams.some(function (stream) { return stream.id === id; })) return id;
2162
- return currentData.streams.length ? currentData.streams[0].id : null;
1545
+ function historyRuns() {
1546
+ if (historyIndex && historyIndex.runs && historyIndex.runs.length) return historyIndex.runs;
1547
+ var run = currentData.run;
1548
+ return [{ runId: run.runId, createdAt: run.createdAt, mode: run.mode, status: overallStatus(), streamCount: currentData.streams.length, href: null }];
2163
1549
  }
2164
-
2165
- async function refresh() {
2166
- if (location.protocol === "file:") return;
2167
- try {
2168
- var response = await fetch(DATA_FILE, { cache: "no-store" });
2169
- if (response.ok) {
2170
- currentData = await response.json();
2171
- render();
2172
- }
2173
- } catch (_error) {}
1550
+ function buildDrawer() {
1551
+ var runs = historyRuns();
1552
+ var activeId = currentData.run.runId;
1553
+ var rows = runs.map(function (r) {
1554
+ var t = tone(r.status);
1555
+ var col = t === "running" ? "var(--accent-2)" : t === "complete" ? "var(--green)" : t === "blocked" ? "var(--amber)" : r.status === "failed" ? "var(--red)" : "var(--text-2)";
1556
+ var liveTagTxt = (r.runId === activeId && t === "running") ? '<span style="color:var(--accent-2)"> · live</span>' : "";
1557
+ return '<button class="run-row" data-active="' + (r.runId === activeId ? "true" : "false") + '" data-action="run:' + esc(r.runId) + '">'
1558
+ + pip(r.status, t === "running")
1559
+ + '<span class="run-row-id mono">' + esc(r.runId) + liveTagTxt + '</span>'
1560
+ + '<span class="run-row-stat" style="color:' + col + '">' + statusLabel(r.status) + '</span>'
1561
+ + '<span class="run-row-meta mono">' + esc((r.mode || "run") + " · " + (r.streamCount || 0) + " lanes · " + shortStamp(r.createdAt)) + '</span></button>';
1562
+ }).join("");
1563
+ return '<div class="scrim" data-action="close-history"></div><aside class="drawer" role="dialog" aria-label="Run history">'
1564
+ + '<header class="drawer-head"><div><span class="eyebrow">Run history</span><h2>Recent runs</h2></div>'
1565
+ + '<button class="icon-btn" data-action="close-history" aria-label="Close">' + icon("x") + '</button></header>'
1566
+ + '<div class="drawer-list">' + rows + '</div></aside>';
2174
1567
  }
2175
1568
 
2176
- async function refreshHistoryIndex() {
2177
- if (location.protocol === "file:") return;
2178
- try {
2179
- var response = await fetch("/_mimetic/history.json", { cache: "no-store" });
2180
- if (!response.ok) throw new Error("history " + response.status);
2181
- historyIndex = await response.json();
2182
- renderSubBar();
2183
- renderHistoryPanel();
2184
- } catch (_error) {
2185
- historyIndex = null;
2186
- historyOpen = false;
2187
- renderSubBar();
2188
- renderHistoryPanel();
2189
- }
1569
+ // ================================================================ RENDER
1570
+ function captureScrolls() {
1571
+ var map = {};
1572
+ [".grid-scroll", ".console-body", ".focus-rail-list", ".side-body", ".focus-stage-area", ".drawer-list"].forEach(function (sel) {
1573
+ var el = app.querySelector(sel);
1574
+ if (el) map[sel] = el.scrollTop;
1575
+ });
1576
+ return map;
2190
1577
  }
2191
-
2192
- function readPref(key, fallback) {
2193
- try {
2194
- var raw = window.localStorage.getItem("mimetic-observer:" + key);
2195
- return raw == null ? fallback : JSON.parse(raw);
2196
- } catch (_error) {
2197
- return fallback;
2198
- }
1578
+ function restoreScrolls(map) {
1579
+ Object.keys(map).forEach(function (sel) {
1580
+ var el = app.querySelector(sel);
1581
+ if (el) el.scrollTop = map[sel];
1582
+ });
2199
1583
  }
2200
-
2201
- function writePref(key, value) {
2202
- try { window.localStorage.setItem("mimetic-observer:" + key, JSON.stringify(value)); } catch (_error) {}
1584
+ function placeMenus() {
1585
+ var menu = document.getElementById("dd-menu");
1586
+ if (!menu) return;
1587
+ var kind = menu.getAttribute("data-dd");
1588
+ var trg = document.getElementById("dd-trigger-" + kind);
1589
+ if (!trg) { menu.style.visibility = "visible"; return; }
1590
+ var w = 210;
1591
+ var r = trg.getBoundingClientRect();
1592
+ var left = r.left;
1593
+ if (left + w > window.innerWidth - 8) left = Math.max(8, window.innerWidth - 8 - w);
1594
+ menu.style.width = w + "px";
1595
+ menu.style.top = (r.bottom + 6) + "px";
1596
+ menu.style.left = left + "px";
1597
+ menu.style.visibility = "visible";
2203
1598
  }
2204
1599
 
2205
- function shortTime(value) {
2206
- if (!value) return "";
2207
- var date = new Date(value);
2208
- if (Number.isNaN(date.getTime())) return value;
2209
- return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
1600
+ function render() {
1601
+ var docEl = document.documentElement;
1602
+ docEl.setAttribute("data-theme", S.theme);
1603
+ docEl.setAttribute("data-motion", S.motion);
1604
+ docEl.style.setProperty("--accent-color", S.accent);
1605
+ docEl.style.setProperty("--accent", S.accent);
1606
+
1607
+ var ss = currentData.streams;
1608
+ if (!S.focusedId || !ss.some(function (s) { return s.id === S.focusedId; })) S.focusedId = ss.length ? ss[0].id : null;
1609
+
1610
+ var active = document.activeElement;
1611
+ var searchFocused = !!(active && active.getAttribute && active.getAttribute("data-role") === "search");
1612
+ var caret = searchFocused ? active.selectionStart : null;
1613
+ var scrolls = captureScrolls();
1614
+
1615
+ var parts = [];
1616
+ parts.push('<div class="runline" data-status="' + overallStatus() + '"><span style="width:' + overallPct() + '%"></span></div>');
1617
+ parts.push(buildHeader());
1618
+ if (S.detailsOpen) parts.push(buildPopover());
1619
+ if (S.tweaksOpen) parts.push(buildTweaks());
1620
+ parts.push(buildToolbar());
1621
+ parts.push('<main class="stage">' + (S.view === "grid" ? buildGrid() : buildFocus()) + '</main>');
1622
+ if (S.consoleOpen) parts.push(buildConsole());
1623
+ parts.push(buildStatusBar());
1624
+ if (S.historyOpen) parts.push(buildDrawer());
1625
+ parts.push(buildDdMenu());
1626
+ app.innerHTML = parts.join("");
1627
+
1628
+ restoreScrolls(scrolls);
1629
+ if (searchFocused) {
1630
+ var inp = app.querySelector('[data-role="search"]');
1631
+ if (inp) { inp.focus(); try { inp.setSelectionRange(caret, caret); } catch (e) {} }
1632
+ }
1633
+ placeMenus();
1634
+ var cb = document.getElementById("console-body");
1635
+ if (cb && scrolls[".console-body"] == null) cb.scrollTop = cb.scrollHeight;
2210
1636
  }
2211
1637
 
2212
- function compact(values) {
2213
- return values.filter(function (value) { return value !== null && value !== undefined && value !== ""; });
1638
+ // ================================================================ ACTIONS
1639
+ function toggleArr(arr, id) {
1640
+ var i = arr.indexOf(id);
1641
+ if (i >= 0) { arr.splice(i, 1); } else { arr.push(id); }
2214
1642
  }
2215
-
2216
- function compactLabel(value) {
2217
- return String(value || "sim").replace(/^builtin-/, "").replace(/-/g, " ");
1643
+ function writeHash(id) {
1644
+ var url = new URL(window.location.href);
1645
+ url.hash = id ? "focus=" + encodeURIComponent(id) : "";
1646
+ if (url.href !== window.location.href) window.history.replaceState({ focusedId: id }, "", url.href);
2218
1647
  }
2219
-
2220
- function dot() {
2221
- var node = document.createElement("span");
2222
- node.className = "chrome-dot";
2223
- return node;
1648
+ function openFocus(id) { S.focusedId = id; S.view = "focus"; writeHash(id); render(); }
1649
+ function exitFocus() { S.view = "grid"; writeHash(null); render(); }
1650
+ function navFocus(delta) {
1651
+ var ss = currentData.streams;
1652
+ if (!ss.length) return;
1653
+ var idx = focusIndex();
1654
+ var next = ((idx + delta) % ss.length + ss.length) % ss.length;
1655
+ S.focusedId = ss[next].id;
1656
+ writeHash(S.focusedId);
1657
+ render();
2224
1658
  }
2225
1659
 
2226
- function thread() {
2227
- var node = document.createElement("div");
2228
- node.className = "codex-thread";
2229
- return node;
1660
+ function handleAction(action) {
1661
+ var parts = action.split(":");
1662
+ var cmd = parts[0];
1663
+ var arg = parts[1];
1664
+ var arg2 = parts[2];
1665
+ switch (cmd) {
1666
+ case "open": openFocus(arg); break;
1667
+ case "select": S.focusedId = arg; writeHash(arg); render(); break;
1668
+ case "exit-focus": exitFocus(); break;
1669
+ case "view":
1670
+ if (arg === "focus" && !S.focusedId && currentData.streams[0]) S.focusedId = currentData.streams[0].id;
1671
+ S.view = arg; writeHash(arg === "focus" ? S.focusedId : null); render(); break;
1672
+ case "nav": navFocus(Number(arg)); break;
1673
+ case "toggle-rail": S.railCollapsed = !S.railCollapsed; writePref("railCollapsed", S.railCollapsed); render(); break;
1674
+ case "toggle-side": S.sideCollapsed = !S.sideCollapsed; writePref("sideCollapsed", S.sideCollapsed); render(); break;
1675
+ case "toggle-sheet": S.sheetOpen = !S.sheetOpen; render(); break;
1676
+ case "tab": S.tab = arg; render(); break;
1677
+ case "density": S.density = arg; writePref("density", arg); render(); break;
1678
+ case "media": if (arg === "live" && !hasLive()) break; S.media = arg; render(); break;
1679
+ case "status":
1680
+ if (arg === "all") S.statusSel = [];
1681
+ else toggleArr(S.statusSel, arg);
1682
+ render(); break;
1683
+ case "clear-filters": S.statusSel = []; S.kindSel = []; S.query = ""; render(); break;
1684
+ case "dd": openDd = (openDd === arg ? null : arg); render(); break;
1685
+ case "dd-all":
1686
+ if (arg === "status") S.statusSel = []; else S.kindSel = [];
1687
+ render(); break;
1688
+ case "dd-row":
1689
+ if (arg === "status") toggleArr(S.statusSel, arg2); else toggleArr(S.kindSel, arg2);
1690
+ render(); break;
1691
+ case "toggle-console": S.consoleOpen = !S.consoleOpen; render(); break;
1692
+ case "toggle-details": S.detailsOpen = !S.detailsOpen; S.tweaksOpen = false; render(); break;
1693
+ case "toggle-tweaks": S.tweaksOpen = !S.tweaksOpen; S.detailsOpen = false; render(); break;
1694
+ case "toggle-theme": S.theme = (S.theme === "light" ? "dark" : "light"); writePref("theme", S.theme); render(); break;
1695
+ case "tweak":
1696
+ S[arg] = arg2; writePref(arg, arg2); render(); break;
1697
+ case "open-history": S.historyOpen = true; render(); refreshHistoryIndex(); break;
1698
+ case "close-history": S.historyOpen = false; render(); break;
1699
+ case "run": gotoRun(arg); break;
1700
+ default: break;
1701
+ }
2230
1702
  }
2231
-
2232
- function bubble(extra) {
2233
- var node = document.createElement("div");
2234
- node.className = "codex-bubble " + extra;
2235
- return node;
1703
+ function gotoRun(runId) {
1704
+ if (runId === currentData.run.runId) { S.historyOpen = false; render(); return; }
1705
+ if (location.protocol === "file:") { S.historyOpen = false; render(); return; }
1706
+ window.location.href = "/_mimetic/runs/" + encodeURIComponent(runId) + "/observer/index.html";
2236
1707
  }
2237
1708
 
2238
- Array.prototype.forEach.call(document.querySelectorAll(".sub-density-btn"), function (button) {
2239
- button.addEventListener("click", function () {
2240
- density = Number(button.dataset.density);
2241
- writePref("density", density);
2242
- renderSubBar();
2243
- getElement("streams").dataset.density = String(density);
2244
- layoutPackedGrid();
2245
- });
2246
- });
2247
-
2248
- getElement("rp-toggle").addEventListener("click", function () {
2249
- stepperOpen = !(getElement("rp-toggle").getAttribute("aria-expanded") === "true");
2250
- writePref("stepperOpen", stepperOpen);
2251
- renderProgress();
2252
- });
2253
-
2254
- getElement("media-toggle").addEventListener("click", function () {
2255
- if (!hasLiveStreams(currentData)) return;
2256
- mediaPreferenceTouched = true;
2257
- preferScreenshots = !preferScreenshots;
2258
- render();
2259
- });
2260
-
2261
- getElement("focus-mode").addEventListener("click", function () {
2262
- focusStream(focusedId || (currentData.streams[0] && currentData.streams[0].id), true);
1709
+ // ================================================================ EVENTS (bound once)
1710
+ app.addEventListener("click", function (e) {
1711
+ var t = e.target.closest ? e.target.closest("[data-action]") : null;
1712
+ if (!t || !app.contains(t)) return;
1713
+ var action = t.getAttribute("data-action");
1714
+ if (t.tagName !== "A") e.preventDefault();
1715
+ handleAction(action);
2263
1716
  });
2264
-
2265
- getElement("grid-mode").addEventListener("click", exitFocus);
2266
-
2267
- getElement("history-toggle").addEventListener("click", function () {
2268
- historyOpen = !historyOpen;
2269
- writePref("historyOpen", historyOpen);
2270
- renderSubBar();
2271
- renderHistoryPanel();
1717
+ app.addEventListener("input", function (e) {
1718
+ var t = e.target;
1719
+ if (t && t.getAttribute && t.getAttribute("data-role") === "search") { S.query = t.value; render(); }
2272
1720
  });
2273
-
2274
- getElement("history-close").addEventListener("click", function () {
2275
- historyOpen = false;
2276
- writePref("historyOpen", historyOpen);
2277
- renderSubBar();
2278
- renderHistoryPanel();
1721
+ document.addEventListener("mousedown", function (e) {
1722
+ if (openDd) {
1723
+ var inMenu = e.target.closest && (e.target.closest(".dd-menu") || e.target.closest(".dd-trigger"));
1724
+ if (!inMenu) { openDd = null; render(); return; }
1725
+ }
1726
+ if (S.detailsOpen) {
1727
+ var inPop = e.target.closest && (e.target.closest(".pop") || e.target.closest(".run-chip"));
1728
+ if (!inPop) { S.detailsOpen = false; render(); }
1729
+ }
1730
+ if (S.tweaksOpen) {
1731
+ var inTw = e.target.closest && (e.target.closest(".tweaks-pop") || e.target.closest('[data-action="toggle-tweaks"]'));
1732
+ if (!inTw) { S.tweaksOpen = false; render(); }
1733
+ }
2279
1734
  });
2280
-
2281
- document.addEventListener("keydown", function (event) {
2282
- if (event.key === "Escape" && historyOpen) {
2283
- historyOpen = false;
2284
- writePref("historyOpen", historyOpen);
2285
- renderSubBar();
2286
- renderHistoryPanel();
2287
- return;
1735
+ document.addEventListener("keydown", function (e) {
1736
+ var typing = document.activeElement && document.activeElement.tagName === "INPUT";
1737
+ if (e.key === "Escape") {
1738
+ if (openDd) { openDd = null; return render(); }
1739
+ if (S.detailsOpen) { S.detailsOpen = false; return render(); }
1740
+ if (S.tweaksOpen) { S.tweaksOpen = false; return render(); }
1741
+ if (S.consoleOpen) { S.consoleOpen = false; return render(); }
1742
+ if (S.historyOpen) { S.historyOpen = false; return render(); }
1743
+ if (S.view === "focus") { return exitFocus(); }
2288
1744
  }
2289
- if (event.key === "Escape" && focusedId) exitFocus();
2290
- if (focusedId && (event.key === "ArrowRight" || event.key === "ArrowLeft")) {
2291
- var list = currentData.streams;
2292
- var index = list.findIndex(function (stream) { return stream.id === focusedId; });
2293
- if (index >= 0) {
2294
- var next = event.key === "ArrowRight" ? (index + 1) % list.length : (index - 1 + list.length) % list.length;
2295
- focusStream(list[next].id, true);
2296
- }
1745
+ if ((e.key === String.fromCharCode(96) || e.key === "~") && !typing) { e.preventDefault(); S.consoleOpen = !S.consoleOpen; render(); }
1746
+ if (e.key === "/" && S.view === "grid" && !typing) {
1747
+ e.preventDefault();
1748
+ var el = app.querySelector(".tb-search input");
1749
+ if (el) el.focus();
1750
+ }
1751
+ if (S.view === "focus" && (e.key === "ArrowRight" || e.key === "ArrowLeft") && !typing) {
1752
+ navFocus(e.key === "ArrowRight" ? 1 : -1);
2297
1753
  }
2298
1754
  });
2299
-
2300
- window.addEventListener("popstate", syncFocusFromLocation);
2301
- window.addEventListener("hashchange", syncFocusFromLocation);
2302
- window.addEventListener("resize", function () {
2303
- fitFocusMedia();
2304
- layoutPackedGrid();
1755
+ window.addEventListener("hashchange", function () {
1756
+ var next = focusFromHash();
1757
+ if (next && currentData.streams.some(function (s) { return s.id === next; })) {
1758
+ S.focusedId = next; if (S.view !== "focus") S.view = "focus"; render();
1759
+ }
2305
1760
  });
1761
+ window.addEventListener("resize", function () { if (openDd) placeMenus(); });
2306
1762
 
2307
- if (typeof ResizeObserver !== "undefined") {
2308
- new ResizeObserver(function () { layoutPackedGrid(); }).observe(getElement("grid-shell"));
1763
+ // ================================================================ POLLING
1764
+ function dataKey(d) {
1765
+ return (d.generatedAt || "") + "|" + (d.streams || []).map(function (s) { return s.id + s.status + laneProgress(s); }).join(",");
1766
+ }
1767
+ function refresh() {
1768
+ if (location.protocol === "file:") return Promise.resolve();
1769
+ return fetch(DATA_FILE, { cache: "no-store" }).then(function (r) {
1770
+ if (!r.ok) return;
1771
+ return r.json().then(function (d) {
1772
+ if (!d || !d.streams) return;
1773
+ var prev = dataKey(currentData);
1774
+ currentData = d;
1775
+ if (!currentData.run) currentData.run = {};
1776
+ if (!currentData.streams) currentData.streams = [];
1777
+ if (!currentData.events) currentData.events = [];
1778
+ if (dataKey(currentData) !== prev) render();
1779
+ });
1780
+ }).catch(function () {});
1781
+ }
1782
+ function refreshHistoryIndex() {
1783
+ if (location.protocol === "file:") return Promise.resolve();
1784
+ return fetch("/_mimetic/history.json", { cache: "no-store" }).then(function (r) {
1785
+ if (!r.ok) throw new Error("history " + r.status);
1786
+ return r.json();
1787
+ }).then(function (d) {
1788
+ historyIndex = d;
1789
+ if (S.historyOpen) render();
1790
+ }).catch(function () { historyIndex = null; if (S.historyOpen) render(); });
2309
1791
  }
2310
1792
 
1793
+ // ================================================================ BOOT
2311
1794
  render();
2312
1795
  refresh().then(function () {
2313
1796
  if (location.protocol !== "file:") {
2314
1797
  refreshTimer = setInterval(refresh, 2500);
2315
- void refreshHistoryIndex();
1798
+ refreshHistoryIndex();
2316
1799
  historyTimer = setInterval(refreshHistoryIndex, 30000);
2317
1800
  }
2318
1801
  });