@zigrivers/scaffold 3.29.0 → 3.31.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 (86) hide show
  1. package/content/guides/AUTHORING.md +146 -0
  2. package/content/guides/cli/index.html +1855 -0
  3. package/content/guides/cli/index.md +206 -0
  4. package/content/guides/concepts/index.html +1970 -0
  5. package/content/guides/concepts/index.md +347 -0
  6. package/content/guides/dashboard/index.html +1913 -0
  7. package/content/guides/dashboard/index.md +264 -0
  8. package/content/guides/index.html +368 -15
  9. package/content/guides/install/.diagrams/diagram-0.svg +1 -0
  10. package/content/guides/install/.diagrams/manifest.json +3 -0
  11. package/content/guides/install/index.html +1653 -0
  12. package/content/guides/install/index.md +186 -0
  13. package/content/guides/knowledge/.diagrams/diagram-0.svg +1 -0
  14. package/content/guides/knowledge/.diagrams/manifest.json +3 -0
  15. package/content/guides/knowledge/index.html +1765 -0
  16. package/content/guides/knowledge/index.md +209 -0
  17. package/content/guides/knowledge-freshness/.diagrams/diagram-0.svg +1 -0
  18. package/content/guides/knowledge-freshness/.diagrams/manifest.json +3 -0
  19. package/content/guides/knowledge-freshness/index.html +2795 -0
  20. package/content/guides/knowledge-freshness/index.md +893 -0
  21. package/content/guides/mmr/index.html +407 -36
  22. package/content/guides/mmr/index.md +39 -16
  23. package/content/guides/multi-agent/.diagrams/diagram-0.svg +1 -0
  24. package/content/guides/multi-agent/.diagrams/manifest.json +3 -0
  25. package/content/guides/multi-agent/index.html +1715 -0
  26. package/content/guides/multi-agent/index.md +243 -0
  27. package/content/guides/observability/.diagrams/diagram-0.svg +1 -0
  28. package/content/guides/observability/.diagrams/diagram-1.svg +1 -0
  29. package/content/guides/observability/.diagrams/diagram-2.svg +1 -0
  30. package/content/guides/observability/.diagrams/diagram-3.svg +1 -0
  31. package/content/guides/observability/.diagrams/manifest.json +6 -0
  32. package/content/guides/observability/index.html +3257 -0
  33. package/content/guides/observability/index.md +1097 -0
  34. package/content/guides/pipeline/.diagrams/diagram-0.svg +1 -0
  35. package/content/guides/pipeline/.diagrams/diagram-1.svg +1 -0
  36. package/content/guides/pipeline/.diagrams/manifest.json +4 -0
  37. package/content/guides/pipeline/index.html +1973 -0
  38. package/content/guides/pipeline/index.md +387 -0
  39. package/content/guides/review-workflow/.diagrams/diagram-0.svg +1 -0
  40. package/content/guides/review-workflow/.diagrams/diagram-1.svg +1 -0
  41. package/content/guides/review-workflow/.diagrams/manifest.json +4 -0
  42. package/content/guides/review-workflow/index.html +1790 -0
  43. package/content/guides/review-workflow/index.md +248 -0
  44. package/dist/guides/build.d.ts +1 -1
  45. package/dist/guides/build.d.ts.map +1 -1
  46. package/dist/guides/build.js +21 -9
  47. package/dist/guides/build.js.map +1 -1
  48. package/dist/guides/build.test.js +47 -0
  49. package/dist/guides/build.test.js.map +1 -1
  50. package/dist/guides/chrome.d.ts.map +1 -1
  51. package/dist/guides/chrome.js +83 -12
  52. package/dist/guides/chrome.js.map +1 -1
  53. package/dist/guides/dashboard-theme.css +8 -0
  54. package/dist/guides/directives-cite.test.d.ts +2 -0
  55. package/dist/guides/directives-cite.test.d.ts.map +1 -0
  56. package/dist/guides/directives-cite.test.js +26 -0
  57. package/dist/guides/directives-cite.test.js.map +1 -0
  58. package/dist/guides/directives-tabs.test.js +47 -0
  59. package/dist/guides/directives-tabs.test.js.map +1 -1
  60. package/dist/guides/directives.d.ts +1 -0
  61. package/dist/guides/directives.d.ts.map +1 -1
  62. package/dist/guides/directives.js +38 -0
  63. package/dist/guides/directives.js.map +1 -1
  64. package/dist/guides/guides.css +268 -0
  65. package/dist/guides/index-page.d.ts.map +1 -1
  66. package/dist/guides/index-page.js +41 -8
  67. package/dist/guides/index-page.js.map +1 -1
  68. package/dist/guides/links.d.ts +14 -0
  69. package/dist/guides/links.d.ts.map +1 -0
  70. package/dist/guides/links.js +56 -0
  71. package/dist/guides/links.js.map +1 -0
  72. package/dist/guides/links.test.d.ts +2 -0
  73. package/dist/guides/links.test.d.ts.map +1 -0
  74. package/dist/guides/links.test.js +72 -0
  75. package/dist/guides/links.test.js.map +1 -0
  76. package/dist/guides/render.d.ts +1 -0
  77. package/dist/guides/render.d.ts.map +1 -1
  78. package/dist/guides/render.js +1 -1
  79. package/dist/guides/render.js.map +1 -1
  80. package/dist/guides/sanitize.d.ts.map +1 -1
  81. package/dist/guides/sanitize.js +5 -0
  82. package/dist/guides/sanitize.js.map +1 -1
  83. package/dist/guides/template.d.ts.map +1 -1
  84. package/dist/guides/template.js +7 -2
  85. package/dist/guides/template.js.map +1 -1
  86. package/package.json +2 -2
@@ -0,0 +1,1913 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-chrome-version="1">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Dashboard &amp; Design System</title>
7
+ <!-- scaffold:chrome v1 -->
8
+ <style>/* Scaffold Dashboard Theme
9
+ * All CSS for the generated pipeline dashboard.
10
+ * Embedded into HTML by scripts/generate-dashboard.sh.
11
+ * Design system reference: docs/design-system.md
12
+ *
13
+ * Aesthetic: "Precision Industrial" — Swiss-typographic control room.
14
+ * Deep navy dark mode with indigo accents, clean cool-white light mode.
15
+ */
16
+
17
+ /* ─── Design Tokens (Light Mode) ──────────────── */
18
+ :root {
19
+ /* Surface */
20
+ --bg: #f5f6fa;
21
+ --bg-card: #ffffff;
22
+ --bg-hover: #eef0f6;
23
+ --bg-inset: #e8eaf2;
24
+
25
+ /* Text */
26
+ --text: #1a1d2e;
27
+ --text-muted: #6b7294;
28
+ --text-faint: #9ba1c0;
29
+
30
+ /* Borders & Structure */
31
+ --border: #dde0ed;
32
+ --border-light: #eceef5;
33
+ --radius: 10px;
34
+ --radius-sm: 6px;
35
+
36
+ /* Accent */
37
+ --accent: #4f46e5;
38
+ --accent-hover: #4338ca;
39
+ --accent-glow: rgba(79, 70, 229, 0.10);
40
+
41
+ /* Semantic: Status */
42
+ --green: #059669;
43
+ --green-bg: #ecfdf5;
44
+ --green-border: #a7f3d0;
45
+ --blue: #2563eb;
46
+ --blue-bg: #eff6ff;
47
+ --blue-border: #bfdbfe;
48
+ --yellow: #d97706;
49
+ --yellow-bg: #fffbeb;
50
+ --yellow-border:#fde68a;
51
+ --red: #dc2626;
52
+ --red-bg: #fef2f2;
53
+ --red-border: #fecaca;
54
+ --gray: #9ca3af;
55
+ --gray-bg: #f3f4f6;
56
+ --gray-border: #e5e7eb;
57
+ --scrim: rgba(15, 17, 23, 0.45);
58
+
59
+ /* Semantic: Next Banner */
60
+ --next-bg: #eef2ff;
61
+ --next-border: #4f46e5;
62
+ --next-glow: rgba(79, 70, 229, 0.06);
63
+
64
+ /* Semantic: Progress */
65
+ --progress-bg: #e5e7eb;
66
+ --progress-h: 10px;
67
+
68
+ /* Depth */
69
+ --shadow-sm: 0 1px 2px rgba(30, 34, 60, 0.04);
70
+ --shadow: 0 1px 3px rgba(30, 34, 60, 0.07), 0 1px 2px rgba(30, 34, 60, 0.04);
71
+ --shadow-md: 0 4px 12px rgba(30, 34, 60, 0.08), 0 1px 3px rgba(30, 34, 60, 0.05);
72
+ --shadow-lg: 0 8px 24px rgba(30, 34, 60, 0.10), 0 2px 6px rgba(30, 34, 60, 0.04);
73
+
74
+ /* Spacing scale (4px base) */
75
+ --sp-1: 4px;
76
+ --sp-2: 8px;
77
+ --sp-3: 12px;
78
+ --sp-4: 16px;
79
+ --sp-5: 20px;
80
+ --sp-6: 24px;
81
+ --sp-8: 32px;
82
+ --sp-10: 40px;
83
+
84
+ /* Typography */
85
+ --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
86
+ --font-mono: "SF Mono", "Cascadia Code", "Fira Code", "JetBrains Mono", Menlo, Consolas, monospace;
87
+ --text-xs: 0.75rem;
88
+ --text-sm: 0.8125rem;
89
+ --text-base: 0.9375rem;
90
+ --text-lg: 1.125rem;
91
+ --text-xl: 1.375rem;
92
+ --text-2xl: 1.75rem;
93
+ --lh-tight: 1.25;
94
+ --lh-normal: 1.5;
95
+ --lh-relaxed: 1.625;
96
+ --ls-tight: -0.01em;
97
+ --ls-wide: 0.025em;
98
+ --fw-normal: 400;
99
+ --fw-medium: 500;
100
+ --fw-semi: 600;
101
+ --fw-bold: 700;
102
+
103
+ /* Layout */
104
+ --max-w: 960px;
105
+ --page-pad: 24px;
106
+ }
107
+
108
+ /* ─── Design Tokens (Dark Mode) ───────────────── */
109
+ [data-theme="dark"] {
110
+ /* Surface */
111
+ --bg: #0f1117;
112
+ --bg-card: #1a1d2e;
113
+ --bg-hover: #252940;
114
+ --bg-inset: #141724;
115
+
116
+ /* Text */
117
+ --text: #e2e5f0;
118
+ --text-muted: #7c82a8;
119
+ --text-faint: #555c80;
120
+
121
+ /* Borders & Structure */
122
+ --border: #2a2f45;
123
+ --border-light: #21253a;
124
+
125
+ /* Accent */
126
+ --accent: #818cf8;
127
+ --accent-hover: #a5b4fc;
128
+ --accent-glow: rgba(129, 140, 248, 0.12);
129
+
130
+ /* Semantic: Status */
131
+ --green: #34d399;
132
+ --green-bg: rgba(6, 78, 59, 0.25);
133
+ --green-border: rgba(52, 211, 153, 0.25);
134
+ --blue: #60a5fa;
135
+ --blue-bg: rgba(30, 58, 95, 0.30);
136
+ --blue-border: rgba(96, 165, 250, 0.25);
137
+ --yellow: #fbbf24;
138
+ --yellow-bg: rgba(120, 53, 15, 0.25);
139
+ --yellow-border:rgba(251, 191, 36, 0.20);
140
+ --red: #f87171;
141
+ --red-bg: rgba(127, 29, 29, 0.25);
142
+ --red-border: rgba(248, 113, 113, 0.22);
143
+ --gray: #6b7294;
144
+ --gray-bg: #252940;
145
+ --gray-border: #363c58;
146
+ --scrim: rgba(0, 0, 0, 0.6);
147
+
148
+ /* Semantic: Next Banner */
149
+ --next-bg: rgba(30, 27, 75, 0.50);
150
+ --next-border: #818cf8;
151
+ --next-glow: rgba(129, 140, 248, 0.08);
152
+
153
+ /* Semantic: Progress */
154
+ --progress-bg: #1f2337;
155
+
156
+ /* Depth */
157
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.20);
158
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.30), 0 1px 2px rgba(0, 0, 0, 0.15);
159
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.35), 0 1px 3px rgba(0, 0, 0, 0.20);
160
+ --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.40), 0 2px 6px rgba(0, 0, 0, 0.20);
161
+ }
162
+
163
+ /* ─── Theme Toggle ───────────────────────────── */
164
+ .theme-toggle {
165
+ background: var(--bg-inset);
166
+ border: 1px solid var(--border);
167
+ border-radius: var(--radius-sm);
168
+ padding: var(--sp-1) var(--sp-2);
169
+ cursor: pointer;
170
+ font-size: var(--text-base);
171
+ line-height: 1;
172
+ color: var(--text-muted);
173
+ transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
174
+ display: flex;
175
+ align-items: center;
176
+ margin-left: auto;
177
+ }
178
+
179
+ .theme-toggle:hover {
180
+ border-color: var(--accent);
181
+ color: var(--accent);
182
+ background: var(--accent-glow);
183
+ }
184
+
185
+ /* ─── Base ────────────────────────────────────── */
186
+ *, *::before, *::after {
187
+ margin: 0;
188
+ padding: 0;
189
+ box-sizing: border-box;
190
+ }
191
+
192
+ body {
193
+ font-family: var(--font-sans);
194
+ font-size: var(--text-base);
195
+ line-height: var(--lh-normal);
196
+ color: var(--text);
197
+ background: var(--bg);
198
+ -webkit-font-smoothing: antialiased;
199
+ -moz-osx-font-smoothing: grayscale;
200
+ }
201
+
202
+ /* ─── Layout ──────────────────────────────────── */
203
+ .wrap {
204
+ max-width: var(--max-w);
205
+ margin: 0 auto;
206
+ padding: var(--sp-8) var(--page-pad);
207
+ }
208
+
209
+ /* ─── Header ──────────────────────────────────── */
210
+ .header {
211
+ display: flex;
212
+ align-items: baseline;
213
+ gap: var(--sp-3);
214
+ margin-bottom: var(--sp-2);
215
+ flex-wrap: wrap;
216
+ }
217
+
218
+ h1 {
219
+ font-size: var(--text-2xl);
220
+ font-weight: var(--fw-bold);
221
+ letter-spacing: var(--ls-tight);
222
+ line-height: var(--lh-tight);
223
+ }
224
+
225
+ h2 {
226
+ font-size: var(--text-lg);
227
+ font-weight: var(--fw-semi);
228
+ letter-spacing: var(--ls-tight);
229
+ line-height: var(--lh-tight);
230
+ margin-bottom: var(--sp-3);
231
+ }
232
+
233
+ .header-meta {
234
+ font-size: var(--text-xs);
235
+ color: var(--text-faint);
236
+ margin-bottom: var(--sp-6);
237
+ letter-spacing: var(--ls-wide);
238
+ text-transform: uppercase;
239
+ }
240
+
241
+ /* ─── Badge ───────────────────────────────────── */
242
+ .badge {
243
+ display: inline-block;
244
+ padding: 2px var(--sp-2);
245
+ border-radius: 99px;
246
+ font-size: var(--text-xs);
247
+ font-weight: var(--fw-semi);
248
+ letter-spacing: var(--ls-wide);
249
+ background: var(--accent);
250
+ color: #fff;
251
+ text-transform: uppercase;
252
+ }
253
+
254
+ .badge-optional {
255
+ background: var(--yellow-bg);
256
+ color: var(--yellow);
257
+ border: 1px solid var(--yellow-border);
258
+ }
259
+
260
+ /* ─── Progress Bar ────────────────────────────── */
261
+ .progress-bar {
262
+ width: 100%;
263
+ height: var(--progress-h);
264
+ background: var(--progress-bg);
265
+ border-radius: 99px;
266
+ overflow: hidden;
267
+ margin-bottom: var(--sp-6);
268
+ display: flex;
269
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);
270
+ }
271
+
272
+ .progress-bar .seg-done {
273
+ background: linear-gradient(135deg, var(--green), #10b981);
274
+ box-shadow: 0 0 8px rgba(5, 150, 105, 0.3);
275
+ }
276
+
277
+ .progress-bar .seg-likely {
278
+ background: linear-gradient(135deg, var(--blue), #3b82f6);
279
+ box-shadow: 0 0 8px rgba(37, 99, 235, 0.25);
280
+ }
281
+
282
+ .progress-bar .seg-skip {
283
+ background: var(--gray);
284
+ opacity: 0.7;
285
+ }
286
+
287
+ /* ─── Summary Cards ───────────────────────────── */
288
+ .cards {
289
+ display: grid;
290
+ grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
291
+ gap: var(--sp-3);
292
+ margin-bottom: var(--sp-6);
293
+ }
294
+
295
+ .card {
296
+ background: var(--bg-card);
297
+ border: 1px solid var(--border);
298
+ border-radius: var(--radius);
299
+ padding: var(--sp-4) var(--sp-5);
300
+ box-shadow: var(--shadow);
301
+ transition: box-shadow 0.15s ease, transform 0.15s ease;
302
+ }
303
+
304
+ .card:hover {
305
+ box-shadow: var(--shadow-md);
306
+ transform: translateY(-1px);
307
+ }
308
+
309
+ .card-num {
310
+ font-size: var(--text-2xl);
311
+ font-weight: var(--fw-bold);
312
+ font-family: var(--font-mono);
313
+ letter-spacing: var(--ls-tight);
314
+ line-height: 1;
315
+ }
316
+
317
+ .card-lbl {
318
+ font-size: var(--text-xs);
319
+ color: var(--text-muted);
320
+ margin-top: var(--sp-1);
321
+ letter-spacing: var(--ls-wide);
322
+ text-transform: uppercase;
323
+ font-weight: var(--fw-medium);
324
+ }
325
+
326
+ /* ─── What's Next Banner ──────────────────────── */
327
+ .next-banner {
328
+ background: var(--next-bg);
329
+ border: 1px solid var(--next-border);
330
+ border-left: 4px solid var(--next-border);
331
+ border-radius: var(--radius);
332
+ padding: var(--sp-5) var(--sp-6);
333
+ margin-bottom: var(--sp-6);
334
+ box-shadow: 0 0 0 1px var(--next-glow), var(--shadow);
335
+ position: relative;
336
+ overflow: hidden;
337
+ }
338
+
339
+ .next-banner::before {
340
+ content: "";
341
+ position: absolute;
342
+ top: 0;
343
+ left: 0;
344
+ width: 4px;
345
+ height: 100%;
346
+ background: var(--next-border);
347
+ animation: pulse-border 2.5s ease-in-out infinite;
348
+ }
349
+
350
+ @keyframes pulse-border {
351
+ 0%, 100% { opacity: 1; }
352
+ 50% { opacity: 0.5; }
353
+ }
354
+
355
+ .next-banner h2 {
356
+ color: var(--accent);
357
+ margin-bottom: var(--sp-1);
358
+ font-size: var(--text-base);
359
+ font-weight: var(--fw-semi);
360
+ letter-spacing: var(--ls-wide);
361
+ text-transform: uppercase;
362
+ }
363
+
364
+ .next-banner p {
365
+ color: var(--text);
366
+ font-size: var(--text-base);
367
+ }
368
+
369
+ .next-cmd {
370
+ font-family: var(--font-mono);
371
+ background: var(--bg-card);
372
+ padding: var(--sp-1) var(--sp-3);
373
+ border-radius: var(--radius-sm);
374
+ font-size: var(--text-sm);
375
+ display: inline-flex;
376
+ align-items: center;
377
+ gap: var(--sp-2);
378
+ margin-top: var(--sp-3);
379
+ border: 1px solid var(--border);
380
+ }
381
+
382
+ /* ─── Phase Headers (Collapsible) ─────────────── */
383
+ .phase {
384
+ margin-bottom: var(--sp-6);
385
+ }
386
+
387
+ .phase-hdr {
388
+ display: flex;
389
+ align-items: center;
390
+ gap: var(--sp-2);
391
+ cursor: pointer;
392
+ padding: var(--sp-2) 0;
393
+ user-select: none;
394
+ border-bottom: 2px solid var(--border);
395
+ margin-bottom: var(--sp-3);
396
+ transition: border-color 0.15s ease;
397
+ }
398
+
399
+ .phase-hdr:hover {
400
+ border-bottom-color: var(--accent);
401
+ }
402
+
403
+ .phase-hdr:hover h2 {
404
+ color: var(--accent);
405
+ }
406
+
407
+ .phase-hdr h2 {
408
+ transition: color 0.15s ease;
409
+ }
410
+
411
+ .phase-hdr .arr {
412
+ transition: transform 0.2s ease;
413
+ font-size: var(--text-xs);
414
+ color: var(--text-muted);
415
+ }
416
+
417
+ .phase-hdr.closed .arr {
418
+ transform: rotate(-90deg);
419
+ }
420
+
421
+ .phase-cnt {
422
+ font-size: var(--text-xs);
423
+ font-family: var(--font-mono);
424
+ color: var(--text-faint);
425
+ margin-left: auto;
426
+ letter-spacing: var(--ls-wide);
427
+ }
428
+
429
+ /* ─── Prompt List ─────────────────────────────── */
430
+ .plist {
431
+ display: flex;
432
+ flex-direction: column;
433
+ gap: var(--sp-2);
434
+ }
435
+
436
+ /* ─── Prompt Cards ────────────────────────────── */
437
+ .pcard {
438
+ background: var(--bg-card);
439
+ border: 1px solid var(--border);
440
+ border-radius: var(--radius);
441
+ padding: var(--sp-3) var(--sp-4);
442
+ box-shadow: var(--shadow-sm);
443
+ display: grid;
444
+ grid-template-columns: auto 1fr auto;
445
+ gap: var(--sp-2) var(--sp-3);
446
+ align-items: start;
447
+ transition: box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
448
+ }
449
+
450
+ .pcard:hover {
451
+ box-shadow: var(--shadow);
452
+ transform: translateY(-1px);
453
+ border-color: var(--accent-glow);
454
+ }
455
+
456
+ /* ─── Status Badges ──────────────────────────── */
457
+ .status-badge {
458
+ display: inline-flex;
459
+ align-items: center;
460
+ gap: var(--sp-1);
461
+ font-size: var(--text-xs);
462
+ font-weight: var(--fw-medium);
463
+ padding: 2px var(--sp-2);
464
+ border-radius: 99px;
465
+ white-space: nowrap;
466
+ flex-shrink: 0;
467
+ letter-spacing: var(--ls-wide);
468
+ line-height: var(--lh-tight);
469
+ }
470
+
471
+ .st-completed {
472
+ background: var(--green-bg);
473
+ color: var(--green);
474
+ border: 1px solid var(--green-border);
475
+ }
476
+
477
+ .st-likely-completed {
478
+ background: var(--blue-bg);
479
+ color: var(--blue);
480
+ border: 1px solid var(--blue-border);
481
+ }
482
+
483
+ .st-skipped {
484
+ background: var(--gray-bg);
485
+ color: var(--gray);
486
+ border: 1px solid var(--gray-border);
487
+ }
488
+
489
+ .st-pending {
490
+ background: var(--bg-inset);
491
+ color: var(--text-faint);
492
+ border: 1px solid var(--border);
493
+ }
494
+
495
+ /* ─── Status Legend ──────────────────────────── */
496
+ .status-legend {
497
+ display: flex;
498
+ flex-wrap: wrap;
499
+ gap: var(--sp-2);
500
+ margin-bottom: var(--sp-4);
501
+ padding: var(--sp-2) 0;
502
+ }
503
+
504
+ .status-legend .status-badge {
505
+ cursor: default;
506
+ }
507
+
508
+ /* ─── Prompt Card Inner ───────────────────────── */
509
+ .pinfo {
510
+ min-width: 0;
511
+ }
512
+
513
+ .pname {
514
+ font-weight: var(--fw-semi);
515
+ font-size: var(--text-base);
516
+ }
517
+
518
+ .pstep {
519
+ font-size: var(--text-xs);
520
+ font-family: var(--font-mono);
521
+ color: var(--text-faint);
522
+ letter-spacing: var(--ls-wide);
523
+ }
524
+
525
+ .pdesc {
526
+ font-size: var(--text-sm);
527
+ color: var(--text-muted);
528
+ margin-top: 2px;
529
+ line-height: var(--lh-relaxed);
530
+ }
531
+
532
+ .pdesc-long {
533
+ font-size: var(--text-xs);
534
+ color: var(--text-faint);
535
+ margin-top: 2px;
536
+ }
537
+
538
+ .pdeps {
539
+ font-size: var(--text-xs);
540
+ color: var(--yellow);
541
+ margin-top: var(--sp-1);
542
+ font-weight: var(--fw-medium);
543
+ }
544
+
545
+ /* ─── Copy Command Button ─────────────────────── */
546
+ .pcmd {
547
+ font-family: var(--font-mono);
548
+ font-size: var(--text-xs);
549
+ background: var(--bg-inset);
550
+ padding: 3px var(--sp-2);
551
+ border-radius: var(--radius-sm);
552
+ cursor: pointer;
553
+ border: 1px solid var(--border);
554
+ white-space: nowrap;
555
+ align-self: center;
556
+ color: var(--text-muted);
557
+ transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
558
+ letter-spacing: var(--ls-wide);
559
+ }
560
+
561
+ .pcmd:hover {
562
+ border-color: var(--accent);
563
+ color: var(--accent);
564
+ background: var(--accent-glow);
565
+ }
566
+
567
+ .pcmd.copied {
568
+ border-color: var(--green);
569
+ color: var(--green);
570
+ background: var(--green-bg);
571
+ }
572
+
573
+ /* ─── Prompt Modal ────────────────────────────── */
574
+ .modal-overlay {
575
+ position: fixed;
576
+ inset: 0;
577
+ background: rgba(0, 0, 0, 0.6);
578
+ display: flex;
579
+ align-items: center;
580
+ justify-content: center;
581
+ z-index: 1000;
582
+ padding: var(--sp-4);
583
+ }
584
+
585
+ .modal {
586
+ background: var(--bg-card);
587
+ border: 1px solid var(--border);
588
+ border-radius: var(--radius);
589
+ box-shadow: var(--shadow-lg);
590
+ max-width: 720px;
591
+ width: 100%;
592
+ max-height: 85vh;
593
+ display: flex;
594
+ flex-direction: column;
595
+ }
596
+
597
+ .modal-header {
598
+ display: flex;
599
+ align-items: center;
600
+ gap: var(--sp-3);
601
+ padding: var(--sp-4) var(--sp-5);
602
+ border-bottom: 1px solid var(--border);
603
+ flex-shrink: 0;
604
+ }
605
+
606
+ .modal-header h3 {
607
+ font-size: var(--text-lg);
608
+ font-weight: var(--fw-semi);
609
+ flex: 1;
610
+ min-width: 0;
611
+ }
612
+
613
+ .modal-close {
614
+ background: var(--bg-inset);
615
+ border: 1px solid var(--border);
616
+ border-radius: var(--radius-sm);
617
+ padding: var(--sp-1) var(--sp-2);
618
+ cursor: pointer;
619
+ font-size: var(--text-base);
620
+ color: var(--text-muted);
621
+ line-height: 1;
622
+ transition: border-color 0.15s ease, color 0.15s ease;
623
+ }
624
+
625
+ .modal-close:hover {
626
+ border-color: var(--accent);
627
+ color: var(--accent);
628
+ }
629
+
630
+ .modal-body {
631
+ padding: var(--sp-5);
632
+ overflow-y: auto;
633
+ flex: 1;
634
+ }
635
+
636
+ .modal-body pre {
637
+ font-family: var(--font-mono);
638
+ font-size: var(--text-sm);
639
+ line-height: var(--lh-relaxed);
640
+ white-space: pre-wrap;
641
+ word-break: break-word;
642
+ color: var(--text);
643
+ }
644
+
645
+ .modal-body pre .md-heading {
646
+ font-weight: var(--fw-bold);
647
+ color: var(--accent);
648
+ }
649
+
650
+ .modal-body pre .md-code {
651
+ background: var(--bg-inset);
652
+ padding: 1px 4px;
653
+ border-radius: 3px;
654
+ font-size: var(--text-xs);
655
+ }
656
+
657
+ .modal-footer {
658
+ display: flex;
659
+ gap: var(--sp-2);
660
+ padding: var(--sp-3) var(--sp-5);
661
+ border-top: 1px solid var(--border);
662
+ flex-shrink: 0;
663
+ }
664
+
665
+ .modal-copy-btn {
666
+ background: var(--accent);
667
+ color: #fff;
668
+ border: none;
669
+ border-radius: var(--radius-sm);
670
+ padding: var(--sp-2) var(--sp-4);
671
+ font-size: var(--text-sm);
672
+ font-weight: var(--fw-medium);
673
+ cursor: pointer;
674
+ transition: background 0.15s ease;
675
+ }
676
+
677
+ .modal-copy-btn:hover {
678
+ background: var(--accent-hover);
679
+ }
680
+
681
+ .modal-copy-btn.copied {
682
+ background: var(--green);
683
+ }
684
+
685
+ /* ─── Beads Task Section ─────────────────────── */
686
+ .beads-section {
687
+ margin-top: var(--sp-8);
688
+ margin-bottom: var(--sp-6);
689
+ }
690
+
691
+ .beads-filters {
692
+ display: flex;
693
+ gap: var(--sp-2);
694
+ margin-bottom: var(--sp-3);
695
+ }
696
+
697
+ .beads-filter {
698
+ background: var(--bg-inset);
699
+ border: 1px solid var(--border);
700
+ border-radius: 99px;
701
+ padding: var(--sp-1) var(--sp-3);
702
+ font-size: var(--text-xs);
703
+ font-weight: var(--fw-medium);
704
+ color: var(--text-muted);
705
+ cursor: pointer;
706
+ transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
707
+ letter-spacing: var(--ls-wide);
708
+ }
709
+
710
+ .beads-filter:hover {
711
+ border-color: var(--accent);
712
+ color: var(--accent);
713
+ }
714
+
715
+ .beads-filter.active {
716
+ background: var(--accent);
717
+ color: #fff;
718
+ border-color: var(--accent);
719
+ }
720
+
721
+ /* ─── Beads Status Badges ────────────────────── */
722
+ .st-bead-open {
723
+ background: var(--accent-glow);
724
+ color: var(--accent);
725
+ border: 1px solid var(--accent);
726
+ }
727
+
728
+ .st-bead-progress {
729
+ background: var(--blue-bg);
730
+ color: var(--blue);
731
+ border: 1px solid var(--blue-border);
732
+ }
733
+
734
+ .st-bead-blocked {
735
+ background: var(--yellow-bg);
736
+ color: var(--yellow);
737
+ border: 1px solid var(--yellow-border);
738
+ }
739
+
740
+ .st-bead-deferred {
741
+ background: var(--gray-bg);
742
+ color: var(--gray);
743
+ border: 1px solid var(--gray-border);
744
+ }
745
+
746
+ .st-bead-closed {
747
+ background: var(--green-bg);
748
+ color: var(--green);
749
+ border: 1px solid var(--green-border);
750
+ }
751
+
752
+ /* ─── Beads Filter Separator ─────────────────── */
753
+ .beads-filter-sep {
754
+ width: 1px;
755
+ background: var(--border);
756
+ align-self: stretch;
757
+ margin: 0 var(--sp-1);
758
+ }
759
+
760
+ /* ─── Beads Priority Filter ──────────────────── */
761
+ .beads-prio-filter {
762
+ background: var(--bg-inset);
763
+ border: 1px solid var(--border);
764
+ border-radius: 99px;
765
+ padding: var(--sp-1) var(--sp-3);
766
+ font-size: var(--text-xs);
767
+ font-weight: var(--fw-medium);
768
+ color: var(--text-muted);
769
+ cursor: pointer;
770
+ transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
771
+ letter-spacing: var(--ls-wide);
772
+ }
773
+
774
+ .beads-prio-filter:hover {
775
+ border-color: var(--accent);
776
+ color: var(--accent);
777
+ }
778
+
779
+ .beads-prio-filter.active {
780
+ background: var(--accent);
781
+ color: #fff;
782
+ border-color: var(--accent);
783
+ }
784
+
785
+ /* ─── Beads Task Detail Modal ────────────────── */
786
+ .bead-meta-grid {
787
+ display: grid;
788
+ grid-template-columns: 1fr 1fr;
789
+ gap: var(--sp-3);
790
+ padding: var(--sp-4) 0;
791
+ border-bottom: 1px solid var(--border-light);
792
+ }
793
+
794
+ .bead-meta-item {
795
+ display: flex;
796
+ flex-direction: column;
797
+ gap: 2px;
798
+ }
799
+
800
+ .bead-meta-label {
801
+ font-size: var(--text-xs);
802
+ color: var(--text-faint);
803
+ text-transform: uppercase;
804
+ letter-spacing: var(--ls-wide);
805
+ font-weight: var(--fw-medium);
806
+ }
807
+
808
+ .bead-meta-value {
809
+ font-size: var(--text-sm);
810
+ font-weight: var(--fw-medium);
811
+ color: var(--text);
812
+ }
813
+
814
+ .bead-description {
815
+ padding: var(--sp-4) 0;
816
+ border-bottom: 1px solid var(--border-light);
817
+ white-space: pre-wrap;
818
+ font-size: var(--text-sm);
819
+ line-height: var(--lh-relaxed);
820
+ color: var(--text-muted);
821
+ }
822
+
823
+ .bead-deps {
824
+ padding: var(--sp-4) 0;
825
+ border-bottom: 1px solid var(--border-light);
826
+ }
827
+
828
+ .bead-dep-group {
829
+ display: flex;
830
+ flex-wrap: wrap;
831
+ align-items: center;
832
+ gap: var(--sp-2);
833
+ margin-bottom: var(--sp-2);
834
+ }
835
+
836
+ .bead-dep-group:last-child {
837
+ margin-bottom: 0;
838
+ }
839
+
840
+ .bead-dep-label {
841
+ font-size: var(--text-xs);
842
+ color: var(--text-faint);
843
+ text-transform: uppercase;
844
+ letter-spacing: var(--ls-wide);
845
+ font-weight: var(--fw-medium);
846
+ min-width: 80px;
847
+ }
848
+
849
+ .bead-dep-link {
850
+ display: inline-block;
851
+ font-family: var(--font-mono);
852
+ font-size: var(--text-xs);
853
+ padding: 2px var(--sp-2);
854
+ border-radius: 99px;
855
+ background: var(--accent-glow);
856
+ color: var(--accent);
857
+ border: 1px solid var(--accent);
858
+ cursor: pointer;
859
+ transition: background 0.15s ease, color 0.15s ease;
860
+ text-decoration: none;
861
+ }
862
+
863
+ .bead-dep-link:hover {
864
+ background: var(--accent);
865
+ color: #fff;
866
+ }
867
+
868
+ .bead-timestamps {
869
+ display: flex;
870
+ flex-wrap: wrap;
871
+ gap: var(--sp-4);
872
+ padding: var(--sp-4) 0;
873
+ }
874
+
875
+ .bead-ts-item {
876
+ display: flex;
877
+ flex-direction: column;
878
+ gap: 2px;
879
+ }
880
+
881
+ .bead-ts-label {
882
+ font-size: var(--text-xs);
883
+ color: var(--text-faint);
884
+ text-transform: uppercase;
885
+ letter-spacing: var(--ls-wide);
886
+ font-weight: var(--fw-medium);
887
+ }
888
+
889
+ .bead-ts-value {
890
+ font-size: var(--text-sm);
891
+ color: var(--text-muted);
892
+ }
893
+
894
+ .bead-ts-value[title] {
895
+ border-bottom: 1px dotted var(--text-faint);
896
+ cursor: help;
897
+ }
898
+
899
+ /* ─── Standalone Commands Section ─────────────── */
900
+ .ongoing {
901
+ margin-top: var(--sp-10);
902
+ }
903
+
904
+ .ongoing h2 {
905
+ letter-spacing: var(--ls-wide);
906
+ text-transform: uppercase;
907
+ font-size: var(--text-sm);
908
+ color: var(--text-muted);
909
+ margin-bottom: var(--sp-4);
910
+ border-bottom: 2px solid var(--border);
911
+ padding-bottom: var(--sp-2);
912
+ }
913
+
914
+ /* ─── Footer ──────────────────────────────────── */
915
+ .footer {
916
+ text-align: center;
917
+ font-size: var(--text-xs);
918
+ color: var(--text-faint);
919
+ margin-top: var(--sp-10);
920
+ padding-top: var(--sp-4);
921
+ border-top: 1px solid var(--border-light);
922
+ letter-spacing: var(--ls-wide);
923
+ }
924
+
925
+ /* ─── Utilities ───────────────────────────────── */
926
+ .hidden {
927
+ display: none;
928
+ }
929
+
930
+ /* Build-observability severity + verdict tokens (Plan 4) */
931
+ :root {
932
+ --sev-p0: #dc2626; /* red 600 */
933
+ --sev-p1: #ea580c; /* orange 600 */
934
+ --sev-p2: #ca8a04; /* yellow 600 */
935
+ --sev-p3: #2563eb; /* blue 600 */
936
+ --sev-pass: #16a34a; /* green 600 */
937
+ }
938
+ [data-theme="dark"] {
939
+ --sev-p0: #f87171;
940
+ --sev-p1: #fb923c;
941
+ --sev-p2: #facc15;
942
+ --sev-p3: #60a5fa;
943
+ --sev-pass: #4ade80;
944
+ }
945
+
946
+ /* Build-observability panel layout */
947
+ .panel {
948
+ background: var(--bg-card);
949
+ border: 1px solid var(--border);
950
+ border-radius: var(--radius);
951
+ padding: var(--sp-4) var(--sp-6);
952
+ margin-bottom: var(--sp-6);
953
+ }
954
+ .panel > header {
955
+ display: flex;
956
+ align-items: center;
957
+ gap: var(--sp-3);
958
+ margin-bottom: var(--sp-4);
959
+ flex-wrap: wrap;
960
+ }
961
+ .panel > header h2 {
962
+ margin: 0;
963
+ font-size: var(--text-base);
964
+ font-weight: var(--fw-semi);
965
+ }
966
+ .panel .meta {
967
+ color: var(--text-muted);
968
+ font-size: var(--text-sm);
969
+ }
970
+ .grid { display: grid; gap: var(--sp-4); }
971
+ .grid-2 { grid-template-columns: repeat(2, 1fr); }
972
+ @media (max-width: 640px) { .grid-2 { grid-template-columns: 1fr; } }
973
+
974
+ /* Finding filters */
975
+ .finding-filters {
976
+ display: flex;
977
+ gap: var(--sp-2);
978
+ flex-wrap: wrap;
979
+ margin-bottom: var(--sp-4);
980
+ }
981
+ .finding-filters button {
982
+ padding: var(--sp-1) var(--sp-3);
983
+ border: 1px solid var(--border);
984
+ border-radius: var(--radius-sm);
985
+ background: var(--bg-inset);
986
+ color: var(--text);
987
+ font-size: var(--text-sm);
988
+ cursor: pointer;
989
+ }
990
+ .finding-filters button:hover,
991
+ .finding-filters button.active {
992
+ background: var(--accent);
993
+ border-color: var(--accent);
994
+ color: #fff;
995
+ }
996
+
997
+ /* Findings list */
998
+ .findings {
999
+ list-style: none;
1000
+ padding: 0;
1001
+ margin: 0;
1002
+ display: flex;
1003
+ flex-direction: column;
1004
+ gap: var(--sp-3);
1005
+ }
1006
+ .finding {
1007
+ background: var(--bg-inset);
1008
+ border: 1px solid var(--border-light);
1009
+ border-radius: var(--radius-sm);
1010
+ padding: var(--sp-3) var(--sp-4);
1011
+ }
1012
+ .finding header {
1013
+ display: flex;
1014
+ align-items: center;
1015
+ gap: var(--sp-2);
1016
+ flex-wrap: wrap;
1017
+ margin-bottom: var(--sp-2);
1018
+ }
1019
+ .finding-id {
1020
+ font-family: var(--font-mono, monospace);
1021
+ font-size: var(--text-xs);
1022
+ color: var(--text-muted);
1023
+ background: var(--bg-card);
1024
+ border: 1px solid var(--border);
1025
+ border-radius: 4px;
1026
+ padding: 1px var(--sp-1);
1027
+ }
1028
+ .finding .lens {
1029
+ font-size: var(--text-xs);
1030
+ color: var(--text-muted);
1031
+ }
1032
+ .finding .title {
1033
+ font-size: var(--text-sm);
1034
+ font-weight: var(--fw-semi);
1035
+ flex: 1;
1036
+ }
1037
+ .finding p { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }
1038
+ .empty { color: var(--text-muted); font-size: var(--text-sm); text-align: center; padding: var(--sp-4); }
1039
+
1040
+ /* ── Mermaid diagrams ─────────────────────────────────────────────────────────
1041
+ The build renders mermaid to inline SVG via mmdc, then sanitizeSvg() +
1042
+ rehype-sanitize strip the SVG's own <script>, <foreignObject>, AND <style>
1043
+ for security. Stripping <style> means the diagram arrives unstyled (nodes
1044
+ default to a black fill). These theme-token rules restyle the SVG so nodes,
1045
+ edges, arrowheads, and labels render correctly — and follow light/dark mode.
1046
+ Authors must render with htmlLabels:false (the generator forces this) so node
1047
+ labels are native <text>/<tspan> rather than stripped <foreignObject> HTML. */
1048
+ figure.mermaid { margin: var(--sp-5) 0; text-align: center; }
1049
+ figure.mermaid svg { max-width: 100%; height: auto; }
1050
+ /* Node shapes */
1051
+ figure.mermaid svg .node rect,
1052
+ figure.mermaid svg .node circle,
1053
+ figure.mermaid svg .node ellipse,
1054
+ figure.mermaid svg .node polygon,
1055
+ figure.mermaid svg .node path {
1056
+ fill: var(--bg-inset);
1057
+ stroke: var(--border);
1058
+ stroke-width: 1px;
1059
+ }
1060
+ /* Background helper rects mermaid emits behind labels */
1061
+ figure.mermaid svg .node .label-container { fill: var(--bg-inset); stroke: var(--border); }
1062
+ figure.mermaid svg rect.background { fill: none; stroke: none; }
1063
+ /* Labels (rendered as <text>/<tspan> when htmlLabels:false) */
1064
+ figure.mermaid svg .nodeLabel,
1065
+ figure.mermaid svg .node text,
1066
+ figure.mermaid svg text.nodeLabel,
1067
+ figure.mermaid svg .label text,
1068
+ figure.mermaid svg span.nodeLabel {
1069
+ fill: var(--text);
1070
+ color: var(--text);
1071
+ font-family: var(--font-sans);
1072
+ }
1073
+ /* Edges: thin strokes, not filled blobs */
1074
+ figure.mermaid svg .edgePath path,
1075
+ figure.mermaid svg path.flowchart-link,
1076
+ figure.mermaid svg .flowchart-link {
1077
+ fill: none;
1078
+ stroke: var(--text-faint);
1079
+ stroke-width: 1.5px;
1080
+ }
1081
+ /* Arrowheads */
1082
+ figure.mermaid svg marker path,
1083
+ figure.mermaid svg .marker {
1084
+ fill: var(--text-faint);
1085
+ stroke: var(--text-faint);
1086
+ }
1087
+ figure.mermaid svg .edgeLabel,
1088
+ figure.mermaid svg .edgeLabel text { fill: var(--text-muted); color: var(--text-muted); }
1089
+
1090
+ /* ============================================================================
1091
+ * guides.css — component + layout styles for `scaffold guides` reference pages.
1092
+ *
1093
+ * Pairs with lib/dashboard-theme.css (the token source) and src/guides/chrome.ts
1094
+ * (the behavior). Styles the guide CHROME (.topbar, .layout, .rail, nav.toc,
1095
+ * .content) and the markdown DIRECTIVES (callouts, sev chips, filter-tables,
1096
+ * charts, tabs, citations) plus base prose typography.
1097
+ *
1098
+ * DESIGN SYSTEM: all COLORS come from dashboard-theme.css tokens, and spacing
1099
+ * uses the --sp-* scale wherever it maps. The few structural layout constants
1100
+ * (topbar height, rail/drawer width, chart label column, card min) are declared
1101
+ * as local custom properties below; a handful of sub-scale UI values (chip/bar
1102
+ * sizing, em-based inline-code padding) and the responsive breakpoint are
1103
+ * literal because no token expresses them. Both themes are covered because every
1104
+ * color is a token.
1105
+ * ==========================================================================*/
1106
+
1107
+ :root {
1108
+ --topbar-h: 52px; /* sticky topbar height; rail sticky offset keys off it */
1109
+ --rail-w: 260px; /* desktop TOC sidebar column */
1110
+ --drawer-w: 280px; /* mobile off-canvas TOC drawer */
1111
+ --card-min: 260px; /* index card min track width */
1112
+ --chart-label-w: 90px; /* chart row label column min */
1113
+ }
1114
+
1115
+ /* ── Base / reset on top of the token base in dashboard-theme.css ─────────── */
1116
+ .content a { color: var(--accent); text-decoration: none; }
1117
+ .content a:hover { text-decoration: underline; }
1118
+ .content strong { font-weight: var(--fw-semi); }
1119
+ .content hr { border: 0; border-top: 1px solid var(--border-light); margin: var(--sp-6) 0; }
1120
+
1121
+ /* Consistent keyboard focus for every interactive control (a11y). */
1122
+ .topbar button:focus-visible,
1123
+ .copy-btn:focus-visible,
1124
+ .tab-btn:focus-visible,
1125
+ .filter-input:focus-visible,
1126
+ nav.toc a:focus-visible,
1127
+ .guide-card:focus-visible,
1128
+ .content a:focus-visible {
1129
+ outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm);
1130
+ }
1131
+
1132
+ /* ── Topbar ────────────────────────────────────────────────────────────────*/
1133
+ .topbar {
1134
+ position: sticky; top: 0; z-index: 60; height: var(--topbar-h);
1135
+ display: flex; align-items: center; gap: var(--sp-3);
1136
+ padding: 0 var(--page-pad);
1137
+ background: var(--bg-card); border-bottom: 1px solid var(--border);
1138
+ }
1139
+ .topbar h1 {
1140
+ flex: 1; min-width: 0; margin: 0;
1141
+ font-size: var(--text-lg); font-weight: var(--fw-bold);
1142
+ letter-spacing: var(--ls-tight);
1143
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1144
+ }
1145
+ .topbar button {
1146
+ background: var(--bg-card); border: 1px solid var(--border); color: var(--text);
1147
+ border-radius: var(--radius-sm); padding: var(--sp-1) var(--sp-3); font-size: var(--text-base);
1148
+ line-height: 1; cursor: pointer; font-family: inherit;
1149
+ }
1150
+ .topbar button:hover { background: var(--bg-hover); border-color: var(--accent); }
1151
+ .nav-toggle { display: none; }
1152
+
1153
+ /* ── Layout: sticky sidebar TOC + reading-width content ──────────────────── */
1154
+ .layout {
1155
+ max-width: var(--max-w); margin: 0 auto;
1156
+ display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr);
1157
+ gap: var(--sp-8); padding: 0 var(--page-pad);
1158
+ }
1159
+ .rail {
1160
+ position: sticky; top: var(--topbar-h); align-self: start;
1161
+ height: calc(100vh - var(--topbar-h)); overflow-y: auto;
1162
+ padding: var(--sp-5) 0; border-right: 1px solid var(--border-light);
1163
+ }
1164
+ .content { min-width: 0; padding: var(--sp-6) 0 var(--sp-10); }
1165
+
1166
+ /* Backdrop behind the mobile drawer (toggled with the rail via chrome.ts). */
1167
+ .rail-backdrop { display: none; }
1168
+ /* In-drawer close button — hidden on desktop (the rail is a static sidebar). */
1169
+ .rail-close { display: none; }
1170
+
1171
+ /* ── Table of contents (scrollspy marks a.active) ────────────────────────── */
1172
+ nav.toc ul { list-style: none; margin: 0; padding: 0; }
1173
+ nav.toc li { margin: 0; }
1174
+ nav.toc a {
1175
+ display: block; padding: var(--sp-1) var(--sp-3); line-height: 1.35;
1176
+ color: var(--text-muted); font-size: var(--text-sm);
1177
+ text-decoration: none; border-left: 2px solid transparent;
1178
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
1179
+ }
1180
+ nav.toc a:hover { color: var(--text); background: var(--bg-hover); }
1181
+ nav.toc a.active {
1182
+ color: var(--accent); border-left-color: var(--accent);
1183
+ background: var(--accent-glow); font-weight: var(--fw-medium);
1184
+ }
1185
+ nav.toc li.toc-3 a { padding-left: var(--sp-6); font-size: var(--text-xs); }
1186
+
1187
+ /* ── Prose typography ──────────────────────────────────────────────────────*/
1188
+ .content h2 {
1189
+ font-size: var(--text-xl); letter-spacing: var(--ls-tight);
1190
+ margin: var(--sp-8) 0 var(--sp-3); padding-bottom: var(--sp-2);
1191
+ border-bottom: 1px solid var(--border-light); scroll-margin-top: calc(var(--topbar-h) + var(--sp-3));
1192
+ }
1193
+ .content h3 {
1194
+ font-size: var(--text-lg); margin: var(--sp-5) 0 var(--sp-2);
1195
+ scroll-margin-top: calc(var(--topbar-h) + var(--sp-3));
1196
+ }
1197
+ .content p { margin: var(--sp-3) 0; line-height: var(--lh-relaxed); }
1198
+ .content ul, .content ol { margin: var(--sp-3) 0; padding-left: var(--sp-6); }
1199
+ .content li { margin: var(--sp-1) 0; line-height: var(--lh-relaxed); }
1200
+ .content blockquote {
1201
+ margin: var(--sp-3) 0; padding: var(--sp-1) var(--sp-4);
1202
+ border-left: 3px solid var(--border); color: var(--text-muted);
1203
+ }
1204
+
1205
+ /* ── Inline code + code blocks (chrome.ts wraps <pre> in .code + .copy-btn) ──*/
1206
+ .content code {
1207
+ font-family: var(--font-mono); font-size: 0.85em;
1208
+ background: var(--bg-inset); padding: 0.12em 0.4em; border-radius: var(--radius-sm);
1209
+ }
1210
+ .content .code { position: relative; margin: var(--sp-3) 0; }
1211
+ .content .code pre {
1212
+ margin: 0; padding: var(--sp-3) var(--sp-4); overflow-x: auto;
1213
+ background: var(--bg-inset); border: 1px solid var(--border-light);
1214
+ border-radius: var(--radius-sm); font-family: var(--font-mono);
1215
+ font-size: var(--text-sm); line-height: var(--lh-relaxed);
1216
+ }
1217
+ .content .code pre code { background: none; padding: 0; font-size: inherit; }
1218
+ .copy-btn {
1219
+ position: absolute; top: var(--sp-1); right: var(--sp-1);
1220
+ background: var(--bg-card); border: 1px solid var(--border); color: var(--text-muted);
1221
+ border-radius: var(--radius-sm); font-size: var(--text-xs); padding: var(--sp-1) var(--sp-2);
1222
+ cursor: pointer; opacity: 0.85; font-family: inherit;
1223
+ }
1224
+ .copy-btn:hover { color: var(--accent); border-color: var(--accent); opacity: 1; }
1225
+
1226
+ /* ── Callouts ─ (border-color BEFORE border-left-color so the accent wins) ── */
1227
+ .callout {
1228
+ margin: var(--sp-4) 0; padding: var(--sp-3) var(--sp-4);
1229
+ border: 1px solid var(--border); border-left-width: 3px;
1230
+ border-radius: var(--radius-sm); background: var(--bg-card);
1231
+ }
1232
+ .callout > :first-child { margin-top: 0; }
1233
+ .callout > :last-child { margin-bottom: 0; }
1234
+ .callout-note, .callout-info { background: var(--blue-bg); border-color: var(--blue-border); border-left-color: var(--blue); }
1235
+ .callout-tip { background: var(--green-bg); border-color: var(--green-border); border-left-color: var(--green); }
1236
+ .callout-warning { background: var(--yellow-bg); border-color: var(--yellow-border); border-left-color: var(--yellow); }
1237
+ .callout-danger { background: var(--red-bg); border-color: var(--red-border); border-left-color: var(--red); }
1238
+
1239
+ /* ── Severity chips (:sev) — tight pill, sub-scale vertical padding ───────── */
1240
+ .sev {
1241
+ display: inline-block; font-size: var(--text-xs); font-weight: var(--fw-semi);
1242
+ padding: 1px var(--sp-2); border-radius: 999px; line-height: 1.5;
1243
+ border: 1px solid var(--border); background: var(--bg-inset); color: var(--text-muted);
1244
+ white-space: nowrap;
1245
+ }
1246
+ .sev-p0 { color: var(--sev-p0); border-color: var(--sev-p0); }
1247
+ .sev-p1 { color: var(--sev-p1); border-color: var(--sev-p1); }
1248
+ .sev-p2 { color: var(--sev-p2); border-color: var(--sev-p2); }
1249
+ .sev-p3 { color: var(--sev-p3); border-color: var(--sev-p3); }
1250
+ .sev-pass { color: var(--sev-pass); border-color: var(--sev-pass); }
1251
+
1252
+ /* ── Citations (:cite) — inline provenance refs ──────────────────────────── */
1253
+ .fp, .cite-advisory {
1254
+ font-family: var(--font-mono); font-size: 0.82em;
1255
+ padding: 0.05em 0.35em; border-radius: var(--radius-sm);
1256
+ background: var(--bg-inset); border: 1px solid var(--border-light);
1257
+ }
1258
+ .fp { color: var(--accent); }
1259
+ .cite-advisory { color: var(--text-faint); border-style: dashed; }
1260
+
1261
+ /* ── Tables + filter-tables ──────────────────────────────────────────────── */
1262
+ .content table { width: 100%; border-collapse: collapse; margin: var(--sp-3) 0; font-size: var(--text-sm); }
1263
+ .content th, .content td { text-align: left; padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--border-light); vertical-align: top; }
1264
+ .content th {
1265
+ color: var(--text-muted); font-weight: var(--fw-semi); font-size: var(--text-xs);
1266
+ text-transform: uppercase; letter-spacing: var(--ls-wide); border-bottom-color: var(--border);
1267
+ }
1268
+ .content tbody tr:hover { background: var(--bg-hover); }
1269
+ .content td code { white-space: nowrap; }
1270
+ .filter-table { margin: var(--sp-4) 0; }
1271
+ .filter-input {
1272
+ width: 100%; max-width: 320px; margin-bottom: var(--sp-2);
1273
+ padding: var(--sp-2) var(--sp-3); font-family: inherit; font-size: var(--text-sm);
1274
+ color: var(--text); background: var(--bg-card);
1275
+ border: 1px solid var(--border); border-radius: var(--radius-sm);
1276
+ }
1277
+ .filter-input:focus { border-color: var(--accent); }
1278
+
1279
+ /* ── Charts (:::chart) — label + proportional bar (fill carries inline width%) */
1280
+ .chart-block { margin: var(--sp-4) 0; }
1281
+ .chart-row {
1282
+ display: grid; grid-template-columns: minmax(var(--chart-label-w), 24%) 1fr;
1283
+ align-items: center; gap: var(--sp-3); margin: var(--sp-1) 0;
1284
+ }
1285
+ .chart-label {
1286
+ font-size: var(--text-sm); color: var(--text-muted); text-align: right;
1287
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
1288
+ }
1289
+ .chart-row .chart-bar {
1290
+ height: 0.9em; min-width: 2px; background: var(--accent);
1291
+ border-radius: var(--radius-sm);
1292
+ }
1293
+
1294
+ /* ── Tabs (::::tabs / :::tab) ─────────────────────────────────────────────── */
1295
+ .tabs { margin: var(--sp-4) 0; }
1296
+ .tablist { display: flex; flex-wrap: wrap; gap: var(--sp-1); border-bottom: 1px solid var(--border); margin-bottom: var(--sp-3); }
1297
+ .tab-btn {
1298
+ background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px;
1299
+ padding: var(--sp-2) var(--sp-3); color: var(--text-muted);
1300
+ font-family: inherit; font-size: var(--text-sm); font-weight: var(--fw-medium); cursor: pointer;
1301
+ }
1302
+ .tab-btn:hover { color: var(--text); }
1303
+ .tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
1304
+ .tabpane { display: none; }
1305
+ .tabpane.active { display: block; }
1306
+
1307
+ /* ── Mermaid diagrams ────────────────────────────────────────────────────── */
1308
+ .content figure { margin: var(--sp-4) 0; text-align: center; }
1309
+ .content figure svg, .content > svg, .content .mermaid svg { max-width: 100%; height: auto; }
1310
+
1311
+ /* ── Index page: category card grid ──────────────────────────────────────── */
1312
+ .content .lead { color: var(--text-muted); font-size: var(--text-base); max-width: 60ch; margin-top: var(--sp-2); }
1313
+ .guide-cards {
1314
+ display: grid; grid-template-columns: repeat(auto-fill, minmax(min(var(--card-min), 100%), 1fr));
1315
+ gap: var(--sp-4); margin: var(--sp-4) 0 var(--sp-6);
1316
+ }
1317
+ .guide-card {
1318
+ display: flex; flex-direction: column; gap: var(--sp-2);
1319
+ padding: var(--sp-4); background: var(--bg-card);
1320
+ border: 1px solid var(--border); border-radius: var(--radius);
1321
+ color: inherit; text-decoration: none;
1322
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
1323
+ }
1324
+ .guide-card:hover { border-color: var(--accent); box-shadow: var(--shadow-md); text-decoration: none; }
1325
+ .guide-card-title { font-weight: var(--fw-semi); color: var(--accent); font-size: var(--text-base); }
1326
+ .guide-card-desc { color: var(--text-muted); font-size: var(--text-sm); line-height: var(--lh-normal); }
1327
+
1328
+ /* ── Responsive: TOC becomes an off-canvas drawer (chrome.ts toggles .open) ──*/
1329
+ /* 860px is literal — media queries cannot read custom properties. Revisit it if
1330
+ --topbar-h / --rail-w / --drawer-w change (the drawer sticky offsets key off them). */
1331
+ @media (max-width: 860px) {
1332
+ .nav-toggle { display: inline-flex; align-items: center; }
1333
+ .layout { grid-template-columns: 1fr; gap: 0; }
1334
+ .rail {
1335
+ position: fixed; top: var(--topbar-h); left: 0; bottom: 0; width: var(--drawer-w); z-index: 50;
1336
+ height: auto; background: var(--bg-card); border-right: 1px solid var(--border);
1337
+ padding: var(--sp-4); box-shadow: var(--shadow-lg);
1338
+ transform: translateX(-100%); transition: transform 0.2s ease, visibility 0.2s ease;
1339
+ /* Closed drawer is off-screen AND removed from tab order / pointer events. */
1340
+ visibility: hidden; pointer-events: none;
1341
+ }
1342
+ .rail.open { transform: translateX(0); visibility: visible; pointer-events: auto; }
1343
+ .rail-close {
1344
+ display: block; margin-left: auto; margin-bottom: var(--sp-2);
1345
+ background: var(--bg-card); border: 1px solid var(--border); color: var(--text);
1346
+ border-radius: var(--radius-sm); padding: var(--sp-1) var(--sp-3); font-size: var(--text-base);
1347
+ line-height: 1; cursor: pointer; font-family: inherit;
1348
+ }
1349
+ .rail-close:hover { background: var(--bg-hover); border-color: var(--accent); }
1350
+ .rail-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1351
+ .rail-backdrop {
1352
+ display: block; position: fixed; inset: var(--topbar-h) 0 0 0;
1353
+ background: var(--scrim); z-index: 49;
1354
+ opacity: 0; pointer-events: none; transition: opacity 0.2s ease;
1355
+ }
1356
+ .rail.open ~ .rail-backdrop { opacity: 1; pointer-events: auto; }
1357
+ }
1358
+ </style>
1359
+ <script>(function(){try{var t=localStorage.getItem('guide-theme');if(!t&&window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches)t='dark';if(t)document.documentElement.setAttribute('data-theme',t);}catch(e){}})();</script>
1360
+ </head>
1361
+ <body>
1362
+ <header class="topbar">
1363
+ <button data-action="nav" class="nav-toggle" aria-label="Toggle navigation"
1364
+ aria-expanded="false" aria-controls="guide-toc">☰</button>
1365
+ <h1>Dashboard &amp; Design System</h1>
1366
+ <button data-action="theme" class="theme-toggle" aria-label="Toggle theme">◐</button>
1367
+ </header>
1368
+ <div class="layout">
1369
+ <aside class="rail" id="guide-toc">
1370
+ <button class="rail-close" data-action="nav" aria-label="Close navigation">✕</button>
1371
+ <nav class="toc" aria-label="Table of contents"><ul><li class="toc-2"><a href="#what-the-dashboard-is">What the dashboard is</a></li><li class="toc-2"><a href="#reading-the-dashboard">Reading the dashboard</a></li><li class="toc-3"><a href="#the-build-observability-panels">The Build-Observability panels</a></li><li class="toc-3"><a href="#filters-and-modals-the-inline-js">Filters and modals (the inline JS)</a></li><li class="toc-2"><a href="#the-design-token-system">The design-token system</a></li><li class="toc-3"><a href="#colors-light-dark-parity">Colors — light + dark parity</a></li><li class="toc-3"><a href="#spacing-the---sp--scale">Spacing — the --sp-* scale</a></li><li class="toc-3"><a href="#typography-radius-layout">Typography, radius &amp; layout</a></li><li class="toc-2"><a href="#customizing-the-dashboard-safely">Customizing the dashboard safely</a></li><li class="toc-2"><a href="#visual-testing">Visual testing</a></li></ul></nav>
1372
+ </aside>
1373
+ <main class="content"><h2 id="what-the-dashboard-is">What the dashboard is</h2>
1374
+ <p>Each dashboard is a single, self-contained HTML file that visualizes where a
1375
+ Scaffold build stands: which pipeline steps are done and what to run next.
1376
+ Everything is inlined (CSS, JS, data); there are no CDN fonts, stylesheets, or
1377
+ scripts, so the file works offline and renders identically wherever you open it.</p>
1378
+ <div class="callout callout-warning"><p><strong>Two different producers — this guide documents the bash generator.</strong> Scaffold
1379
+ has two distinct dashboard surfaces that do <strong>not</strong> share markup, CSS, or
1380
+ features:</p><ul>
1381
+ <li><strong><code>scaffold dashboard</code></strong> (the user-facing CLI) renders HTML from
1382
+ <code>src/dashboard/template.ts</code> + <code>generator.ts</code>. It has its own inline <code>&#x3C;style></code>
1383
+ block (classes like <code>.container</code>, <code>.phase-header</code>, <code>.summary-cards</code>), a
1384
+ <strong>Decision Log</strong> section, and standalone-command cards. It does <strong>not</strong> read
1385
+ <code>lib/dashboard-theme.css</code>, has <strong>no</strong> Beads task section, and does <strong>not</strong>
1386
+ inject the Build-Observability panels. The CLI writes the HTML to a temp file
1387
+ (or <code>--output &#x3C;path></code>) and opens it via <code>open</code> / <code>xdg-open</code> / <code>start</code>
1388
+ (<span class="fp" data-path="src/cli/commands/dashboard.ts:64-66">src/cli/commands/dashboard.ts:64-66</span>).</li>
1389
+ <li><strong><code>scripts/generate-dashboard.sh</code></strong> is the visual-test fixture that
1390
+ <code>make dashboard-test</code> runs. It embeds <code>lib/dashboard-theme.css</code>, renders a
1391
+ Beads task section, and injects the two Build-Observability panels. Its
1392
+ classes (<code>.phase-hdr</code>, <code>.pcard</code>, <code>.beads-section</code>) and design tokens are
1393
+ exclusive to this surface.</li>
1394
+ </ul><p>The rest of this guide documents the <strong><code>scripts/generate-dashboard.sh</code></strong>
1395
+ surface — the one used for visual verification and the one that carries the
1396
+ <code>lib/dashboard-theme.css</code> token system. The panels, classes, design tokens, and
1397
+ inline JS described below belong to that generator, <strong>not</strong> to the
1398
+ <code>scaffold dashboard</code> CLI command.</p></div>
1399
+ <h2 id="reading-the-dashboard">Reading the dashboard</h2>
1400
+ <p>The page is built top-to-bottom by the inline renderer in the generator. From
1401
+ the header down:</p>
1402
+
1403
+
1404
+
1405
+
1406
+
1407
+
1408
+
1409
+
1410
+
1411
+
1412
+
1413
+
1414
+
1415
+
1416
+
1417
+
1418
+
1419
+
1420
+
1421
+
1422
+
1423
+
1424
+
1425
+
1426
+
1427
+
1428
+
1429
+
1430
+
1431
+
1432
+
1433
+
1434
+
1435
+
1436
+
1437
+
1438
+
1439
+
1440
+
1441
+
1442
+
1443
+
1444
+
1445
+
1446
+
1447
+
1448
+
1449
+
1450
+
1451
+
1452
+ <table><thead><tr><th>Region</th><th>Classes</th><th>What it shows</th></tr></thead><tbody><tr><td>Header</td><td><code>.header</code>, <code>.header-meta</code>, <code>.theme-toggle</code></td><td>Title, profile badge, project name + timestamp, light/dark toggle</td></tr><tr><td>Status legend</td><td><code>.status-legend</code></td><td>The four pipeline status badges (Done / Likely Done / Skipped / Pending)</td></tr><tr><td>Progress bar</td><td><code>.progress-bar</code>, <code>.seg-done</code>, <code>.seg-likely</code>, <code>.seg-skip</code></td><td>Proportional completion rail</td></tr><tr><td>Summary cards</td><td><code>.cards</code>, <code>.card</code>, <code>.card-num</code></td><td>Counts: completed, likely, skipped, pending, total, Beads-open</td></tr><tr><td>What's Next</td><td><code>.next-banner</code>, <code>.next-cmd</code></td><td>The recommended next command, with a copy button</td></tr><tr><td>Phases</td><td><code>.phase</code>, <code>.phase-hdr</code>, <code>.pcard</code></td><td>Collapsible phase sections, one prompt card per step</td></tr><tr><td>Beads Tasks</td><td><code>.beads-section</code>, <code>.beads-filters</code> (container), <code>.beads-filter</code> (buttons)</td><td>Filterable task list (status + priority), cards open a detail modal</td></tr><tr><td>Build Progress / Audit</td><td><code>#build-progress</code>, <code>#build-audit</code></td><td>The two Build-Observability panels (only when populated)</td></tr></tbody></table>
1453
+ <p>The header, legend, progress bar, summary cards, and What's Next banner are all
1454
+ emitted in sequence by the renderer (<span class="fp" data-path="scripts/generate-dashboard.sh:505-544">scripts/generate-dashboard.sh:505-544</span>).</p>
1455
+ <h3 id="the-build-observability-panels">The Build-Observability panels</h3>
1456
+ <p>After the pipeline content, the generator shells out to Build Observability and
1457
+ splices its HTML fragments into the page between named HTML comment markers
1458
+ (<code>&#x3C;!-- observe:progress --></code> … <code>&#x3C;!-- /observe:progress --></code> and the audit pair),
1459
+ preferring a local <code>dist/index.js</code> build and falling back to a global <code>scaffold</code>
1460
+ binary (<span class="fp" data-path="scripts/generate-dashboard.sh:872-887">scripts/generate-dashboard.sh:872-887</span>):</p>
1461
+ <pre><code class="language-bash">scaffold observe progress --render=dashboard-fragment # → #build-progress panel
1462
+ scaffold observe audit --render=dashboard-fragment-audit # → #build-audit panel
1463
+ </code></pre>
1464
+ <p>Each renders as a <code>&#x3C;section class="panel"></code> — <code>#build-progress</code> for the live
1465
+ timeline and <code>#build-audit</code> (carrying <code>data-verdict</code> and <code>data-threshold</code>) for
1466
+ audit findings. If neither a local build nor a global <code>scaffold</code> is available the
1467
+ markers stay empty and the panels simply don't appear. See the
1468
+ <a href="../observability/index.md">Build Observability guide</a>{mode=advisory} for what
1469
+ those panels report.</p>
1470
+ <h3 id="filters-and-modals-the-inline-js">Filters and modals (the inline JS)</h3>
1471
+ <p>The dashboard is interactive without any framework — a handful of vanilla
1472
+ functions are inlined at the foot of the generated HTML:</p>
1473
+ <ul>
1474
+ <li><strong>Collapse a phase</strong> — clicking a <code>.phase-hdr</code> calls <code>togglePhase()</code>, which
1475
+ toggles <code>.closed</code> on the header (rotating its arrow) and <code>.hidden</code> on the next
1476
+ sibling list (<span class="fp" data-path="scripts/generate-dashboard.sh:825-828">scripts/generate-dashboard.sh:825-828</span>).</li>
1477
+ <li><strong>Copy a command</strong> — clicking a <code>.pcmd</code> calls <code>copyCmd()</code>, which writes the
1478
+ <code>data-cmd</code> value to the clipboard and flashes the <code>.copied</code> state for 1.5 s
1479
+ (<span class="fp" data-path="scripts/generate-dashboard.sh:829-838">scripts/generate-dashboard.sh:829-838</span>).</li>
1480
+ <li><strong>Filter Beads</strong> — <code>filterBeads()</code> switches the active status filter and
1481
+ <code>filterBeadsPrio()</code> toggles priority filters; cards carry <code>data-bead-status</code>
1482
+ and <code>data-bead-priority</code> so the filters can show/hide them
1483
+ (<span class="fp" data-path="scripts/generate-dashboard.sh:733-741">scripts/generate-dashboard.sh:733-741</span>).</li>
1484
+ <li><strong>Beads task detail modal</strong> — <code>openBeadModal(id)</code> builds a <code>.modal-overlay</code>
1485
+ detail view for a Beads task (status, priority, deps, timestamps); close via
1486
+ the X, Escape, or a backdrop click
1487
+ (<span class="fp" data-path="scripts/generate-dashboard.sh:756-816">scripts/generate-dashboard.sh:756-816</span>).</li>
1488
+ <li><strong>Prompt detail modal</strong> — clicking a prompt card calls <code>openModal(slug)</code>,
1489
+ which renders the full prompt content for that pipeline step in the same
1490
+ <code>.modal-overlay</code> shell (<span class="fp" data-path="scripts/generate-dashboard.sh:693-710">scripts/generate-dashboard.sh:693-710</span>).</li>
1491
+ <li><strong>Audit finding filters</strong> — on load, <code>initAuditFilters()</code> finds the
1492
+ <code>#build-audit</code> section, reads its <code>data-threshold</code>, and wires the
1493
+ <code>[data-filter]</code> buttons to show/hide <code>.finding</code> rows by severity rank
1494
+ (<span class="fp" data-path="scripts/generate-dashboard.sh:839-863">scripts/generate-dashboard.sh:839-863</span>).</li>
1495
+ </ul>
1496
+ <h2 id="the-design-token-system">The design-token system</h2>
1497
+ <p><code>lib/dashboard-theme.css</code> defines a shared set of CSS custom properties for
1498
+ colors, spacing, sizes, and radii. Component styles should prefer these tokens
1499
+ via <code>var(--token)</code> — that is what keeps light and dark mode in lockstep and the
1500
+ surface coherent. The contract is "prefer tokens," not "tokens only": the file
1501
+ still contains some component-level raw values (e.g. <code>#fff</code>, gradient stop hex
1502
+ colors, <code>rgba(...)</code>, and a few one-off pixel values like <code>99px</code>, <code>720px</code>, and
1503
+ <code>130px</code>). Promote a raw value to a token when it needs light/dark parity or
1504
+ reuse; leave genuinely one-off structural values inline rather than minting a
1505
+ single-use token.</p>
1506
+ <h3 id="colors-light-dark-parity">Colors — light + dark parity</h3>
1507
+ <p>The light palette is declared on <code>:root</code> (<span class="fp" data-path="lib/dashboard-theme.css:11-35">lib/dashboard-theme.css:11-35</span>),
1508
+ and every color token has a matching override under <code>[data-theme="dark"]</code>
1509
+ (<span class="fp" data-path="lib/dashboard-theme.css:98-120">lib/dashboard-theme.css:98-120</span>). Dark mode is not a filter — backgrounds
1510
+ go dramatically darker, text lightens but stays slightly warm, and accents shift
1511
+ lighter for contrast on dark surfaces.</p>
1512
+ <div class="filter-table"><input type="text" class="filter-input" placeholder="Filter…" aria-label="Filter table rows" disabled>
1513
+
1514
+
1515
+
1516
+
1517
+
1518
+
1519
+
1520
+
1521
+
1522
+
1523
+
1524
+
1525
+
1526
+
1527
+
1528
+
1529
+
1530
+
1531
+
1532
+
1533
+
1534
+
1535
+
1536
+
1537
+
1538
+
1539
+
1540
+
1541
+
1542
+
1543
+
1544
+
1545
+
1546
+
1547
+
1548
+
1549
+
1550
+
1551
+
1552
+
1553
+
1554
+
1555
+
1556
+
1557
+
1558
+
1559
+
1560
+
1561
+
1562
+
1563
+
1564
+
1565
+
1566
+
1567
+
1568
+
1569
+
1570
+
1571
+
1572
+
1573
+
1574
+
1575
+
1576
+
1577
+
1578
+
1579
+
1580
+
1581
+
1582
+
1583
+
1584
+
1585
+
1586
+
1587
+
1588
+
1589
+ <table><thead><tr><th>token</th><th>light</th><th>dark</th><th>role</th></tr></thead><tbody><tr><td><code>--bg</code></td><td><code>#f5f6fa</code></td><td><code>#0f1117</code></td><td>Page background</td></tr><tr><td><code>--bg-card</code></td><td><code>#ffffff</code></td><td><code>#1a1d2e</code></td><td>Card / panel surface</td></tr><tr><td><code>--bg-inset</code></td><td><code>#e8eaf2</code></td><td><code>#141724</code></td><td>Recessed elements (copy buttons, inputs)</td></tr><tr><td><code>--text</code></td><td><code>#1a1d2e</code></td><td><code>#e2e5f0</code></td><td>Primary text</td></tr><tr><td><code>--text-muted</code></td><td><code>#6b7294</code></td><td><code>#7c82a8</code></td><td>Secondary text</td></tr><tr><td><code>--border</code></td><td><code>#dde0ed</code></td><td><code>#2a2f45</code></td><td>Default borders</td></tr><tr><td><code>--accent</code></td><td><code>#4f46e5</code></td><td><code>#818cf8</code></td><td>Primary interactive color</td></tr><tr><td><code>--green</code></td><td><code>#059669</code></td><td><code>#34d399</code></td><td>Completed status</td></tr><tr><td><code>--blue</code></td><td><code>#2563eb</code></td><td><code>#60a5fa</code></td><td>Likely-completed status</td></tr><tr><td><code>--yellow</code></td><td><code>#d97706</code></td><td><code>#fbbf24</code></td><td>Warnings / blocked</td></tr><tr><td><code>--gray</code></td><td><code>#9ca3af</code></td><td><code>#6b7294</code></td><td>Skipped status</td></tr></tbody></table></div>
1590
+ <p>Each status color (<code>--green</code>, <code>--blue</code>, <code>--yellow</code>, <code>--gray</code>) also has <code>-bg</code> and
1591
+ <code>-border</code> companions used by badges and status dots, so a status reads correctly
1592
+ on both card and inset surfaces.</p>
1593
+ <div class="callout callout-note"><p>The table above is a high-level subset of the most visible status and surface
1594
+ tokens — not the complete set. <code>lib/dashboard-theme.css</code> (and
1595
+ <code>docs/design-system.md</code> §2) also define many component-specific semantic tokens
1596
+ such as <code>--bg-hover</code>, <code>--text-faint</code>, the <code>--next-*</code>, <code>--progress-*</code>,
1597
+ <code>--shadow-*</code>, <code>--accent-hover</code> / <code>--accent-glow</code>, and <code>--border-light</code>. Consult
1598
+ the CSS file and §2 for the full system.</p></div>
1599
+ <h3 id="spacing-the---sp--scale">Spacing — the <code>--sp-*</code> scale</h3>
1600
+ <p>All spacing comes from an 8-step scale on a 4px base
1601
+ (<span class="fp" data-path="lib/dashboard-theme.css:63-71">lib/dashboard-theme.css:63-71</span>). There are no ad-hoc margins or paddings;
1602
+ layout is composed entirely from these:</p>
1603
+
1604
+
1605
+
1606
+
1607
+
1608
+
1609
+
1610
+
1611
+
1612
+
1613
+
1614
+
1615
+
1616
+
1617
+
1618
+
1619
+
1620
+
1621
+
1622
+
1623
+
1624
+
1625
+
1626
+
1627
+
1628
+
1629
+
1630
+
1631
+
1632
+
1633
+
1634
+
1635
+
1636
+
1637
+
1638
+
1639
+
1640
+
1641
+
1642
+
1643
+
1644
+
1645
+
1646
+
1647
+
1648
+
1649
+
1650
+
1651
+
1652
+
1653
+ <table><thead><tr><th>token</th><th>value</th><th>typical use</th></tr></thead><tbody><tr><td><code>--sp-1</code></td><td><code>4px</code></td><td>minimal gaps (dot margin)</td></tr><tr><td><code>--sp-2</code></td><td><code>8px</code></td><td>tight gaps (badge padding)</td></tr><tr><td><code>--sp-3</code></td><td><code>12px</code></td><td>card gap, prompt-card padding</td></tr><tr><td><code>--sp-4</code></td><td><code>16px</code></td><td>card inner padding, section gap</td></tr><tr><td><code>--sp-5</code></td><td><code>20px</code></td><td>banner padding</td></tr><tr><td><code>--sp-6</code></td><td><code>24px</code></td><td>section margin, page side padding</td></tr><tr><td><code>--sp-8</code></td><td><code>32px</code></td><td>page top/bottom padding</td></tr><tr><td><code>--sp-10</code></td><td><code>40px</code></td><td>major section separation, footer</td></tr></tbody></table>
1654
+ <h3 id="typography-radius-layout">Typography, radius &#x26; layout</h3>
1655
+ <p>The font stacks are system-only (no web fonts): <code>--font-sans</code> for body and
1656
+ headings, <code>--font-mono</code> for commands, counts, and step numbers
1657
+ (<span class="fp" data-path="lib/dashboard-theme.css:74-75">lib/dashboard-theme.css:74-75</span>). Sizes run on a <code>--text-xs</code> … <code>--text-2xl</code>
1658
+ scale (<span class="fp" data-path="lib/dashboard-theme.css:76-81">lib/dashboard-theme.css:76-81</span>), paired with <code>--lh-*</code> line heights,
1659
+ <code>--fw-*</code> weights, and <code>--ls-*</code> letter-spacing tokens. Surfaces use <code>--radius</code>
1660
+ (10px) for cards/panels and <code>--radius-sm</code> (6px) for buttons and code blocks
1661
+ (<span class="fp" data-path="lib/dashboard-theme.css:26">lib/dashboard-theme.css:26</span>); content is centered within <code>--max-w</code> (960px)
1662
+ (<span class="fp" data-path="lib/dashboard-theme.css:93">lib/dashboard-theme.css:93</span>). Depth comes from a four-step shadow scale,
1663
+ with <code>--shadow-lg</code> reserved for modals and overlays
1664
+ (<span class="fp" data-path="lib/dashboard-theme.css:61">lib/dashboard-theme.css:61</span>).</p>
1665
+ <h2 id="customizing-the-dashboard-safely">Customizing the dashboard safely</h2>
1666
+ <p>The dashboard's coherence is enforced by convention, not by a build step — so the
1667
+ rules in <code>docs/design-system.md</code> are load-bearing (with the §6.1 caveat noted
1668
+ below). Follow the add-a-token / add-a-component flow and stay inside the token
1669
+ system.</p>
1670
+ <div class="callout callout-warning"><p><strong>Two rules that are never optional.</strong> (1) <strong>Prefer tokens for anything that
1671
+ needs light/dark parity</strong> — never hardcode a color, theme-dependent value, or
1672
+ font name in a component style; if you need a value that doesn't exist, add a
1673
+ <em>token</em> first. (Purely structural one-offs may stay inline — see the token
1674
+ section above.) (2) <strong>Always ship both modes</strong> —
1675
+ every new color token needs a <code>:root</code> value <em>and</em> a <code>[data-theme="dark"]</code>
1676
+ override, and every change must be checked in light <em>and</em> dark. Skipping the dark
1677
+ override leaves the token undefined in dark mode and breaks the surface.</p></div>
1678
+ <p>To add a token: declare it on <code>:root</code> in the light section of
1679
+ <code>lib/dashboard-theme.css</code>, add the dark override under <code>[data-theme="dark"]</code>,
1680
+ then reference it as <code>var(--token)</code>. Note that <code>docs/design-system.md</code> §6.1 is
1681
+ stale here — it still says to add the dark override to a
1682
+ <code>@media (prefers-color-scheme: dark)</code> block, but no such block exists in
1683
+ <code>lib/dashboard-theme.css</code>; the dark tokens live only under the <code>[data-theme="dark"]</code>
1684
+ attribute selector. Follow the code, not §6.1. To add a component: add its styles to the right section of the
1685
+ theme file, reuse existing tokens (add new ones first if needed), wire its markup
1686
+ into the generator JS, and document it in §3. New components should reuse
1687
+ established patterns — for example, collapsible sections reuse the same
1688
+ <code>.phase-hdr</code> + <code>togglePhase()</code> mechanism as pipeline phases, and detail views
1689
+ reuse <code>.modal-overlay</code>.</p>
1690
+ <p>Also avoid: <code>!important</code> (restructure selectors instead), any second theme
1691
+ mechanism (the <code>[data-theme]</code> toggle is the only one), and external resource
1692
+ references — the generated HTML must stay self-contained. Dark mode is driven by
1693
+ the inline bootstrap that reads <code>localStorage.getItem('scaffold-theme')</code>, falling
1694
+ back to <code>prefers-color-scheme</code> on first visit
1695
+ (<span class="fp" data-path="scripts/generate-dashboard.sh:438">scripts/generate-dashboard.sh:438</span>), and the <code>.theme-toggle</code> button flips
1696
+ the <code>[data-theme]</code> attribute and persists the choice via
1697
+ <code>localStorage.setItem('scaffold-theme', …)</code>
1698
+ (<span class="fp" data-path="scripts/generate-dashboard.sh:817-823">scripts/generate-dashboard.sh:817-823</span>).</p>
1699
+ <h2 id="visual-testing">Visual testing</h2>
1700
+ <p>Reference guides are verified manually with a screenshot, and the dashboard
1701
+ itself is verified the same way: after any change to
1702
+ <code>scripts/generate-dashboard.sh</code>, <code>lib/dashboard-theme.css</code>, or a dashboard test,
1703
+ render it and look at it in a browser. There is no pixel-diff gate — a human (or
1704
+ an agent driving Playwright MCP) confirms the rendering.</p>
1705
+ <pre><code class="language-bash">make dashboard-test # writes tests/screenshots/dashboard-test.html
1706
+ </code></pre>
1707
+ <p>Then drive Playwright MCP over the generated file: <code>browser_navigate</code> to its
1708
+ <code>file://</code> path, <code>browser_resize</code> to 1280×800 then 375×812, take a
1709
+ <code>browser_take_screenshot</code> at each. For dark mode, don't rely on emulating
1710
+ <code>prefers-color-scheme</code> after the page has loaded — the inline bootstrap reads it
1711
+ only once, so the page stays on whatever <code>[data-theme]</code> is already set. Instead,
1712
+ either set <code>localStorage('scaffold-theme', 'dark')</code> and reload, or click the
1713
+ <code>.theme-toggle</code> button; then confirm
1714
+ <code>document.documentElement.dataset.theme === 'dark'</code> <strong>before</strong> capturing the
1715
+ dark screenshots (and clear/set the key back for light shots). Also exercise the
1716
+ interactive bits (expand/collapse a phase, a Beads filter, a modal), and
1717
+ <code>browser_snapshot</code> to sanity-check accessibility.</p>
1718
+ <p>The minimum coverage for any dashboard change is desktop + mobile in both light
1719
+ and dark mode, plus the interactive elements, compared against the committed
1720
+ baselines.</p>
1721
+
1722
+
1723
+
1724
+
1725
+
1726
+
1727
+
1728
+
1729
+
1730
+
1731
+
1732
+
1733
+
1734
+
1735
+
1736
+
1737
+
1738
+
1739
+
1740
+
1741
+
1742
+ <table><thead><tr><th>Path</th><th>Role</th></tr></thead><tbody><tr><td><code>tests/screenshots/dashboard-test.html</code></td><td>Generated test fixture (from <code>make dashboard-test</code>)</td></tr><tr><td><code>tests/screenshots/baseline/</code></td><td>Committed baselines</td></tr><tr><td><code>tests/screenshots/current/</code></td><td>New screenshots (gitignored); name <code>{feature}_{viewport}_{state}.png</code></td></tr></tbody></table>
1743
+ <p>Update a baseline only for an intentional visual change — copy the new shot from
1744
+ <code>current/</code> to <code>baseline/</code> and commit it. Full workflow and naming live in
1745
+ <code>docs/tdd-standards.md</code> §7 and the design rules in <code>docs/design-system.md</code>.</p></main>
1746
+ <div class="rail-backdrop" data-action="nav" aria-hidden="true"></div>
1747
+ </div>
1748
+ <script>(function(){
1749
+ var LS_KEY = 'guide-theme';
1750
+ function applyTheme(t) {
1751
+ document.documentElement.setAttribute('data-theme', t);
1752
+ }
1753
+
1754
+ document.addEventListener('DOMContentLoaded', function() {
1755
+ // ─── Theme toggle ────────────────────────────────────────────────────────
1756
+ document.querySelectorAll('[data-action="theme"]').forEach(function(btn) {
1757
+ btn.addEventListener('click', function() {
1758
+ var current = document.documentElement.getAttribute('data-theme');
1759
+ var next = current === 'dark' ? 'light' : 'dark';
1760
+ applyTheme(next);
1761
+ try { localStorage.setItem(LS_KEY, next); } catch(e) {}
1762
+ });
1763
+ });
1764
+
1765
+ // ─── Mobile nav (drawer + backdrop; aria-expanded + Escape-to-close) ──────
1766
+ function setNav(open) {
1767
+ var rail = document.querySelector('.rail');
1768
+ if (rail) rail.classList.toggle('open', open);
1769
+ var toggle = document.querySelector('.nav-toggle');
1770
+ if (toggle) toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
1771
+ // Modal-drawer focus containment: while open, make the page content inert
1772
+ // (out of tab order + a11y tree) and move focus into the drawer; on close,
1773
+ // restore content and return focus to the toggle.
1774
+ var main = document.querySelector('.content');
1775
+ if (main) main.inert = open;
1776
+ if (open) {
1777
+ var first = rail && rail.querySelector('a, button, [tabindex]:not([tabindex="-1"])');
1778
+ if (first) first.focus();
1779
+ else if (rail) { rail.setAttribute('tabindex', '-1'); rail.focus(); }
1780
+ } else if (toggle) {
1781
+ toggle.focus();
1782
+ }
1783
+ }
1784
+ // If the viewport grows past the mobile breakpoint while the drawer is open,
1785
+ // the rail becomes the desktop sidebar and the toggle hides — clear the open
1786
+ // state so .content doesn't stay inert with no way to close it.
1787
+ if (window.matchMedia) {
1788
+ var mq = window.matchMedia('(max-width: 860px)');
1789
+ var onMq = function() {
1790
+ if (mq.matches) return;
1791
+ var rail = document.querySelector('.rail');
1792
+ if (rail) rail.classList.remove('open');
1793
+ var toggle = document.querySelector('.nav-toggle');
1794
+ if (toggle) toggle.setAttribute('aria-expanded', 'false');
1795
+ var main = document.querySelector('.content');
1796
+ if (main) main.inert = false;
1797
+ };
1798
+ if (mq.addEventListener) mq.addEventListener('change', onMq);
1799
+ else if (mq.addListener) mq.addListener(onMq);
1800
+ }
1801
+ document.querySelectorAll('[data-action="nav"]').forEach(function(btn) {
1802
+ btn.addEventListener('click', function() {
1803
+ var rail = document.querySelector('.rail');
1804
+ setNav(!(rail && rail.classList.contains('open')));
1805
+ });
1806
+ });
1807
+ // Selecting a TOC link closes the drawer (so the now-active content isn't
1808
+ // left inert behind the panel) before the anchor navigation scrolls.
1809
+ var drawerRail = document.querySelector('.rail');
1810
+ if (drawerRail) {
1811
+ drawerRail.querySelectorAll('a').forEach(function(a) {
1812
+ a.addEventListener('click', function() {
1813
+ if (drawerRail.classList.contains('open')) setNav(false);
1814
+ });
1815
+ });
1816
+ }
1817
+ document.addEventListener('keydown', function(e) {
1818
+ var rail = document.querySelector('.rail');
1819
+ if (!rail || !rail.classList.contains('open')) return;
1820
+ if (e.key === 'Escape') { setNav(false); return; } // setNav restores focus to the toggle
1821
+ // Trap Tab within the open drawer (modal pattern).
1822
+ if (e.key !== 'Tab') return;
1823
+ var f = rail.querySelectorAll('a[href], button, [tabindex]:not([tabindex="-1"])');
1824
+ if (!f.length) return;
1825
+ var first = f[0], last = f[f.length - 1];
1826
+ if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
1827
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
1828
+ });
1829
+
1830
+ // ─── Copy buttons ─────────────────────────────────────────────────────────
1831
+ document.querySelectorAll('pre').forEach(function(pre) {
1832
+ if (!pre.parentNode) return;
1833
+ var wrapper = document.createElement('div');
1834
+ wrapper.className = 'code';
1835
+ pre.parentNode.insertBefore(wrapper, pre);
1836
+ wrapper.appendChild(pre);
1837
+ var btn = document.createElement('button');
1838
+ btn.className = 'copy-btn';
1839
+ btn.textContent = 'Copy';
1840
+ btn.addEventListener('click', function() {
1841
+ var text = pre.textContent || '';
1842
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1843
+ navigator.clipboard.writeText(text).then(function() {
1844
+ btn.textContent = 'Copied';
1845
+ setTimeout(function() { btn.textContent = 'Copy'; }, 1200);
1846
+ }, function() {
1847
+ btn.textContent = 'Copy';
1848
+ });
1849
+ }
1850
+ });
1851
+ wrapper.insertBefore(btn, pre);
1852
+ });
1853
+
1854
+ // ─── Tabs (ARIA pattern: aria-selected + roving tabindex + arrow keys) ────
1855
+ function activateTab(group, btn, focus) {
1856
+ var idx = btn.getAttribute('data-tab');
1857
+ group.querySelectorAll('.tab-btn').forEach(function(b) {
1858
+ var on = b === btn;
1859
+ b.classList.toggle('active', on);
1860
+ b.setAttribute('aria-selected', on ? 'true' : 'false');
1861
+ b.setAttribute('tabindex', on ? '0' : '-1');
1862
+ });
1863
+ group.querySelectorAll('.tabpane').forEach(function(pane) {
1864
+ pane.classList.toggle('active', pane.getAttribute('data-tab') === idx);
1865
+ });
1866
+ if (focus) btn.focus();
1867
+ }
1868
+ document.querySelectorAll('.tabs').forEach(function(group) {
1869
+ var btns = [].slice.call(group.querySelectorAll('.tab-btn'));
1870
+ btns.forEach(function(btn, i) {
1871
+ btn.addEventListener('click', function() { activateTab(group, btn, false); });
1872
+ btn.addEventListener('keydown', function(e) {
1873
+ var ni = -1;
1874
+ if (e.key === 'ArrowRight' || e.key === 'ArrowDown') ni = (i + 1) % btns.length;
1875
+ else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') ni = (i - 1 + btns.length) % btns.length;
1876
+ else if (e.key === 'Home') ni = 0;
1877
+ else if (e.key === 'End') ni = btns.length - 1;
1878
+ if (ni >= 0) { e.preventDefault(); activateTab(group, btns[ni], true); }
1879
+ });
1880
+ });
1881
+ });
1882
+
1883
+ // ─── Filter tables ────────────────────────────────────────────────────────
1884
+ document.querySelectorAll('.filter-input').forEach(function(input) {
1885
+ input.addEventListener('input', function() {
1886
+ var q = input.value.toLowerCase();
1887
+ var container = input.closest('.filter-table');
1888
+ if (!container) return;
1889
+ container.querySelectorAll('tbody tr').forEach(function(row) {
1890
+ var text = (row.textContent || '').toLowerCase();
1891
+ row.style.display = text.includes(q) ? '' : 'none';
1892
+ });
1893
+ });
1894
+ });
1895
+
1896
+ // ─── Scrollspy ────────────────────────────────────────────────────────────
1897
+ if (typeof IntersectionObserver === 'undefined') return;
1898
+ var headings = document.querySelectorAll('h2[id],h3[id]');
1899
+ if (!headings.length) return;
1900
+ var observer = new IntersectionObserver(function(entries) {
1901
+ entries.forEach(function(entry) {
1902
+ if (!entry.isIntersecting) return;
1903
+ var id = entry.target.getAttribute('id');
1904
+ document.querySelectorAll('.toc a').forEach(function(a) {
1905
+ a.classList.toggle('active', a.getAttribute('href') === '#' + id);
1906
+ });
1907
+ });
1908
+ }, { rootMargin: '0px 0px -70% 0px', threshold: 0 });
1909
+ headings.forEach(function(h) { observer.observe(h); });
1910
+ });
1911
+ })();</script>
1912
+ </body>
1913
+ </html>