ductwork 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/app/assets/javascripts/ductwork/application.js +50 -41
  4. data/app/assets/stylesheets/ductwork/application.css +859 -146
  5. data/app/controllers/ductwork/application_controller.rb +39 -8
  6. data/app/controllers/ductwork/dashboards_controller.rb +44 -11
  7. data/app/controllers/ductwork/pipelines_controller.rb +47 -7
  8. data/app/controllers/ductwork/step_errors_controller.rb +9 -5
  9. data/app/helpers/ductwork/application_helper.rb +107 -18
  10. data/app/views/ductwork/dashboards/show.html.erb +75 -126
  11. data/app/views/ductwork/pipelines/_graph.html.erb +37 -0
  12. data/app/views/ductwork/pipelines/_step.html.erb +76 -0
  13. data/app/views/ductwork/pipelines/index.html.erb +11 -101
  14. data/app/views/ductwork/pipelines/show.html.erb +62 -285
  15. data/app/views/ductwork/shared/_error.html.erb +7 -0
  16. data/app/views/ductwork/shared/_filter_menu.html.erb +13 -0
  17. data/app/views/ductwork/shared/_pager.html.erb +16 -0
  18. data/app/views/ductwork/shared/_runs_table.html.erb +30 -0
  19. data/app/views/ductwork/shared/_status_tabs.html.erb +11 -0
  20. data/app/views/ductwork/step_errors/index.html.erb +52 -96
  21. data/app/views/layouts/ductwork/application.html.erb +19 -23
  22. data/lib/ductwork/engine.rb +0 -2
  23. data/lib/ductwork/models/pipeline.rb +52 -31
  24. data/lib/ductwork/pipeline_graph/layout.rb +161 -0
  25. data/lib/ductwork/pipeline_graph.rb +231 -0
  26. data/lib/ductwork/version.rb +1 -1
  27. metadata +11 -5
  28. data/app/assets/images/ductwork/octagon-x.svg +0 -1
  29. data/app/assets/stylesheets/ductwork/vendor/flexboxgrid.min.css +0 -1
  30. data/app/assets/stylesheets/ductwork/vendor/pico.min.css +0 -4
@@ -1,240 +1,953 @@
1
- [data-theme=light],
2
- [data-theme=dark],
3
- :root:not([data-theme=dark]),
4
- :host:not([data-theme=dark]) {
5
- --pico-border-radius: 0.5rem;
6
-
7
- --pico-background-color: #27262b;
8
- --pico-primary-background: #2c84fa;
9
- --pico-progress-background-color: #201f23;
10
- --pico-card-background-color: #27262b;
11
- --pico-color: #f5f6fa;
12
- --pico-primary: #2c84fa;
13
- --pico-primary-border: #44434d;
14
- --pico-border-color: #44434d;
15
- --pico-table-border-color: #44434d;
16
- --pico-muted-color: #e6e1e8;
17
- --pico-secondary-hover: #201f23;
18
- --pico-card-sectioning-background-color: #232327;
19
- --pico-form-element-background-color: #27262b;
20
- --pico-form-element-active-background-color: #27262b;
21
- --pico-dropdown-background-color: #27262b;
22
- --pico-dropdown-hover-background-color: #27262b;
23
- --pico-form-element-border-color: #44434d;
24
- --pico-dropdown-border-color: #44434d;
25
- --pico-accordion-open-summary-color: #f5f6fa;
26
- --pico-accordion-active-summary-color: #f5f6fa;
1
+ /* --- tokens --- */
2
+ :root {
3
+ color-scheme: dark;
4
+
5
+ --page: #201f23;
6
+ --surface: #27262b;
7
+ --surface-raised: #302f35;
8
+ --border: #44434d;
9
+ --border-soft: #36353c;
10
+
11
+ --text: #f5f6fa;
12
+ --text-muted: #c4c2ca;
13
+ --text-faint: #9c9ba3;
14
+
15
+ --blue: #2c84fa;
16
+ --blue-text: #7cb5ff;
17
+ --blue-wash: rgb(44 132 250 / 0.16);
18
+ --green: #4ade80;
19
+ --green-wash: rgb(74 222 128 / 0.13);
20
+ --amber: #fbbf24;
21
+ --amber-wash: rgb(251 191 36 / 0.13);
22
+ --red: #f43f5e;
23
+ --red-text: #fda4af;
24
+ --red-wash: rgb(244 63 94 / 0.16);
25
+ --gray: #8b8a92;
26
+ --gray-wash: rgb(156 155 163 / 0.14);
27
+
28
+ --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
29
+ --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
30
+
31
+ --radius: 8px;
32
+ --topbar-height: 52px;
33
+ }
34
+
35
+ /* --- base --- */
36
+ *,
37
+ *::before,
38
+ *::after {
39
+ box-sizing: border-box;
40
+ }
41
+
42
+ html {
43
+ -webkit-text-size-adjust: 100%;
44
+ }
45
+
46
+ body {
47
+ margin: 0;
48
+ background: var(--page);
49
+ color: var(--text);
50
+ font-family: var(--sans);
51
+ font-size: 14px;
52
+ line-height: 20px;
53
+ }
54
+
55
+ h1,
56
+ h2,
57
+ p,
58
+ pre,
59
+ dl,
60
+ dd {
61
+ margin: 0;
62
+ }
63
+
64
+ a {
65
+ color: var(--blue-text);
66
+ text-decoration: none;
67
+ }
27
68
 
28
- --pico-nav-element-spacing-vertical: 5px;
29
- --pico-nav-element-spacing-horizontal: 5px;
69
+ a:hover {
70
+ text-decoration: underline;
30
71
  }
31
72
 
32
- article {
33
- border: 2px solid var(--pico-border-color);
73
+ :focus-visible {
74
+ outline: 2px solid var(--blue);
75
+ outline-offset: 2px;
34
76
  }
35
77
 
36
- form[role="search"] {
37
- width: 50%
78
+ code,
79
+ pre {
80
+ font-family: var(--mono);
81
+ font-size: 12.5px;
38
82
  }
39
83
 
40
- hr {
41
- border-top: 1px solid var(--pico-border-color);
84
+ .faint {
85
+ color: var(--text-faint);
42
86
  }
43
87
 
44
- label {
45
- color: var(--pico-code-color);
88
+ .shell {
89
+ width: 100%;
90
+ max-width: 1240px;
91
+ margin: 0 auto;
92
+ padding: 0 24px;
46
93
  }
47
94
 
48
- table, .table-border {
49
- border: 2px solid var(--pico-table-border-color);
50
- border-radius: var(--pico-border-radius);
51
- border-collapse: separate;
52
- border-spacing: 0;
95
+ /* --- top bar --- */
96
+ .topbar {
97
+ position: sticky;
98
+ top: 0;
99
+ z-index: 10;
100
+ background: var(--page);
101
+ border-bottom: 1px solid var(--border-soft);
53
102
  }
54
103
 
55
- tr:hover td {
56
- background-color: var(--pico-table-border-color) !important;
57
- cursor: pointer;
104
+ .topbar-inner {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 32px;
108
+ height: var(--topbar-height);
58
109
  }
59
110
 
60
- tr.not-clickable:hover td {
61
- cursor: auto;
111
+ .brand {
112
+ display: flex;
113
+ align-items: center;
114
+ gap: 8px;
115
+ color: var(--text);
116
+ font-size: 15px;
117
+ font-weight: 700;
62
118
  }
63
119
 
64
- .sticky-header {
65
- background-color: var(--pico-background-color);
66
- color: var(--pico-background-color);
67
- top: 0;
68
- position: sticky;
69
- z-index: 100;
70
- overflow: hidden;
71
- border-bottom: 2px solid var(--pico-border-color);
72
- padding: 0rem;
120
+ .brand:hover {
121
+ text-decoration: none;
73
122
  }
74
123
 
75
- .logo {
76
- width: 2.5rem;
77
- height: 2.5rem;
124
+ .brand img {
125
+ width: 28px;
126
+ height: 28px;
127
+ padding: 2px;
128
+ border-radius: 6px;
78
129
  background: linear-gradient(45deg, #2c84fa, #1e5bb8);
79
- border-radius: var(--pico-border-radius);
80
- margin: 0.25rem;
81
130
  }
82
131
 
83
- .title {
84
- margin-bottom: 0px;
85
- color: var(--pico-primary);
132
+ .nav {
133
+ display: flex;
134
+ align-self: stretch;
135
+ gap: 24px;
136
+ flex: 1;
137
+ min-width: 0;
138
+ }
139
+
140
+ .nav a {
141
+ display: flex;
142
+ align-items: center;
143
+ border-bottom: 2px solid transparent;
144
+ border-top: 2px solid transparent;
145
+ color: var(--text-muted);
146
+ font-weight: 500;
147
+ }
148
+
149
+ .nav a:hover {
150
+ color: var(--text);
151
+ text-decoration: none;
152
+ }
153
+
154
+ .nav a[aria-current="page"] {
155
+ border-bottom-color: var(--blue);
156
+ color: var(--text);
157
+ }
158
+
159
+ .version {
160
+ color: var(--text-faint);
161
+ font-family: var(--mono);
162
+ font-size: 12px;
163
+ }
164
+
165
+ /* --- page structure --- */
166
+ main.shell {
167
+ padding-top: 32px;
168
+ padding-bottom: 64px;
169
+ }
170
+
171
+ .page-title {
172
+ font-size: 22px;
173
+ font-weight: 700;
174
+ line-height: 30px;
175
+ overflow-wrap: anywhere;
176
+ }
177
+
178
+ .page-head {
179
+ margin-bottom: 20px;
86
180
  }
87
181
 
88
- .metrics {
89
- margin-bottom: 3rem;
182
+ .section {
183
+ margin-top: 36px;
90
184
  }
91
185
 
92
- .metric-heading {
186
+ .section:first-child {
187
+ margin-top: 0;
188
+ }
189
+
190
+ .section-head {
191
+ display: flex;
192
+ align-items: baseline;
193
+ justify-content: space-between;
194
+ gap: 16px;
93
195
  margin-bottom: 12px;
94
196
  }
95
197
 
96
- .metric-value {
97
- margin-bottom: 0px;
198
+ .section-title {
199
+ font-size: 15px;
200
+ font-weight: 600;
201
+ line-height: 20px;
98
202
  }
99
203
 
100
- .table-pipeline-klass {
101
- padding-top: 0.5rem;
102
- padding-bottom: 0.5rem;
204
+ .panel {
205
+ border: 1px solid var(--border-soft);
206
+ border-radius: var(--radius);
207
+ background: var(--surface);
103
208
  }
104
209
 
105
- .secondary-text {
106
- font-size: 80%;
107
- color: gray;
210
+ .empty {
211
+ padding: 32px 16px;
212
+ color: var(--text-faint);
213
+ text-align: center;
214
+ }
215
+
216
+ /* --- statuses --- */
217
+ .status {
218
+ display: inline-block;
219
+ padding: 1px 8px;
220
+ border-radius: 999px;
221
+ background: var(--gray-wash);
222
+ color: var(--text-muted);
223
+ font-size: 12px;
224
+ font-weight: 600;
225
+ line-height: 18px;
226
+ white-space: nowrap;
227
+ }
228
+
229
+ .status[data-status="completed"] {
230
+ background: var(--blue-wash);
231
+ color: var(--blue-text);
232
+ }
233
+
234
+ .status[data-status="in_progress"] {
235
+ background: var(--green-wash);
236
+ color: var(--green);
237
+ }
238
+
239
+ .status[data-status="advancing"] {
240
+ background: var(--amber-wash);
241
+ color: var(--amber);
242
+ }
243
+
244
+ .status[data-status="halted"],
245
+ .status[data-status="failed"] {
246
+ background: var(--red-wash);
247
+ color: var(--red-text);
248
+ }
249
+
250
+ .count-alert {
251
+ color: var(--red-text);
252
+ font-weight: 600;
253
+ }
254
+
255
+ .count-warn {
256
+ color: var(--amber);
257
+ font-weight: 600;
258
+ }
259
+
260
+ /* --- overview stats --- */
261
+ .stats {
262
+ display: grid;
263
+ grid-template-columns: repeat(4, minmax(0, 1fr));
264
+ overflow: hidden;
265
+ }
266
+
267
+ .stat {
268
+ display: block;
269
+ padding: 16px 20px;
270
+ border-left: 1px solid var(--border-soft);
271
+ color: var(--text-muted);
272
+ }
273
+
274
+ .stat:first-child {
275
+ border-left: 0;
276
+ }
277
+
278
+ .stat:hover {
279
+ background: var(--surface-raised);
108
280
  text-decoration: none;
109
281
  }
110
282
 
111
- .selected {
112
- border-left: 5px solid var(--pico-primary);
113
- background-color: var(--pico-table-border-color);
283
+ .stat-value {
284
+ display: block;
285
+ color: var(--text);
286
+ font-size: 28px;
287
+ font-weight: 700;
288
+ font-variant-numeric: tabular-nums;
289
+ line-height: 36px;
114
290
  }
115
291
 
116
- .hover-underline {
292
+ .stat[data-alert="true"] .stat-value {
293
+ color: var(--red-text);
117
294
  }
118
295
 
119
- .hover-underline:hover {
120
- text-decoration: underline;
296
+ /* --- halted runs --- */
297
+ .halt {
298
+ display: grid;
299
+ grid-template-columns: minmax(0, 1fr) auto;
300
+ gap: 2px 16px;
301
+ padding: 12px 16px;
302
+ border-top: 1px solid var(--border-soft);
303
+ }
304
+
305
+ .halt:first-child {
306
+ border-top: 0;
121
307
  }
122
308
 
123
- .stealthy-link {
309
+ .halt-reason {
310
+ color: var(--red-text);
311
+ }
312
+
313
+ .halt-time {
314
+ grid-column: 2;
315
+ grid-row: 1;
316
+ color: var(--text-faint);
317
+ font-size: 13px;
318
+ }
319
+
320
+ .notice {
321
+ margin-bottom: 20px;
322
+ padding: 10px 16px;
323
+ border: 1px solid rgb(244 63 94 / 0.45);
324
+ border-radius: var(--radius);
325
+ background: var(--red-wash);
326
+ color: var(--red-text);
327
+ }
328
+
329
+ /* --- tables --- */
330
+ .table-scroller {
331
+ overflow-x: auto;
332
+ scrollbar-color: var(--border) transparent;
333
+ }
334
+
335
+ .table {
336
+ width: 100%;
337
+ border-collapse: collapse;
338
+ font-variant-numeric: tabular-nums;
339
+ }
340
+
341
+ .table th {
342
+ padding: 10px 16px;
343
+ border-bottom: 1px solid var(--border-soft);
344
+ color: var(--text-faint);
345
+ font-size: 12px;
346
+ font-weight: 600;
347
+ text-align: left;
348
+ white-space: nowrap;
349
+ }
350
+
351
+ .table td {
352
+ padding: 10px 16px;
353
+ border-top: 1px solid var(--border-soft);
354
+ color: var(--text-muted);
355
+ white-space: nowrap;
356
+ }
357
+
358
+ .table tbody tr:first-child td {
359
+ border-top: 0;
360
+ }
361
+
362
+ .table .numeric {
363
+ text-align: right;
364
+ }
365
+
366
+ .table-primary {
367
+ color: var(--text);
368
+ font-weight: 600;
369
+ }
370
+
371
+ .table a.table-primary:hover {
372
+ color: var(--blue-text);
373
+ }
374
+
375
+ tr[data-href] {
376
+ cursor: pointer;
377
+ }
378
+
379
+ tr[data-href]:hover td {
380
+ background: var(--surface-raised);
381
+ }
382
+
383
+ .table-compact {
384
+ font-size: 13px;
385
+ }
386
+
387
+ .table-compact th,
388
+ .table-compact td {
389
+ padding: 6px 12px;
390
+ }
391
+
392
+ /* --- filter bar and pager --- */
393
+ .toolbar {
394
+ display: flex;
395
+ flex-wrap: wrap;
396
+ align-items: center;
397
+ gap: 8px 16px;
398
+ margin-bottom: 12px;
399
+ }
400
+
401
+ .tabs {
402
+ display: flex;
403
+ gap: 2px;
404
+ flex-wrap: wrap;
405
+ flex: 1;
406
+ min-width: 0;
407
+ }
408
+
409
+ .tab {
410
+ padding: 4px 10px;
411
+ border-radius: 6px;
412
+ color: var(--text-muted);
413
+ white-space: nowrap;
414
+ }
415
+
416
+ .tab:hover {
417
+ background: var(--surface);
418
+ color: var(--text);
124
419
  text-decoration: none;
125
- color: #f5f6fa;
126
420
  }
127
421
 
128
- .separate-content {
129
- justify-content: space-between;
422
+ .tab[aria-current="true"] {
423
+ background: var(--surface-raised);
424
+ color: var(--text);
425
+ font-weight: 600;
426
+ }
427
+
428
+ .tab-count {
429
+ margin-left: 4px;
430
+ color: var(--text-faint);
431
+ font-variant-numeric: tabular-nums;
432
+ }
433
+
434
+ .menus {
130
435
  display: flex;
436
+ flex-wrap: wrap;
437
+ gap: 8px;
438
+ margin-left: auto;
131
439
  }
132
440
 
133
- .pill {
134
- border-radius: 50px;
135
- padding: 10px 20px;
136
- background-color: var(--pico-primary);
137
- color: var(--pico-color);
138
- font-size: 65%;
139
- font-weight: bold;
441
+ .menu {
442
+ position: relative;
140
443
  }
141
444
 
142
- .advancing {
143
- background-color: orange;
144
- color: var(--pico-color);
445
+ .menu > summary {
446
+ display: block;
447
+ max-width: 320px;
448
+ padding: 4px 10px;
449
+ border: 1px solid var(--border);
450
+ border-radius: 6px;
451
+ color: var(--text-muted);
452
+ cursor: pointer;
453
+ list-style: none;
454
+ overflow: hidden;
455
+ text-overflow: ellipsis;
456
+ white-space: nowrap;
145
457
  }
146
458
 
147
- .completed {
148
- background-color: var(--pico-primary);
149
- color: var(--pico-color);
459
+ .menu > summary::-webkit-details-marker {
460
+ display: none;
150
461
  }
151
462
 
152
- .halted, .failed {
153
- background-color: red;
154
- color: var(--pico-color);
463
+ .menu > summary::after {
464
+ content: " \25BE";
465
+ color: var(--text-faint);
155
466
  }
156
467
 
157
- .in_progress {
158
- background-color: green;
159
- color: var(--pico-color);
468
+ .menu > summary:hover,
469
+ .menu[data-active="true"] > summary {
470
+ color: var(--text);
160
471
  }
161
472
 
162
- .list-item {
163
- padding: 0.25rem;
473
+ .menu[data-active="true"] > summary {
474
+ border-color: var(--blue);
164
475
  }
165
476
 
166
- .disabled-link {
167
- color: var(--pico-table-border-color);
477
+ .menu-list {
478
+ position: absolute;
479
+ top: calc(100% + 4px);
480
+ right: 0;
481
+ z-index: 5;
482
+ width: max-content;
483
+ min-width: 220px;
484
+ max-width: min(420px, calc(100vw - 48px));
485
+ max-height: 320px;
486
+ padding: 4px;
487
+ overflow-y: auto;
488
+ scrollbar-color: var(--border) transparent;
489
+ border: 1px solid var(--border);
490
+ border-radius: var(--radius);
491
+ background: var(--surface-raised);
492
+ box-shadow: 0 8px 24px rgb(0 0 0 / 0.4);
168
493
  }
169
494
 
170
- .search-field {
171
- border-width: 0.125rem;
172
- background-color: var(--pico-background-color);
495
+ .menu-list a {
496
+ display: block;
497
+ padding: 5px 8px;
498
+ border-radius: 4px;
499
+ color: var(--text-muted);
500
+ font-family: var(--mono);
501
+ font-size: 12.5px;
502
+ overflow-wrap: anywhere;
173
503
  }
174
504
 
175
- .search-button {
176
- background-color: var(--pico-primary);
505
+ .menu-list a:hover {
506
+ background: var(--border-soft);
507
+ color: var(--text);
508
+ text-decoration: none;
177
509
  }
178
510
 
179
- .status-pill {
180
- font-size: 75%;
181
- border-radius: 50px;
182
- width: min-content;
183
- padding: 0.25rem 0.5rem 0.25rem 0.5rem;
184
- white-space: nowrap;
185
- font-weight: bold;
511
+ .menu-list a[aria-current="true"] {
512
+ color: var(--blue-text);
186
513
  }
187
514
 
188
- .tight-timestamp {
189
- font-size: 85%;
190
- align-content: center;
515
+ .menu-list a.menu-clear {
516
+ border-bottom: 1px solid var(--border-soft);
517
+ border-radius: 4px 4px 0 0;
518
+ font-family: var(--sans);
519
+ font-size: 13px;
191
520
  }
192
521
 
193
- .filter-group {
522
+ .pager {
194
523
  display: flex;
195
- align-items: baseline;
524
+ align-items: center;
525
+ justify-content: flex-end;
526
+ gap: 12px;
527
+ margin-top: 12px;
528
+ color: var(--text-faint);
529
+ font-size: 13px;
530
+ }
531
+
532
+ .pager-links {
533
+ display: flex;
534
+ gap: 4px;
535
+ }
536
+
537
+ .pager-link {
538
+ padding: 2px 10px;
539
+ border: 1px solid var(--border);
540
+ border-radius: 6px;
541
+ color: var(--text-muted);
542
+ font-size: 14px;
543
+ }
544
+
545
+ .pager-link:hover {
546
+ border-color: var(--text-faint);
547
+ color: var(--text);
548
+ text-decoration: none;
196
549
  }
197
550
 
198
- .filter-group-item {
199
- margin-right: 1rem;
551
+ .pager-link[aria-disabled="true"] {
552
+ border-color: var(--border-soft);
553
+ color: var(--border);
200
554
  }
201
555
 
202
- .hard-no-border {
203
- border: none !important;
556
+ /* --- run page --- */
557
+ .crumbs {
558
+ margin-bottom: 4px;
559
+ color: var(--text-faint);
560
+ font-size: 13px;
204
561
  }
205
562
 
206
- .details-grid {
563
+ .run-title {
207
564
  display: flex;
565
+ flex-wrap: wrap;
208
566
  align-items: center;
567
+ gap: 4px 12px;
209
568
  }
210
569
 
211
- .details-grid .grid {
212
- flex: 1;
213
- margin-right: 1rem;
570
+ .run-id {
571
+ color: var(--text-faint);
572
+ overflow-wrap: anywhere;
573
+ }
574
+
575
+ .facts {
576
+ display: grid;
577
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
578
+ gap: 12px 24px;
579
+ margin: 20px 0;
580
+ }
581
+
582
+ .fact dt {
583
+ color: var(--text-faint);
584
+ font-size: 12px;
585
+ font-weight: 600;
586
+ }
587
+
588
+ .fact dd {
589
+ color: var(--text);
590
+ font-variant-numeric: tabular-nums;
591
+ }
592
+
593
+ #steps {
594
+ scroll-margin-top: calc(var(--topbar-height) + 16px);
595
+ }
596
+
597
+ /* --- expandable rows --- */
598
+ .row-grid {
599
+ display: grid;
600
+ align-items: center;
601
+ gap: 16px;
602
+ padding: 9px 16px;
603
+ }
604
+
605
+ .row-grid-steps {
606
+ grid-template-columns: minmax(0, 1fr) 100px 76px 64px 130px 80px 12px;
607
+ }
608
+
609
+ .row-grid-errors {
610
+ grid-template-columns: 130px minmax(0, 1fr) minmax(0, 240px) 90px 12px;
611
+ }
612
+
613
+ .row-head {
614
+ border-bottom: 1px solid var(--border-soft);
615
+ color: var(--text-faint);
616
+ font-size: 12px;
617
+ font-weight: 600;
618
+ }
619
+
620
+ .row {
621
+ border-top: 1px solid var(--border-soft);
622
+ }
623
+
624
+ .row-head + .row {
625
+ border-top: 0;
626
+ }
627
+
628
+ .row > summary {
629
+ color: var(--text-muted);
630
+ cursor: pointer;
631
+ font-variant-numeric: tabular-nums;
632
+ list-style: none;
633
+ }
634
+
635
+ .row > summary::-webkit-details-marker {
636
+ display: none;
637
+ }
638
+
639
+ .row > summary:hover {
640
+ background: var(--surface-raised);
641
+ }
642
+
643
+ .row > summary::after {
644
+ content: "\25B8";
645
+ color: var(--text-faint);
646
+ }
647
+
648
+ .row[open] > summary {
649
+ background: var(--surface-raised);
650
+ }
651
+
652
+ .row[open] > summary::after {
653
+ content: "\25BE";
654
+ }
655
+
656
+ .cell-truncate {
657
+ overflow: hidden;
658
+ text-overflow: ellipsis;
659
+ white-space: nowrap;
660
+ }
661
+
662
+ .row-body {
663
+ padding: 16px;
664
+ border-top: 1px solid var(--border-soft);
665
+ background: var(--page);
666
+ }
667
+
668
+ .detail-grid {
669
+ display: grid;
670
+ grid-template-columns: repeat(2, minmax(0, 1fr));
671
+ gap: 16px;
672
+ }
673
+
674
+ .detail-label {
675
+ display: block;
676
+ margin-bottom: 4px;
677
+ color: var(--text-faint);
678
+ font-size: 12px;
679
+ font-weight: 600;
680
+ }
681
+
682
+ .detail-block {
683
+ margin-top: 16px;
684
+ }
685
+
686
+ .detail-block:first-child {
687
+ margin-top: 0;
688
+ }
689
+
690
+ .code {
691
+ max-height: 280px;
692
+ padding: 10px 12px;
693
+ overflow: auto;
694
+ border: 1px solid var(--border-soft);
695
+ border-radius: 6px;
696
+ background: var(--surface);
697
+ color: var(--text-muted);
698
+ scrollbar-color: var(--border) transparent;
699
+ }
700
+
701
+ .error + .error {
702
+ margin-top: 12px;
703
+ }
704
+
705
+ .error-title {
706
+ color: var(--red-text);
707
+ font-weight: 600;
708
+ }
709
+
710
+ .error-message {
711
+ margin-bottom: 8px;
712
+ color: var(--text-muted);
713
+ overflow-wrap: anywhere;
714
+ }
715
+
716
+ /* --- pipeline graph --- */
717
+ .graph-scroller {
718
+ padding: 16px;
719
+ overflow-x: auto;
720
+ scrollbar-color: var(--border) transparent;
721
+ }
722
+
723
+ .graph {
724
+ display: block;
725
+ margin: 0 auto;
726
+ }
727
+
728
+ .graph-edge {
729
+ fill: none;
730
+ stroke: var(--border);
731
+ stroke-width: 1.5;
732
+ }
733
+
734
+ .graph-edge[data-state="reached"] {
735
+ stroke: var(--blue);
736
+ }
737
+
738
+ .graph-edge[data-state="skipped"] {
739
+ stroke-dasharray: 4 4;
740
+ }
741
+
742
+ .graph-card {
743
+ fill: var(--page);
744
+ stroke: var(--border);
745
+ stroke-width: 1.5;
214
746
  }
215
747
 
216
- .details-grid::after {
217
- flex-shrink: 0;
748
+ .graph-node[data-state="completed"] .graph-card {
749
+ stroke: var(--blue);
218
750
  }
219
751
 
220
- .grid-header {
221
- margin-bottom: 1rem;
222
- padding-bottom: 0.25rem;
223
- border-bottom: 2px solid var(--pico-border-color);
752
+ .graph-node[data-state="active"] .graph-card {
753
+ stroke: var(--green);
754
+ animation: graph-pulse 1.6s ease-in-out infinite;
224
755
  }
225
756
 
226
- .small-table {
227
- font-size: 75%;
757
+ .graph-node[data-state="failed"] .graph-card {
758
+ stroke: var(--red);
759
+ stroke-width: 2;
228
760
  }
229
761
 
230
- .big-pill {
231
- padding: 0.5rem 1rem 0.5rem 1rem;
762
+ .graph-node[data-state="skipped"] .graph-card {
763
+ stroke-dasharray: 4 4;
232
764
  }
233
765
 
234
- .errors {
235
- color: red;
766
+ .graph-node[data-selected="true"] .graph-card {
767
+ fill: var(--border);
236
768
  }
237
769
 
238
- .icon {
239
- vertical-align: text-top;
770
+ .graph-bar-track {
771
+ fill: var(--border);
772
+ }
773
+
774
+ .graph-bar-segment[data-status="completed"] {
775
+ fill: var(--blue);
776
+ }
777
+
778
+ .graph-bar-segment[data-status="in_progress"] {
779
+ fill: var(--green);
780
+ }
781
+
782
+ .graph-bar-segment[data-status="waiting"],
783
+ .graph-bar-segment[data-status="pending"] {
784
+ fill: var(--gray);
785
+ }
786
+
787
+ .graph-bar-segment[data-status="advancing"] {
788
+ fill: var(--amber);
789
+ }
790
+
791
+ .graph-bar-segment[data-status="failed"] {
792
+ fill: var(--red);
793
+ }
794
+
795
+ .graph-header {
796
+ padding: 8px 12px 0;
797
+ }
798
+
799
+ .graph-transition {
800
+ color: var(--text-faint);
801
+ font-family: var(--mono);
802
+ font-size: 11px;
803
+ line-height: 16px;
804
+ white-space: nowrap;
805
+ overflow: hidden;
806
+ text-overflow: ellipsis;
807
+ }
808
+
809
+ .graph-name {
810
+ display: block;
811
+ color: var(--text);
812
+ font-size: 14px;
813
+ font-weight: 700;
814
+ line-height: 22px;
815
+ text-decoration: none;
816
+ white-space: nowrap;
817
+ overflow: hidden;
818
+ text-overflow: ellipsis;
819
+ }
820
+
821
+ .graph-name:hover {
822
+ color: var(--blue-text);
823
+ text-decoration: none;
824
+ }
825
+
826
+ .graph-labels {
827
+ display: grid;
828
+ grid-template-columns: 1fr 1fr;
829
+ padding: 0 12px;
830
+ }
831
+
832
+ .graph-label {
833
+ color: var(--text-muted);
834
+ font-size: 11px;
835
+ line-height: 18px;
836
+ white-space: nowrap;
837
+ overflow: hidden;
838
+ text-overflow: ellipsis;
839
+ }
840
+
841
+ a.graph-label:hover {
842
+ text-decoration: underline;
843
+ }
844
+
845
+ .graph-label-wide {
846
+ grid-column: 1 / -1;
847
+ }
848
+
849
+ .graph-label[data-tone="failed"] {
850
+ color: var(--red-text);
851
+ }
852
+
853
+ .graph-label[data-tone="in_progress"] {
854
+ color: var(--green);
855
+ }
856
+
857
+ .graph-label[data-tone="advancing"],
858
+ .graph-label[data-tone="warning"] {
859
+ color: var(--amber);
860
+ }
861
+
862
+ .graph-label[data-tone="quiet"] {
863
+ color: var(--text-faint);
864
+ }
865
+
866
+ @keyframes graph-pulse {
867
+ 50% { stroke-opacity: 0.45; }
868
+ }
869
+
870
+ @media (prefers-reduced-motion: reduce) {
871
+ .graph-node[data-state="active"] .graph-card {
872
+ animation: none;
873
+ }
874
+ }
875
+
876
+ /* --- narrow screens --- */
877
+ @media (max-width: 820px) {
878
+ .shell {
879
+ padding: 0 16px;
880
+ }
881
+
882
+ .topbar-inner {
883
+ gap: 16px;
884
+ }
885
+
886
+ .nav {
887
+ gap: 16px;
888
+ }
889
+
890
+ .brand span,
891
+ .version {
892
+ display: none;
893
+ }
894
+
895
+ .stats {
896
+ grid-template-columns: repeat(2, minmax(0, 1fr));
897
+ }
898
+
899
+ .stat:nth-child(odd) {
900
+ border-left: 0;
901
+ }
902
+
903
+ .stat:nth-child(n + 3) {
904
+ border-top: 1px solid var(--border-soft);
905
+ }
906
+
907
+ .page-title {
908
+ font-size: 18px;
909
+ line-height: 24px;
910
+ }
911
+
912
+ .halt {
913
+ grid-template-columns: minmax(0, 1fr);
914
+ }
915
+
916
+ .halt-time {
917
+ grid-column: 1;
918
+ grid-row: auto;
919
+ }
920
+
921
+ .tabs {
922
+ flex-basis: 100%;
923
+ }
924
+
925
+ .menus {
926
+ margin-left: 0;
927
+ }
928
+
929
+ .menu-list {
930
+ right: auto;
931
+ left: 0;
932
+ }
933
+
934
+ .row-grid-steps {
935
+ grid-template-columns: minmax(0, 1fr) 90px 60px 12px;
936
+ }
937
+
938
+ .row-grid-errors {
939
+ grid-template-columns: minmax(0, 1fr) 12px;
940
+ }
941
+
942
+ .row-grid .wide-only {
943
+ display: none;
944
+ }
945
+
946
+ .row-head.row-grid-errors {
947
+ display: none;
948
+ }
949
+
950
+ .detail-grid {
951
+ grid-template-columns: minmax(0, 1fr);
952
+ }
240
953
  }