changeledger 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/AGENTS.md +25 -0
  2. package/LICENSE +21 -0
  3. package/README.md +160 -0
  4. package/bin/changeledger.mjs +375 -0
  5. package/package.json +72 -0
  6. package/src/atomic-write.mjs +134 -0
  7. package/src/change.mjs +102 -0
  8. package/src/check.mjs +536 -0
  9. package/src/commands/agent.mjs +256 -0
  10. package/src/commands/check.mjs +48 -0
  11. package/src/commands/graduate.mjs +105 -0
  12. package/src/commands/init.mjs +43 -0
  13. package/src/commands/new.mjs +164 -0
  14. package/src/commands/register.mjs +28 -0
  15. package/src/commands/release.mjs +138 -0
  16. package/src/commands/view.mjs +52 -0
  17. package/src/config.mjs +76 -0
  18. package/src/contract.mjs +100 -0
  19. package/src/git.mjs +73 -0
  20. package/src/lifecycle.mjs +57 -0
  21. package/src/metrics.mjs +143 -0
  22. package/src/paths.mjs +12 -0
  23. package/src/registry.mjs +55 -0
  24. package/src/release.mjs +71 -0
  25. package/src/repo.mjs +122 -0
  26. package/src/slug.mjs +12 -0
  27. package/src/spec.mjs +12 -0
  28. package/src/viewer/domain.mjs +133 -0
  29. package/src/viewer/public/api.js +25 -0
  30. package/src/viewer/public/app-state.js +87 -0
  31. package/src/viewer/public/app.js +717 -0
  32. package/src/viewer/public/index.html +64 -0
  33. package/src/viewer/public/security.js +65 -0
  34. package/src/viewer/public/state.js +31 -0
  35. package/src/viewer/public/styles.css +1062 -0
  36. package/src/viewer/public/templates.js +9 -0
  37. package/src/viewer/public/view-parts.js +162 -0
  38. package/src/viewer/public/view-renderers.js +191 -0
  39. package/src/viewer/server/router.mjs +200 -0
  40. package/src/viewer/server/security.mjs +27 -0
  41. package/src/writer.mjs +151 -0
  42. package/src/yaml.mjs +75 -0
  43. package/templates/AGENTS.md +540 -0
  44. package/templates/config.yml +55 -0
@@ -0,0 +1,1062 @@
1
+ :root {
2
+ --bg: #0f1115;
3
+ --panel: #171a21;
4
+ --panel-2: #1e222b;
5
+ --line: #2a2f3a;
6
+ --text: #e6e8eb;
7
+ --muted: #8b929e;
8
+ --accent: #5b9dff;
9
+ --feature: #3fb950;
10
+ --bug: #f85149;
11
+ --audit: #a371f7;
12
+ --refactor: #d29922;
13
+ --chore: #6e7681;
14
+ --done: #3fb950;
15
+ --blocked: #f85149;
16
+ --status-draft: #8b929e;
17
+ --status-approved: #7aa2f7;
18
+ --status-in-progress: #4fd1c5;
19
+ --status-in-review: #b794f4;
20
+ --status-in-validation: #f6c177;
21
+ --status-blocked: #f85149;
22
+ --status-done: #3fb950;
23
+ --status-discarded: #9aa0aa;
24
+ --status-muted: #8b929e;
25
+ font-family: "Avenir Next", Avenir, ui-sans-serif, sans-serif;
26
+ }
27
+
28
+ * {
29
+ box-sizing: border-box;
30
+ }
31
+
32
+ body {
33
+ margin: 0;
34
+ background: var(--bg);
35
+ color: var(--text);
36
+ font-size: 14px;
37
+ }
38
+
39
+ .hidden {
40
+ /* biome-ignore lint/complexity/noImportantStyles: utility must beat any element display */
41
+ display: none !important;
42
+ }
43
+
44
+ /* Top bar */
45
+ .topbar {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 8px;
49
+ padding: 10px 14px;
50
+ background: var(--panel);
51
+ border-bottom: 1px solid var(--line);
52
+ position: sticky;
53
+ top: 0;
54
+ z-index: 10;
55
+ }
56
+ .brand {
57
+ font-weight: 700;
58
+ letter-spacing: 0.3px;
59
+ }
60
+ .spacer {
61
+ flex: 1;
62
+ }
63
+ .search,
64
+ .filter {
65
+ background: var(--panel-2);
66
+ border: 1px solid var(--line);
67
+ color: var(--text);
68
+ border-radius: 8px;
69
+ padding: 7px 10px;
70
+ font-size: 13px;
71
+ }
72
+ .search {
73
+ width: clamp(180px, 14vw, 260px);
74
+ min-width: 0;
75
+ }
76
+ .tab {
77
+ background: transparent;
78
+ border: 1px solid var(--line);
79
+ color: var(--muted);
80
+ padding: 7px 10px;
81
+ border-radius: 8px;
82
+ cursor: pointer;
83
+ }
84
+ .tab.active {
85
+ color: var(--text);
86
+ border-color: var(--accent);
87
+ }
88
+ .tabs {
89
+ display: flex;
90
+ gap: 4px;
91
+ }
92
+
93
+ .chip {
94
+ font-size: 12px;
95
+ padding: 5px 10px;
96
+ border-radius: 999px;
97
+ background: var(--panel-2);
98
+ border: 1px solid var(--line);
99
+ color: var(--muted);
100
+ cursor: pointer;
101
+ text-transform: capitalize;
102
+ }
103
+ .chip.active {
104
+ color: var(--text);
105
+ border-color: var(--accent);
106
+ background: rgba(91, 157, 255, 0.12);
107
+ }
108
+ .filter-menu {
109
+ position: relative;
110
+ }
111
+ .filter-trigger {
112
+ min-width: 138px;
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 8px;
116
+ list-style: none;
117
+ background: var(--panel-2);
118
+ border: 1px solid var(--line);
119
+ color: var(--text);
120
+ border-radius: 8px;
121
+ padding: 7px 10px;
122
+ font-size: 13px;
123
+ cursor: pointer;
124
+ user-select: none;
125
+ }
126
+ .filter-trigger::-webkit-details-marker {
127
+ display: none;
128
+ }
129
+ .filter-trigger svg,
130
+ .icon-button svg {
131
+ width: 16px;
132
+ height: 16px;
133
+ fill: none;
134
+ stroke: currentColor;
135
+ stroke-width: 1.5;
136
+ stroke-linecap: round;
137
+ }
138
+ .filter-trigger svg {
139
+ color: var(--muted);
140
+ }
141
+ .filter-trigger:focus-visible,
142
+ .filter-menu[open] .filter-trigger {
143
+ outline: none;
144
+ border-color: var(--accent);
145
+ box-shadow: 0 0 0 3px rgba(91, 157, 255, 0.13);
146
+ }
147
+ .filter-chevron {
148
+ width: 14px;
149
+ height: 14px;
150
+ margin-left: auto;
151
+ color: var(--muted);
152
+ transition: transform 0.16s ease;
153
+ }
154
+ .filter-menu[open] .filter-chevron {
155
+ transform: rotate(180deg);
156
+ }
157
+ .filter-popover {
158
+ position: absolute;
159
+ top: calc(100% + 8px);
160
+ left: 0;
161
+ z-index: 30;
162
+ width: 248px;
163
+ padding: 10px;
164
+ border: 1px solid #353c49;
165
+ border-radius: 12px;
166
+ background: #191d25;
167
+ box-shadow: 0 18px 48px rgba(0, 0, 0, 0.42);
168
+ }
169
+ .filter-heading {
170
+ display: flex;
171
+ justify-content: space-between;
172
+ align-items: center;
173
+ padding: 2px 4px 8px;
174
+ color: var(--muted);
175
+ font-size: 10px;
176
+ font-weight: 700;
177
+ letter-spacing: 0.1em;
178
+ text-transform: uppercase;
179
+ }
180
+ .filter-heading button {
181
+ padding: 2px 4px;
182
+ border: 0;
183
+ background: transparent;
184
+ color: var(--accent);
185
+ font: inherit;
186
+ cursor: pointer;
187
+ }
188
+ .visibility-heading {
189
+ margin-top: 8px;
190
+ padding-top: 10px;
191
+ border-top: 1px solid var(--line);
192
+ }
193
+ .filter-options {
194
+ display: grid;
195
+ grid-template-columns: 1fr 1fr;
196
+ gap: 3px;
197
+ }
198
+ .check-option {
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 8px;
202
+ min-height: 32px;
203
+ padding: 5px 6px;
204
+ border-radius: 7px;
205
+ color: var(--muted);
206
+ font-size: 12px;
207
+ text-transform: capitalize;
208
+ cursor: pointer;
209
+ }
210
+ .check-option:hover {
211
+ color: var(--text);
212
+ background: var(--panel-2);
213
+ }
214
+ .check-option input {
215
+ position: absolute;
216
+ opacity: 0;
217
+ pointer-events: none;
218
+ }
219
+ .check-box {
220
+ width: 14px;
221
+ height: 14px;
222
+ display: grid;
223
+ place-items: center;
224
+ flex: 0 0 auto;
225
+ border: 1px solid #4a5260;
226
+ border-radius: 4px;
227
+ background: var(--bg);
228
+ }
229
+ .check-option input:checked + .check-box {
230
+ border-color: var(--accent);
231
+ background: var(--accent);
232
+ }
233
+ .check-option input:checked + .check-box::after {
234
+ content: "";
235
+ width: 6px;
236
+ height: 3px;
237
+ border: solid #08111f;
238
+ border-width: 0 0 2px 2px;
239
+ transform: rotate(-45deg) translateY(-1px);
240
+ }
241
+ .check-option input:focus-visible + .check-box {
242
+ outline: 2px solid var(--accent);
243
+ outline-offset: 2px;
244
+ }
245
+
246
+ /* Table view */
247
+ .table-view {
248
+ padding: 16px;
249
+ overflow-x: auto;
250
+ }
251
+ .grid {
252
+ width: 100%;
253
+ border-collapse: collapse;
254
+ }
255
+ .grid th,
256
+ .grid td {
257
+ text-align: left;
258
+ vertical-align: middle;
259
+ padding: 10px 12px;
260
+ border-bottom: 1px solid var(--line);
261
+ }
262
+ .grid th {
263
+ color: var(--muted);
264
+ font-weight: 600;
265
+ cursor: pointer;
266
+ user-select: none;
267
+ position: sticky;
268
+ top: 0;
269
+ background: var(--bg);
270
+ }
271
+ .grid th .column-label {
272
+ vertical-align: middle;
273
+ }
274
+ .sort-indicator {
275
+ width: 10px;
276
+ height: 10px;
277
+ margin-left: 3px;
278
+ vertical-align: -1px;
279
+ fill: none;
280
+ stroke: currentColor;
281
+ stroke-width: 1.25;
282
+ stroke-linecap: round;
283
+ stroke-linejoin: round;
284
+ }
285
+ .grid tbody tr {
286
+ cursor: pointer;
287
+ }
288
+ .grid tbody tr:hover {
289
+ background: var(--panel);
290
+ }
291
+ .grid .cell-nowrap {
292
+ white-space: nowrap;
293
+ }
294
+ .grid .cell-title {
295
+ min-width: 260px;
296
+ max-width: 48vw;
297
+ overflow: hidden;
298
+ text-overflow: ellipsis;
299
+ }
300
+ .grid .cell-deps {
301
+ min-width: 170px;
302
+ white-space: normal;
303
+ overflow-wrap: anywhere;
304
+ }
305
+ .mono {
306
+ font-family: ui-monospace, monospace;
307
+ font-size: 12px;
308
+ color: var(--muted);
309
+ }
310
+
311
+ .badge {
312
+ font-size: 11px;
313
+ font-weight: 600;
314
+ padding: 1px 7px;
315
+ border-radius: 999px;
316
+ background: var(--panel-2);
317
+ color: var(--muted);
318
+ text-transform: uppercase;
319
+ }
320
+
321
+ /* Board */
322
+ .board {
323
+ display: flex;
324
+ gap: 14px;
325
+ padding: 16px;
326
+ overflow-x: auto;
327
+ align-items: flex-start;
328
+ scrollbar-width: thin;
329
+ scrollbar-color: #3a414e transparent;
330
+ }
331
+ .board::-webkit-scrollbar {
332
+ height: 8px;
333
+ }
334
+ .board::-webkit-scrollbar-track {
335
+ background: transparent;
336
+ }
337
+ .board::-webkit-scrollbar-thumb {
338
+ background: #3a414e;
339
+ border: 2px solid var(--bg);
340
+ border-radius: 999px;
341
+ }
342
+ .column {
343
+ background: var(--panel);
344
+ border: 1px solid var(--line);
345
+ border-radius: 12px;
346
+ width: clamp(238px, calc((100vw - 116px) / 7), 320px);
347
+ min-width: 238px;
348
+ max-width: 320px;
349
+ flex: 0 0 clamp(238px, calc((100vw - 116px) / 7), 320px);
350
+ }
351
+ .column-head {
352
+ display: flex;
353
+ justify-content: space-between;
354
+ align-items: center;
355
+ padding: 12px 14px;
356
+ font-weight: 600;
357
+ text-transform: capitalize;
358
+ border-bottom: 1px solid var(--line);
359
+ }
360
+ .column-head .count {
361
+ color: var(--muted);
362
+ font-weight: 500;
363
+ }
364
+ .column-body {
365
+ padding: 10px;
366
+ display: flex;
367
+ flex-direction: column;
368
+ gap: 10px;
369
+ min-height: 40px;
370
+ }
371
+
372
+ .card {
373
+ background: var(--panel-2);
374
+ border: 1px solid var(--line);
375
+ border-left: 3px solid var(--type-color, var(--chore));
376
+ border-radius: 10px;
377
+ padding: 11px 12px;
378
+ cursor: pointer;
379
+ transition: border-color 0.12s;
380
+ }
381
+ .card:hover {
382
+ border-color: var(--accent);
383
+ }
384
+ .card-top {
385
+ display: flex;
386
+ align-items: center;
387
+ gap: 8px;
388
+ margin-bottom: 6px;
389
+ }
390
+ .card-id {
391
+ font-family: ui-monospace, monospace;
392
+ color: var(--muted);
393
+ font-size: 12px;
394
+ }
395
+ .type-tag {
396
+ font-size: 10px;
397
+ font-weight: 700;
398
+ text-transform: uppercase;
399
+ padding: 1px 6px;
400
+ border-radius: 999px;
401
+ color: #0f1115;
402
+ background: var(--type-color, var(--chore));
403
+ }
404
+ .status-tag {
405
+ display: inline-flex;
406
+ align-items: center;
407
+ gap: 6px;
408
+ padding: 2px 8px;
409
+ border: 1px solid color-mix(in srgb, var(--status-color) 45%, var(--line));
410
+ border-radius: 999px;
411
+ background: color-mix(in srgb, var(--status-color) 9%, transparent);
412
+ color: color-mix(in srgb, var(--status-color) 82%, white);
413
+ font-size: 11px;
414
+ font-weight: 600;
415
+ }
416
+ .status-tag i {
417
+ width: 5px;
418
+ height: 5px;
419
+ border-radius: 50%;
420
+ background: var(--status-color);
421
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--status-color) 16%, transparent);
422
+ }
423
+ .card-title {
424
+ font-weight: 600;
425
+ line-height: 1.3;
426
+ }
427
+ .progress {
428
+ margin-top: 9px;
429
+ height: 5px;
430
+ background: var(--line);
431
+ border-radius: 999px;
432
+ overflow: hidden;
433
+ }
434
+ .progress > i {
435
+ display: block;
436
+ height: 100%;
437
+ background: var(--done);
438
+ }
439
+ .card-meta {
440
+ display: flex;
441
+ gap: 10px;
442
+ margin-top: 7px;
443
+ font-size: 11px;
444
+ color: var(--muted);
445
+ }
446
+ .flag-blocked {
447
+ color: var(--blocked);
448
+ font-weight: 600;
449
+ }
450
+ .validation-actions {
451
+ position: relative;
452
+ margin: 18px 0;
453
+ padding: 18px;
454
+ border: 1px solid color-mix(in srgb, var(--status-in-validation) 32%, var(--line));
455
+ border-radius: 12px;
456
+ background: linear-gradient(135deg, rgba(246, 193, 119, 0.08), transparent 48%), var(--panel);
457
+ transition: opacity 0.15s ease;
458
+ }
459
+ .validation-actions.is-pending {
460
+ opacity: 0.62;
461
+ cursor: wait;
462
+ }
463
+ .validation-kicker {
464
+ color: var(--status-in-validation);
465
+ font-size: 10px;
466
+ font-weight: 700;
467
+ letter-spacing: 0.12em;
468
+ text-transform: uppercase;
469
+ }
470
+ .validation-copy h2 {
471
+ margin: 4px 0 3px;
472
+ color: var(--text);
473
+ font-size: 16px;
474
+ letter-spacing: -0.01em;
475
+ text-transform: none;
476
+ }
477
+ .validation-copy p {
478
+ margin: 0;
479
+ color: var(--muted);
480
+ font-size: 12px;
481
+ line-height: 1.5;
482
+ }
483
+ .validation-controls {
484
+ display: grid;
485
+ grid-template-columns: auto minmax(190px, 1fr) auto;
486
+ align-items: end;
487
+ gap: 10px;
488
+ margin-top: 15px;
489
+ }
490
+ .rejection-field {
491
+ display: flex;
492
+ flex-direction: column;
493
+ gap: 5px;
494
+ }
495
+ .rejection-field label {
496
+ color: var(--muted);
497
+ font-size: 10px;
498
+ font-weight: 600;
499
+ }
500
+ .rejection-field input {
501
+ min-width: 0;
502
+ height: 34px;
503
+ padding: 0 10px;
504
+ border: 1px solid var(--line);
505
+ border-radius: 7px;
506
+ outline: none;
507
+ background: var(--bg);
508
+ color: var(--text);
509
+ font: inherit;
510
+ }
511
+ .rejection-field input:focus {
512
+ border-color: var(--accent);
513
+ box-shadow: 0 0 0 3px rgba(91, 157, 255, 0.12);
514
+ }
515
+ .validation-error {
516
+ margin: 0;
517
+ color: #ff8078;
518
+ font-size: 11px;
519
+ }
520
+ .button {
521
+ min-height: 34px;
522
+ padding: 0 12px;
523
+ border: 1px solid var(--line);
524
+ border-radius: 7px;
525
+ background: var(--panel-2);
526
+ color: var(--text);
527
+ font: inherit;
528
+ font-size: 12px;
529
+ font-weight: 650;
530
+ cursor: pointer;
531
+ }
532
+ .button:hover:not(:disabled) {
533
+ transform: translateY(-1px);
534
+ }
535
+ .button:focus-visible {
536
+ outline: 2px solid var(--accent);
537
+ outline-offset: 2px;
538
+ }
539
+ .button-primary {
540
+ border-color: #67a9ff;
541
+ background: var(--accent);
542
+ color: #07101d;
543
+ }
544
+ .button-danger {
545
+ border-color: rgba(248, 81, 73, 0.48);
546
+ background: rgba(248, 81, 73, 0.08);
547
+ color: #ff8d86;
548
+ }
549
+ .owner {
550
+ font-weight: 600;
551
+ }
552
+ .card.archived {
553
+ opacity: 0.55;
554
+ }
555
+ .card[draggable="true"] {
556
+ cursor: grab;
557
+ }
558
+ .column.drop-target {
559
+ outline: 2px dashed var(--accent, #4a9eff);
560
+ outline-offset: -4px;
561
+ border-radius: 8px;
562
+ }
563
+ .global-view {
564
+ padding: 18px;
565
+ overflow: auto;
566
+ }
567
+ .search-group {
568
+ margin-bottom: 20px;
569
+ }
570
+ .search-group h3 {
571
+ margin: 0 0 8px;
572
+ font-size: 13px;
573
+ color: var(--muted);
574
+ }
575
+ .search-hit {
576
+ display: flex;
577
+ align-items: center;
578
+ gap: 10px;
579
+ padding: 8px 10px;
580
+ border: 1px solid var(--line);
581
+ border-radius: 6px;
582
+ margin-bottom: 6px;
583
+ cursor: pointer;
584
+ }
585
+ .search-hit:hover {
586
+ background: var(--panel);
587
+ }
588
+ .metrics-view {
589
+ padding: 18px;
590
+ overflow: auto;
591
+ }
592
+ .metrics-cards {
593
+ display: flex;
594
+ gap: 14px;
595
+ flex-wrap: wrap;
596
+ }
597
+ .metric-card {
598
+ background: var(--panel);
599
+ border: 1px solid var(--line);
600
+ border-radius: 8px;
601
+ padding: 16px 22px;
602
+ min-width: 120px;
603
+ }
604
+ .metric-val {
605
+ font-size: 26px;
606
+ font-weight: 700;
607
+ }
608
+ .metric-label {
609
+ font-size: 12px;
610
+ color: var(--muted);
611
+ margin-top: 4px;
612
+ }
613
+ .metrics-h {
614
+ margin: 22px 0 10px;
615
+ font-size: 13px;
616
+ color: var(--muted);
617
+ }
618
+ .bar-row {
619
+ display: flex;
620
+ align-items: center;
621
+ gap: 10px;
622
+ margin-bottom: 5px;
623
+ font-size: 12px;
624
+ }
625
+ .bar-date {
626
+ width: 92px;
627
+ color: var(--muted);
628
+ }
629
+ .bar {
630
+ display: inline-block;
631
+ height: 14px;
632
+ background: var(--accent, #4a9eff);
633
+ border-radius: 3px;
634
+ min-width: 2px;
635
+ }
636
+ .git-commits {
637
+ list-style: none;
638
+ padding: 0;
639
+ margin: 8px 0 0;
640
+ }
641
+ .git-commits li {
642
+ padding: 4px 0;
643
+ font-size: 13px;
644
+ border-bottom: 1px solid var(--line);
645
+ }
646
+
647
+ /* Overlay + detail */
648
+ .overlay {
649
+ position: fixed;
650
+ inset: 0;
651
+ background: rgba(0, 0, 0, 0.55);
652
+ display: flex;
653
+ justify-content: flex-end;
654
+ z-index: 20;
655
+ }
656
+ .detail {
657
+ width: min(720px, 92vw);
658
+ height: 100%;
659
+ background: var(--bg);
660
+ border-left: 1px solid var(--line);
661
+ overflow-y: auto;
662
+ padding: 22px 26px 60px;
663
+ }
664
+ .detail h1 {
665
+ font-size: 22px;
666
+ margin: 0 0 4px;
667
+ }
668
+ .detail-meta {
669
+ display: flex;
670
+ flex-wrap: wrap;
671
+ gap: 8px;
672
+ margin: 10px 0 4px;
673
+ }
674
+ .pill {
675
+ font-size: 11px;
676
+ padding: 2px 9px;
677
+ border-radius: 999px;
678
+ background: var(--panel-2);
679
+ border: 1px solid var(--line);
680
+ color: var(--muted);
681
+ }
682
+ .icon-button {
683
+ width: 32px;
684
+ height: 32px;
685
+ display: grid;
686
+ place-items: center;
687
+ padding: 0;
688
+ border: 1px solid var(--line);
689
+ border-radius: 50%;
690
+ background: color-mix(in srgb, var(--panel-2) 88%, transparent);
691
+ color: var(--muted);
692
+ cursor: pointer;
693
+ transition:
694
+ color 0.14s ease,
695
+ border-color 0.14s ease,
696
+ background 0.14s ease,
697
+ transform 0.14s ease;
698
+ }
699
+ .icon-button:hover {
700
+ border-color: #4a5260;
701
+ background: #252a34;
702
+ color: var(--text);
703
+ transform: rotate(3deg);
704
+ }
705
+ .icon-button:focus-visible {
706
+ outline: 2px solid var(--accent);
707
+ outline-offset: 3px;
708
+ }
709
+ .close {
710
+ position: absolute;
711
+ top: 16px;
712
+ right: 22px;
713
+ }
714
+
715
+ .pipeline {
716
+ display: flex;
717
+ flex-wrap: wrap;
718
+ gap: 6px;
719
+ margin: 16px 0;
720
+ }
721
+ .stage-chip {
722
+ font-size: 12px;
723
+ padding: 5px 11px;
724
+ border-radius: 999px;
725
+ background: var(--panel);
726
+ border: 1px solid var(--line);
727
+ color: var(--muted);
728
+ cursor: pointer;
729
+ }
730
+ .stage-chip:hover {
731
+ color: var(--text);
732
+ border-color: var(--accent);
733
+ }
734
+
735
+ .stage {
736
+ border-top: 1px solid var(--line);
737
+ padding-top: 14px;
738
+ margin-top: 18px;
739
+ }
740
+ .stage h2 {
741
+ font-size: 15px;
742
+ color: var(--accent);
743
+ text-transform: uppercase;
744
+ letter-spacing: 0.5px;
745
+ }
746
+ .stage-content {
747
+ line-height: 1.6;
748
+ }
749
+ .stage-content code {
750
+ background: var(--panel-2);
751
+ padding: 1px 5px;
752
+ border-radius: 5px;
753
+ font-size: 12px;
754
+ }
755
+ .stage-content pre {
756
+ background: var(--panel-2);
757
+ padding: 12px;
758
+ border-radius: 8px;
759
+ overflow-x: auto;
760
+ }
761
+ .stage-content table {
762
+ border-collapse: collapse;
763
+ width: 100%;
764
+ }
765
+ .stage-content th,
766
+ .stage-content td {
767
+ border: 1px solid var(--line);
768
+ padding: 6px 9px;
769
+ text-align: left;
770
+ }
771
+ .stage-content h3 {
772
+ font-size: 14px;
773
+ }
774
+ .graduation-history {
775
+ margin: 12px 0 20px;
776
+ border: 1px solid var(--line);
777
+ border-radius: 10px;
778
+ background: linear-gradient(120deg, rgba(91, 157, 255, 0.055), transparent 42%), var(--panel);
779
+ overflow: hidden;
780
+ }
781
+ .graduation-history summary {
782
+ display: flex;
783
+ align-items: center;
784
+ gap: 9px;
785
+ padding: 11px 13px;
786
+ list-style: none;
787
+ color: var(--muted);
788
+ font-size: 12px;
789
+ font-weight: 650;
790
+ cursor: pointer;
791
+ }
792
+ .graduation-history summary::-webkit-details-marker {
793
+ display: none;
794
+ }
795
+ .graduation-history summary:hover {
796
+ color: var(--text);
797
+ }
798
+ .history-icon {
799
+ color: var(--accent);
800
+ font-size: 16px;
801
+ transition: transform 0.16s ease;
802
+ }
803
+ .graduation-history[open] .history-icon {
804
+ transform: rotate(90deg);
805
+ }
806
+ .history-count {
807
+ min-width: 22px;
808
+ padding: 1px 6px;
809
+ border-radius: 999px;
810
+ background: var(--panel-2);
811
+ color: var(--text);
812
+ text-align: center;
813
+ font-family: ui-monospace, monospace;
814
+ font-size: 10px;
815
+ }
816
+ .graduation-history ol {
817
+ margin: 0;
818
+ padding: 4px 18px 14px 42px;
819
+ border-top: 1px solid var(--line);
820
+ color: var(--muted);
821
+ font-size: 12px;
822
+ line-height: 1.55;
823
+ }
824
+ .graduation-history li {
825
+ padding: 4px 0 4px 3px;
826
+ }
827
+
828
+ .tasks {
829
+ list-style: none;
830
+ padding: 0;
831
+ margin: 0;
832
+ }
833
+ .task {
834
+ display: flex;
835
+ gap: 9px;
836
+ padding: 7px 0;
837
+ border-bottom: 1px solid var(--line);
838
+ align-items: baseline;
839
+ }
840
+ .task .mark {
841
+ width: 16px;
842
+ flex-shrink: 0;
843
+ }
844
+ .task.done .text {
845
+ color: var(--muted);
846
+ }
847
+ .task.done .mark {
848
+ color: var(--done);
849
+ }
850
+ .task.blocked .mark {
851
+ color: var(--blocked);
852
+ }
853
+ .task .cr {
854
+ font-size: 10px;
855
+ color: var(--accent);
856
+ font-family: ui-monospace, monospace;
857
+ }
858
+ .task .when {
859
+ font-size: 10px;
860
+ color: var(--muted);
861
+ font-family: ui-monospace, monospace;
862
+ margin-left: auto;
863
+ }
864
+ .task .reason {
865
+ font-size: 12px;
866
+ color: var(--blocked);
867
+ }
868
+
869
+ /* Graph */
870
+ .graph {
871
+ padding: 24px;
872
+ overflow: auto;
873
+ }
874
+ .graph svg {
875
+ width: 100%;
876
+ }
877
+ .mermaid-expandable {
878
+ position: relative;
879
+ cursor: zoom-in;
880
+ border-radius: 9px;
881
+ transition:
882
+ box-shadow 0.16s ease,
883
+ background 0.16s ease;
884
+ }
885
+ .mermaid-expandable::after {
886
+ content: "Expand";
887
+ position: absolute;
888
+ top: 8px;
889
+ right: 8px;
890
+ padding: 3px 7px;
891
+ border: 1px solid var(--line);
892
+ border-radius: 999px;
893
+ background: rgba(15, 17, 21, 0.82);
894
+ color: var(--muted);
895
+ font-size: 10px;
896
+ opacity: 0;
897
+ transition: opacity 0.14s ease;
898
+ pointer-events: none;
899
+ }
900
+ .mermaid-expandable:hover,
901
+ .mermaid-expandable:focus-visible {
902
+ outline: none;
903
+ background: rgba(91, 157, 255, 0.04);
904
+ box-shadow: 0 0 0 2px rgba(91, 157, 255, 0.28);
905
+ }
906
+ .mermaid-expandable:hover::after,
907
+ .mermaid-expandable:focus-visible::after {
908
+ opacity: 1;
909
+ }
910
+ .diagram-overlay {
911
+ position: fixed;
912
+ inset: 0;
913
+ z-index: 50;
914
+ display: grid;
915
+ place-items: center;
916
+ padding: 24px;
917
+ background: rgba(4, 6, 10, 0.88);
918
+ backdrop-filter: blur(7px);
919
+ }
920
+ .diagram-shell {
921
+ position: relative;
922
+ width: min(1500px, 96vw);
923
+ height: min(900px, 92vh);
924
+ border: 1px solid #353c49;
925
+ border-radius: 16px;
926
+ background: #11141a;
927
+ box-shadow: 0 30px 90px rgba(0, 0, 0, 0.58);
928
+ overflow: hidden;
929
+ }
930
+ .diagram-close {
931
+ position: absolute;
932
+ top: 14px;
933
+ right: 14px;
934
+ z-index: 2;
935
+ }
936
+ .diagram-canvas {
937
+ width: 100%;
938
+ height: 100%;
939
+ padding: 58px 30px 30px;
940
+ overflow: auto;
941
+ }
942
+ .diagram-canvas svg {
943
+ display: block;
944
+ width: max-content;
945
+ min-width: 100%;
946
+ height: auto;
947
+ min-height: 100%;
948
+ margin: auto;
949
+ }
950
+ .node rect {
951
+ fill: var(--panel-2);
952
+ stroke: var(--line);
953
+ rx: 8;
954
+ cursor: pointer;
955
+ }
956
+ .node:hover rect {
957
+ stroke: var(--accent);
958
+ }
959
+ .node text {
960
+ fill: var(--text);
961
+ font-size: 12px;
962
+ }
963
+ .node .nid {
964
+ fill: var(--muted);
965
+ font-family: ui-monospace, monospace;
966
+ font-size: 10px;
967
+ }
968
+ .edge {
969
+ stroke: var(--muted);
970
+ stroke-width: 1.5;
971
+ fill: none;
972
+ marker-end: url(#arrow);
973
+ }
974
+
975
+ /* Specs view */
976
+ .specs-view {
977
+ padding: 16px;
978
+ display: flex;
979
+ flex-direction: column;
980
+ gap: 10px;
981
+ max-width: 760px;
982
+ }
983
+ .spec-card {
984
+ background: var(--panel);
985
+ border: 1px solid var(--line);
986
+ border-radius: 10px;
987
+ padding: 13px 15px;
988
+ cursor: pointer;
989
+ }
990
+ .spec-card:hover {
991
+ border-color: var(--accent);
992
+ }
993
+ .spec-title {
994
+ font-weight: 600;
995
+ margin-bottom: 6px;
996
+ }
997
+ .empty {
998
+ color: var(--muted);
999
+ padding: 8px;
1000
+ }
1001
+
1002
+ @media (max-width: 1280px) {
1003
+ .topbar {
1004
+ flex-wrap: wrap;
1005
+ }
1006
+ .spacer {
1007
+ display: none;
1008
+ }
1009
+ .tabs {
1010
+ margin-left: auto;
1011
+ }
1012
+ }
1013
+
1014
+ /* Responsive: stack on narrow viewports (mobile) */
1015
+ @media (max-width: 680px) {
1016
+ .topbar {
1017
+ flex-wrap: wrap;
1018
+ }
1019
+ .search {
1020
+ width: 100%;
1021
+ order: 1;
1022
+ }
1023
+ .spacer {
1024
+ display: none;
1025
+ }
1026
+ .board {
1027
+ flex-direction: column;
1028
+ overflow-x: visible;
1029
+ }
1030
+ .column {
1031
+ max-width: none;
1032
+ min-width: 0;
1033
+ width: 100%;
1034
+ flex: 0 0 auto;
1035
+ }
1036
+ .validation-controls {
1037
+ grid-template-columns: 1fr;
1038
+ align-items: stretch;
1039
+ }
1040
+ .filter-popover {
1041
+ position: fixed;
1042
+ top: 58px;
1043
+ left: 12px;
1044
+ right: 12px;
1045
+ width: auto;
1046
+ }
1047
+ .diagram-overlay {
1048
+ padding: 8px;
1049
+ }
1050
+ .diagram-shell {
1051
+ width: 100%;
1052
+ height: 96vh;
1053
+ }
1054
+ .detail {
1055
+ width: 100vw;
1056
+ padding: 18px 16px 60px;
1057
+ }
1058
+ .grid th,
1059
+ .grid td {
1060
+ padding: 8px 8px;
1061
+ }
1062
+ }