codedebrief 0.11.0__py3-none-any.whl

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 (48) hide show
  1. codedebrief/__init__.py +12 -0
  2. codedebrief/analysis/__init__.py +16 -0
  3. codedebrief/analysis/common.py +527 -0
  4. codedebrief/analysis/discovery.py +100 -0
  5. codedebrief/analysis/languages/__init__.py +6 -0
  6. codedebrief/analysis/languages/_common.py +68 -0
  7. codedebrief/analysis/languages/c.py +96 -0
  8. codedebrief/analysis/languages/cpp.py +146 -0
  9. codedebrief/analysis/languages/csharp.py +137 -0
  10. codedebrief/analysis/languages/go.py +157 -0
  11. codedebrief/analysis/languages/java.py +158 -0
  12. codedebrief/analysis/languages/php.py +83 -0
  13. codedebrief/analysis/languages/ruby.py +75 -0
  14. codedebrief/analysis/languages/rust.py +96 -0
  15. codedebrief/analysis/project.py +373 -0
  16. codedebrief/analysis/python.py +939 -0
  17. codedebrief/analysis/registry.py +320 -0
  18. codedebrief/analysis/treesitter.py +884 -0
  19. codedebrief/analysis/typescript.py +1019 -0
  20. codedebrief/artifacts.py +49 -0
  21. codedebrief/cli.py +585 -0
  22. codedebrief/config.py +226 -0
  23. codedebrief/doctor.py +175 -0
  24. codedebrief/install.py +441 -0
  25. codedebrief/mcp_server.py +2720 -0
  26. codedebrief/model.py +189 -0
  27. codedebrief/py.typed +1 -0
  28. codedebrief/quality.py +392 -0
  29. codedebrief/query.py +641 -0
  30. codedebrief/render/__init__.py +6 -0
  31. codedebrief/render/assets/generated/codedebrief-viewer-runtime.iife.js +10 -0
  32. codedebrief/render/assets/panels.js +462 -0
  33. codedebrief/render/assets/shell.js +1649 -0
  34. codedebrief/render/assets/styles.css +1715 -0
  35. codedebrief/render/assets/tree.js +616 -0
  36. codedebrief/render/html.py +191 -0
  37. codedebrief/render/markdown.py +153 -0
  38. codedebrief/render/payload.py +326 -0
  39. codedebrief/render/snapshot.py +769 -0
  40. codedebrief/schema/codedebrief.schema.json +449 -0
  41. codedebrief/util.py +65 -0
  42. codedebrief/validation.py +214 -0
  43. codedebrief-0.11.0.dist-info/METADATA +426 -0
  44. codedebrief-0.11.0.dist-info/RECORD +48 -0
  45. codedebrief-0.11.0.dist-info/WHEEL +4 -0
  46. codedebrief-0.11.0.dist-info/entry_points.txt +2 -0
  47. codedebrief-0.11.0.dist-info/licenses/LICENSE +176 -0
  48. codedebrief-0.11.0.dist-info/licenses/NOTICE +9 -0
@@ -0,0 +1,1715 @@
1
+
2
+ :root {
3
+ --paper: #080a0f;
4
+ --grid: rgba(174, 187, 212, 0.035);
5
+ --panel: #11141b;
6
+ --panel-2: #0d1016;
7
+ --header: rgba(9, 11, 16, 0.9);
8
+ --ink: #edf2fb;
9
+ --muted: #8b94a6;
10
+ --line: #222834;
11
+ --line-strong: #333b4b;
12
+ --blue: #6f93ff;
13
+ --cyan: #29d1e7;
14
+ --amber: #f0b33f;
15
+ --coral: #ff6b6b;
16
+ --violet: #a78bfa;
17
+ --hover: #171b24;
18
+ --active: #202637;
19
+ --chip: #1a2130;
20
+ --tool: rgba(17, 20, 27, 0.92);
21
+ --drawer-backdrop: rgba(0, 0, 0, .55);
22
+ --on-accent: #090b10;
23
+ --node-fill: #151a24;
24
+ --fill-entry: #17213b;
25
+ --fill-decision: #2a220f;
26
+ --fill-call: #211935;
27
+ --fill-terminal: #102b29;
28
+ --fill-error: #2d171b;
29
+ --edge: #566174;
30
+ --radius: 10px;
31
+ --radius-sm: 8px;
32
+ --shadow: 0 22px 60px rgba(0, 0, 0, 0.58);
33
+ --left-rail-width: 312px;
34
+ --right-rail-width: 336px;
35
+ color-scheme: dark;
36
+ }
37
+ [data-theme="dark"] {
38
+ --paper: #080a0f;
39
+ --grid: rgba(174, 187, 212, 0.035);
40
+ --panel: #11141b;
41
+ --panel-2: #0d1016;
42
+ --header: rgba(9, 11, 16, 0.9);
43
+ --ink: #edf2fb;
44
+ --muted: #8b94a6;
45
+ --line: #222834;
46
+ --line-strong: #333b4b;
47
+ --blue: #6f93ff;
48
+ --cyan: #29d1e7;
49
+ --amber: #f0b33f;
50
+ --coral: #ff6b6b;
51
+ --violet: #a78bfa;
52
+ --hover: #171b24;
53
+ --active: #202637;
54
+ --chip: #1a2130;
55
+ --tool: rgba(17, 20, 27, 0.92);
56
+ --drawer-backdrop: rgba(0, 0, 0, .55);
57
+ --on-accent: #090b10;
58
+ --node-fill: #151a24;
59
+ --fill-entry: #17213b;
60
+ --fill-decision: #2a220f;
61
+ --fill-call: #211935;
62
+ --fill-terminal: #102b29;
63
+ --fill-error: #2d171b;
64
+ --edge: #566174;
65
+ --shadow: 0 22px 60px rgba(0, 0, 0, 0.58);
66
+ color-scheme: dark;
67
+ }
68
+ * { box-sizing: border-box; }
69
+ [hidden] { display: none !important; }
70
+ html, body { height: 100%; margin: 0; }
71
+ body {
72
+ color: var(--ink);
73
+ background:
74
+ linear-gradient(var(--grid) 1px, transparent 1px),
75
+ linear-gradient(90deg, var(--grid) 1px, transparent 1px),
76
+ var(--paper);
77
+ background-size: 26px 26px;
78
+ font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
79
+ overflow: hidden;
80
+ transition: background-color .25s ease, color .25s ease;
81
+ }
82
+ button, input { font: inherit; }
83
+ button { color: inherit; }
84
+ body[data-rail-resizing] {
85
+ cursor: ew-resize;
86
+ user-select: none;
87
+ }
88
+ body[data-rail-resizing] * { cursor: ew-resize !important; }
89
+ .shell {
90
+ display: grid;
91
+ grid-template-columns: var(--left-rail-width) minmax(0, 1fr) var(--right-rail-width);
92
+ grid-template-rows: 56px minmax(0, 1fr);
93
+ height: 100%;
94
+ }
95
+ header {
96
+ grid-column: 1 / -1;
97
+ display: flex;
98
+ align-items: center;
99
+ gap: 16px;
100
+ padding: 0 18px;
101
+ background: var(--header);
102
+ border-bottom: 1px solid var(--line);
103
+ backdrop-filter: blur(16px);
104
+ z-index: 3;
105
+ }
106
+ .brand {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 9px;
110
+ min-width: 160px;
111
+ }
112
+ .brand-mark {
113
+ flex: none;
114
+ display: grid;
115
+ place-items: center;
116
+ }
117
+ /* Mini decision flow: entry node (circle) linked to a decision (diamond), in the
118
+ same blue/amber the chart uses for those node kinds. */
119
+ .brand-mark svg { height: 24px; width: auto; display: block; overflow: visible; }
120
+ .logo-node { fill: var(--blue); }
121
+ .logo-link { stroke: var(--violet); stroke-width: 3; stroke-linecap: round; }
122
+ .logo-decision { fill: var(--amber); }
123
+ .brand h1 {
124
+ font-size: 15px;
125
+ font-weight: 800;
126
+ letter-spacing: 0;
127
+ margin: 0;
128
+ }
129
+ .flow-heading { min-width: 0; flex: 1; }
130
+ .flow-heading .eyebrow {
131
+ color: var(--blue);
132
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
133
+ font-size: 9px;
134
+ font-weight: 700;
135
+ letter-spacing: 0.1em;
136
+ text-transform: uppercase;
137
+ }
138
+ .flow-heading h2 {
139
+ margin: 4px 0 0;
140
+ overflow: hidden;
141
+ text-overflow: ellipsis;
142
+ white-space: nowrap;
143
+ font-size: 17px;
144
+ }
145
+ .metrics { display: flex; gap: 8px; }
146
+ .metric {
147
+ display: flex;
148
+ align-items: baseline;
149
+ gap: 6px;
150
+ padding: 5px 9px;
151
+ border: 1px solid var(--line);
152
+ border-radius: var(--radius-sm);
153
+ background: color-mix(in srgb, var(--panel) 78%, transparent);
154
+ min-width: 0;
155
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02);
156
+ }
157
+ .metric strong { display: block; font-size: 14px; }
158
+ .metric span {
159
+ color: var(--muted);
160
+ font-size: 9px;
161
+ letter-spacing: 0.08em;
162
+ text-transform: uppercase;
163
+ }
164
+ aside {
165
+ min-height: 0;
166
+ background: var(--panel-2);
167
+ position: relative;
168
+ min-width: 0;
169
+ }
170
+ .left-rail { border-right: 1px solid var(--line); }
171
+ .right-rail { border-left: 1px solid var(--line); }
172
+ .rail-resizer {
173
+ position: absolute;
174
+ top: 0;
175
+ bottom: 0;
176
+ z-index: 8;
177
+ width: 14px;
178
+ cursor: ew-resize;
179
+ touch-action: none;
180
+ outline: none;
181
+ }
182
+ .rail-resizer-left { right: -7px; }
183
+ .rail-resizer-right { left: -7px; }
184
+ .rail-resizer::before {
185
+ content: "";
186
+ position: absolute;
187
+ top: 14px;
188
+ bottom: 14px;
189
+ left: 6px;
190
+ width: 2px;
191
+ border-radius: 999px;
192
+ background: transparent;
193
+ transition: background-color .15s ease, box-shadow .15s ease;
194
+ }
195
+ .rail-resizer:hover::before,
196
+ .rail-resizer:focus-visible::before,
197
+ body[data-rail-resizing] .rail-resizer::before {
198
+ background: var(--blue);
199
+ box-shadow: 0 0 0 3px rgba(47, 99, 239, .14);
200
+ }
201
+ .rail-resizer:focus-visible {
202
+ box-shadow: inset 0 0 0 2px var(--blue);
203
+ }
204
+ .rail-inner {
205
+ height: 100%;
206
+ display: flex;
207
+ flex-direction: column;
208
+ min-height: 0;
209
+ }
210
+ .rail-head { padding: 16px; border-bottom: 1px solid var(--line); }
211
+ .rail-head-row {
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: space-between;
215
+ gap: 10px;
216
+ margin-bottom: 10px;
217
+ }
218
+ .rail-title {
219
+ margin: 0;
220
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
221
+ font-size: 11px;
222
+ letter-spacing: 0.1em;
223
+ text-transform: uppercase;
224
+ color: var(--muted);
225
+ }
226
+ .rail-toggle {
227
+ min-height: 28px;
228
+ border: 1px solid var(--line);
229
+ border-radius: 999px;
230
+ background: var(--paper);
231
+ color: var(--muted);
232
+ padding: 0 10px;
233
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
234
+ font-size: 10px;
235
+ font-weight: 800;
236
+ letter-spacing: .06em;
237
+ text-transform: uppercase;
238
+ cursor: pointer;
239
+ }
240
+ .rail-toggle:hover,
241
+ .rail-toggle:focus-visible {
242
+ border-color: var(--line-strong);
243
+ color: var(--ink);
244
+ outline: none;
245
+ }
246
+ .rail-toggle[aria-pressed="true"] {
247
+ border-color: var(--amber);
248
+ background: color-mix(in srgb, var(--amber) 16%, transparent);
249
+ color: var(--amber);
250
+ }
251
+ /* Language dropdown above the tree (shown only for polyglot repos). */
252
+ .filter {
253
+ width: 100%;
254
+ min-width: 0;
255
+ margin-top: 8px;
256
+ border: 1px solid var(--line);
257
+ border-radius: var(--radius-sm);
258
+ background: var(--paper);
259
+ color: var(--ink);
260
+ padding: 7px 9px;
261
+ font-size: 12px;
262
+ outline: none;
263
+ cursor: pointer;
264
+ }
265
+ .codebase-head .filter:first-of-type { margin-top: 0; }
266
+ .compact-filter {
267
+ min-height: 32px;
268
+ padding-block: 5px;
269
+ color: var(--muted);
270
+ }
271
+ input.filter { cursor: text; }
272
+ .filter:focus { border-color: var(--blue); }
273
+ .tree { overflow: auto; min-height: 0; }
274
+ /* Directory tree (left rail). Rows indent by --depth; folders/files toggle, flow
275
+ rows select. Colors reuse the shared theme variables (no new hardcoded color). */
276
+ .tree { padding: 10px 10px 18px; }
277
+ .tree-dir, .tree-file, .tree-flow {
278
+ width: 100%;
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 8px;
282
+ text-align: left;
283
+ border: 1px solid transparent;
284
+ border-radius: var(--radius-sm);
285
+ background: transparent;
286
+ color: var(--ink);
287
+ padding: 6px 10px;
288
+ padding-left: calc(8px + var(--depth, 0) * 15px);
289
+ cursor: pointer;
290
+ font-size: 13px;
291
+ line-height: 1.3;
292
+ transition: background-color .12s ease, border-color .12s ease, color .12s ease;
293
+ }
294
+ .tree-dir:hover, .tree-file:hover, .tree-flow:hover {
295
+ background: var(--hover);
296
+ }
297
+ /* Keyboard focus must be distinct from hover (which only changes the background).
298
+ A theme-aware outline reads correctly in both light and dark. */
299
+ .tree-dir:focus-visible, .tree-file:focus-visible, .tree-flow:focus-visible {
300
+ outline: 2px solid var(--blue);
301
+ outline-offset: -2px;
302
+ }
303
+ .tree-label {
304
+ overflow: hidden;
305
+ text-overflow: ellipsis;
306
+ white-space: nowrap;
307
+ }
308
+ .tree-dir .tree-label { font-weight: 680; }
309
+ .tree-dir.active-folder, .tree-file.active-file {
310
+ background: var(--active);
311
+ border-color: var(--blue);
312
+ }
313
+ .tree-dir.active-folder .tree-label, .tree-file.active-file .tree-label { color: var(--ink); }
314
+ .tree-file.active-parent {
315
+ color: var(--ink);
316
+ background: var(--active);
317
+ border-color: var(--blue);
318
+ }
319
+ .tree-file.active-parent .tree-label { color: var(--ink); font-weight: 750; }
320
+ .tree-file.active-parent .tree-count {
321
+ background: var(--blue);
322
+ color: var(--on-accent);
323
+ }
324
+ .tree-caret {
325
+ flex: none;
326
+ width: 13px;
327
+ height: 13px;
328
+ fill: none;
329
+ stroke: var(--muted);
330
+ stroke-width: 2;
331
+ stroke-linecap: round;
332
+ stroke-linejoin: round;
333
+ transition: transform .12s ease;
334
+ }
335
+ .tree-caret.open { transform: rotate(90deg); }
336
+ .tree-file .tree-caret { stroke: var(--line-strong); }
337
+ .tree-count {
338
+ margin-left: auto;
339
+ flex: none;
340
+ min-width: 22px;
341
+ padding: 1px 7px;
342
+ border-radius: 999px;
343
+ background: var(--chip);
344
+ color: var(--muted);
345
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
346
+ font-size: 10px;
347
+ font-weight: 700;
348
+ }
349
+ .tree-children { display: block; }
350
+ .tree-children[hidden] { display: none; }
351
+ .tree-flow {
352
+ display: grid;
353
+ grid-template-columns: 5px minmax(0, 1fr);
354
+ grid-template-rows: auto auto;
355
+ gap: 9px;
356
+ row-gap: 6px;
357
+ padding-top: 8px;
358
+ padding-bottom: 9px;
359
+ border: 1px solid transparent;
360
+ border-radius: var(--radius-sm);
361
+ background: transparent;
362
+ }
363
+ .tree-flow::before {
364
+ content: "";
365
+ grid-row: 1 / span 2;
366
+ align-self: stretch;
367
+ background: color-mix(in srgb, var(--line-strong) 72%, transparent);
368
+ border-radius: 999px;
369
+ min-height: 39px;
370
+ }
371
+ .tree-flow.active::before {
372
+ background: var(--blue);
373
+ }
374
+ .tree-flow.active {
375
+ background: var(--active);
376
+ border-color: var(--blue);
377
+ }
378
+ .tree-flow-title {
379
+ grid-column: 2;
380
+ display: flex;
381
+ align-items: baseline;
382
+ gap: 8px;
383
+ min-width: 0;
384
+ }
385
+ .tree-flow strong {
386
+ display: block;
387
+ min-width: 0;
388
+ overflow: hidden;
389
+ text-overflow: ellipsis;
390
+ white-space: nowrap;
391
+ font-size: 13px;
392
+ font-weight: 780;
393
+ line-height: 1.2;
394
+ }
395
+ .tree-flow.active strong { color: var(--ink); }
396
+ .tree-flow-source {
397
+ margin-left: auto;
398
+ min-width: 0;
399
+ max-width: 42%;
400
+ overflow: hidden;
401
+ text-overflow: ellipsis;
402
+ white-space: nowrap;
403
+ color: var(--muted);
404
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
405
+ font-size: 9px;
406
+ font-weight: 650;
407
+ letter-spacing: .02em;
408
+ }
409
+ .tree-flow.active .tree-flow-source { color: color-mix(in srgb, var(--ink) 74%, var(--muted)); }
410
+ .tree-flow-badges {
411
+ grid-column: 2;
412
+ display: flex;
413
+ align-items: center;
414
+ gap: 5px;
415
+ margin-top: 0;
416
+ min-width: 0;
417
+ flex-wrap: wrap;
418
+ }
419
+ .tree-flow-badge {
420
+ display: inline-flex;
421
+ align-items: center;
422
+ flex: none;
423
+ min-height: 18px;
424
+ max-width: none;
425
+ padding: 2px 6px;
426
+ border: 1px solid var(--line);
427
+ border-radius: 999px;
428
+ background: var(--panel-2);
429
+ color: var(--muted);
430
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
431
+ font-size: 9px;
432
+ font-weight: 750;
433
+ line-height: 1;
434
+ letter-spacing: .04em;
435
+ text-transform: uppercase;
436
+ white-space: nowrap;
437
+ }
438
+ .tree-flow-badge.role-entry {
439
+ border-color: color-mix(in srgb, var(--blue), var(--line) 45%);
440
+ background: var(--chip);
441
+ color: var(--blue);
442
+ }
443
+ .tree-flow-badge.role-subflow {
444
+ border-color: color-mix(in srgb, var(--violet), var(--line) 45%);
445
+ color: var(--violet);
446
+ }
447
+ .tree-flow.tree-empty { color: var(--muted); font-style: italic; cursor: default; }
448
+ .tree-flow.tree-empty::before { background: transparent; }
449
+ main { position: relative; min-width: 0; min-height: 0; overflow: hidden; }
450
+ .canvas-toolbar {
451
+ position: absolute;
452
+ top: 14px;
453
+ right: 14px;
454
+ z-index: 2;
455
+ display: flex;
456
+ align-items: center;
457
+ gap: 8px;
458
+ }
459
+ .tool-group {
460
+ display: flex;
461
+ align-items: center;
462
+ gap: 2px;
463
+ padding: 3px;
464
+ border: 1px solid var(--line);
465
+ border-radius: 12px;
466
+ background: color-mix(in srgb, var(--panel) 88%, transparent);
467
+ box-shadow: 0 12px 28px rgba(0, 0, 0, .26);
468
+ backdrop-filter: blur(10px);
469
+ }
470
+ .tool {
471
+ border: 1px solid transparent;
472
+ border-radius: 9px;
473
+ background: transparent;
474
+ color: var(--ink);
475
+ min-width: 36px;
476
+ height: 36px;
477
+ cursor: pointer;
478
+ box-shadow: none;
479
+ transition: background-color .15s ease, border-color .15s ease, transform .15s ease;
480
+ }
481
+ .tool:hover, .tool:focus-visible {
482
+ background: var(--hover);
483
+ border-color: var(--line-strong);
484
+ outline: none;
485
+ transform: translateY(-1px);
486
+ }
487
+ .tool[aria-pressed="true"],
488
+ body[data-detail-open] .detail-tool {
489
+ border-color: var(--blue);
490
+ color: var(--blue);
491
+ background: color-mix(in srgb, var(--blue) 13%, transparent);
492
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--blue), transparent 84%);
493
+ }
494
+ .command-tool {
495
+ padding: 0 12px;
496
+ font-size: 11px;
497
+ font-weight: 800;
498
+ letter-spacing: .04em;
499
+ text-transform: uppercase;
500
+ }
501
+ .detail-tool { display: grid; place-items: center; font-weight: 800; font-family: ui-serif, Georgia, serif; }
502
+ body[data-runtime="react"] #emptyState {
503
+ display: none;
504
+ }
505
+ .typed-viewer-host {
506
+ position: absolute;
507
+ inset: 0;
508
+ background: var(--paper);
509
+ overflow: hidden;
510
+ }
511
+ .typed-viewer-host[hidden] { display: none; }
512
+ .typed-viewer-host .codedebrief-viewer-frame {
513
+ position: absolute;
514
+ inset: 0;
515
+ }
516
+ .typed-viewer-host .codedebrief-expand-progress {
517
+ position: absolute;
518
+ left: 16px;
519
+ bottom: 16px;
520
+ z-index: 6;
521
+ display: grid;
522
+ grid-template-columns: minmax(0, 1fr) auto;
523
+ align-items: center;
524
+ gap: 8px 12px;
525
+ width: min(360px, calc(100% - 32px));
526
+ padding: 11px 12px;
527
+ border: 1px solid var(--line-strong);
528
+ border-radius: var(--radius-sm);
529
+ background: var(--panel);
530
+ color: var(--ink);
531
+ box-shadow: 0 14px 28px rgba(0, 0, 0, .22);
532
+ }
533
+ .typed-viewer-host .codedebrief-expand-progress-label,
534
+ .typed-viewer-host .codedebrief-expand-progress-count {
535
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
536
+ font-size: 10px;
537
+ font-weight: 800;
538
+ letter-spacing: .05em;
539
+ text-transform: uppercase;
540
+ }
541
+ .typed-viewer-host .codedebrief-expand-progress-count {
542
+ color: var(--muted);
543
+ }
544
+ .typed-viewer-host .codedebrief-expand-progress-track {
545
+ grid-column: 1 / -1;
546
+ height: 6px;
547
+ overflow: hidden;
548
+ border-radius: 999px;
549
+ background: var(--chip);
550
+ }
551
+ .typed-viewer-host .codedebrief-expand-progress-value {
552
+ width: 0%;
553
+ height: 100%;
554
+ border-radius: inherit;
555
+ background: var(--blue);
556
+ transition: width .12s linear;
557
+ }
558
+ .typed-viewer-host .codedebrief-viewer {
559
+ width: 100%;
560
+ height: 100%;
561
+ display: block;
562
+ cursor: grab;
563
+ }
564
+ .typed-viewer-host .codedebrief-viewer.dragging {
565
+ cursor: grabbing;
566
+ }
567
+ .typed-viewer-host .canvas-hit-zone {
568
+ fill: transparent;
569
+ pointer-events: all;
570
+ }
571
+ .typed-viewer-host .edge-link-group {
572
+ cursor: pointer;
573
+ outline: none;
574
+ }
575
+ .typed-viewer-host .root-scope-hit,
576
+ .typed-viewer-host .edge-hit-path {
577
+ fill: none;
578
+ stroke: var(--paper);
579
+ stroke-width: 18;
580
+ stroke-linecap: round;
581
+ stroke-linejoin: round;
582
+ opacity: .01;
583
+ pointer-events: stroke;
584
+ vector-effect: non-scaling-stroke;
585
+ }
586
+ .typed-viewer-host .node {
587
+ cursor: pointer;
588
+ }
589
+ .typed-viewer-host .node.movable {
590
+ cursor: grab;
591
+ }
592
+ .typed-viewer-host .node.movable.dragging {
593
+ cursor: grabbing;
594
+ }
595
+ .typed-viewer-host .node .shape {
596
+ fill: color-mix(in srgb, var(--node-fill) 88%, var(--blue));
597
+ stroke: color-mix(in srgb, var(--blue) 88%, var(--ink));
598
+ stroke-width: 2.4;
599
+ filter: url(#typedNodeShadow);
600
+ vector-effect: non-scaling-stroke;
601
+ transition: filter .14s ease, stroke-width .14s ease, opacity .18s ease;
602
+ }
603
+ .typed-viewer-host .node.entry .shape {
604
+ fill: var(--node-fill);
605
+ stroke: var(--blue);
606
+ }
607
+ .typed-viewer-host .scope-node .shape {
608
+ fill: hsl(var(--scope-hue, 222) 70% 94%);
609
+ stroke: hsl(var(--scope-hue, 222) 72% 48%);
610
+ }
611
+ [data-theme="dark"] .typed-viewer-host .scope-node .shape {
612
+ fill: hsl(var(--scope-hue, 222) 44% 17%);
613
+ stroke: hsl(var(--scope-hue, 222) 88% 64%);
614
+ }
615
+ .typed-viewer-host .flow-node .shape {
616
+ fill: var(--node-fill);
617
+ stroke: var(--blue);
618
+ }
619
+ .typed-viewer-host .flow-node.flow-kind-route .shape {
620
+ fill: color-mix(in srgb, var(--fill-entry) 80%, var(--panel));
621
+ stroke: var(--blue);
622
+ }
623
+ .typed-viewer-host .flow-node.flow-kind-function .shape {
624
+ fill: color-mix(in srgb, var(--fill-call) 78%, var(--panel));
625
+ stroke: var(--violet);
626
+ }
627
+ .typed-viewer-host .flow-node.flow-kind-method .shape,
628
+ .typed-viewer-host .flow-node.flow-kind-service .shape {
629
+ fill: color-mix(in srgb, var(--fill-decision) 72%, var(--panel));
630
+ stroke: var(--amber);
631
+ }
632
+ .typed-viewer-host .flow-node.flow-kind-component .shape {
633
+ fill: color-mix(in srgb, var(--fill-terminal) 78%, var(--panel));
634
+ stroke: var(--cyan);
635
+ }
636
+ .typed-viewer-host .flow-node.flow-kind-data .shape {
637
+ fill: color-mix(in srgb, #12372f 78%, var(--panel));
638
+ stroke: #34d399;
639
+ }
640
+ .typed-viewer-host .flow-node.flow-kind-worker .shape,
641
+ .typed-viewer-host .flow-node.flow-kind-test .shape {
642
+ fill: color-mix(in srgb, var(--fill-error) 55%, var(--panel));
643
+ stroke: var(--coral);
644
+ }
645
+ .typed-viewer-host .flow-node.flow-kind-type .shape,
646
+ .typed-viewer-host .flow-node.flow-kind-other .shape {
647
+ fill: color-mix(in srgb, var(--node-fill) 88%, var(--panel));
648
+ stroke: color-mix(in srgb, var(--edge) 76%, var(--blue));
649
+ }
650
+ .typed-viewer-host .flow-node.flow-open .shape {
651
+ fill: color-mix(in srgb, var(--node-fill) 84%, var(--violet));
652
+ stroke: var(--violet);
653
+ stroke-width: 3;
654
+ filter: url(#typedNodeLift);
655
+ }
656
+ .typed-viewer-host .flow-node.selected .shape {
657
+ fill: color-mix(in srgb, var(--node-fill) 82%, var(--blue));
658
+ stroke-width: 3.4;
659
+ filter: url(#typedNodeLift);
660
+ }
661
+ .typed-viewer-host .flow-node.flow-open.selected .shape {
662
+ fill: color-mix(in srgb, var(--node-fill) 78%, var(--violet));
663
+ stroke: var(--violet);
664
+ }
665
+ .typed-viewer-host .node:hover .shape,
666
+ .typed-viewer-host .node:focus-visible .shape {
667
+ filter: url(#typedNodeLift);
668
+ stroke-width: 2.6;
669
+ }
670
+ .typed-viewer-host .node.edge-source .shape {
671
+ stroke: var(--amber);
672
+ stroke-width: 3.4;
673
+ filter: url(#typedNodeLift);
674
+ }
675
+ .typed-viewer-host .node.edge-target .shape {
676
+ stroke: var(--cyan);
677
+ stroke-width: 3.4;
678
+ filter: url(#typedNodeLift);
679
+ }
680
+ .typed-viewer-host .node.dimmed {
681
+ opacity: .32;
682
+ }
683
+ .typed-viewer-host .node text {
684
+ fill: var(--ink);
685
+ font-size: 13px;
686
+ font-weight: 650;
687
+ pointer-events: none;
688
+ }
689
+ .typed-viewer-host .node .meta {
690
+ fill: var(--muted);
691
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
692
+ font-size: 9px;
693
+ font-weight: 500;
694
+ letter-spacing: .05em;
695
+ text-transform: uppercase;
696
+ }
697
+ .typed-viewer-host .root-scope-edge-group,
698
+ .typed-viewer-host .scope-entry-edge-group {
699
+ cursor: pointer;
700
+ outline: none;
701
+ }
702
+ .typed-viewer-host .root-scope-link,
703
+ .typed-viewer-host .scope-entry-link {
704
+ marker-end: url(#typedArrow);
705
+ vector-effect: non-scaling-stroke;
706
+ }
707
+ .typed-viewer-host .root-scope-link.selected-link,
708
+ .typed-viewer-host .scope-entry-link.selected-link {
709
+ marker-end: url(#typedArrowFocus);
710
+ }
711
+ .typed-viewer-host .root-scope-link.incident,
712
+ .typed-viewer-host .scope-entry-link.incident {
713
+ stroke: var(--cyan);
714
+ stroke-width: 2.7;
715
+ marker-end: url(#typedArrowFocus);
716
+ opacity: .95;
717
+ }
718
+ .typed-viewer-host .root-scope-link.dimmed,
719
+ .typed-viewer-host .scope-entry-link.dimmed {
720
+ opacity: .18;
721
+ }
722
+ .typed-viewer-host .flow-call-edge-group {
723
+ cursor: pointer;
724
+ outline: none;
725
+ }
726
+ .typed-viewer-host .flow-call-link {
727
+ fill: none;
728
+ stroke: color-mix(in srgb, var(--edge) 82%, var(--blue));
729
+ stroke-width: 2;
730
+ marker-end: url(#typedArrow);
731
+ opacity: .78;
732
+ vector-effect: non-scaling-stroke;
733
+ }
734
+ .typed-viewer-host .flow-call-hit {
735
+ fill: none;
736
+ stroke: var(--paper);
737
+ stroke-width: 20;
738
+ stroke-linecap: round;
739
+ stroke-linejoin: round;
740
+ opacity: .01;
741
+ pointer-events: stroke;
742
+ vector-effect: non-scaling-stroke;
743
+ }
744
+ .typed-viewer-host .flow-call-link.selected-link {
745
+ stroke: var(--violet);
746
+ stroke-width: 3.2;
747
+ marker-end: url(#typedArrowFocus);
748
+ opacity: 1;
749
+ }
750
+ .typed-viewer-host .flow-call-link.incident {
751
+ stroke: var(--cyan);
752
+ stroke-width: 2.7;
753
+ marker-end: url(#typedArrowFocus);
754
+ opacity: .95;
755
+ }
756
+ .typed-viewer-host .flow-call-link.dimmed {
757
+ opacity: .16;
758
+ }
759
+ .typed-viewer-host .typed-arrow {
760
+ fill: color-mix(in srgb, var(--edge) 74%, var(--blue));
761
+ }
762
+ .typed-viewer-host .typed-arrow-focus {
763
+ fill: var(--violet);
764
+ }
765
+ .typed-viewer-host .flow-detail-edge {
766
+ fill: none;
767
+ stroke: color-mix(in srgb, var(--edge) 78%, var(--blue));
768
+ stroke-width: 1.8;
769
+ marker-end: url(#typedArrow);
770
+ opacity: .86;
771
+ }
772
+ .typed-viewer-host .flow-detail-edge-hit {
773
+ fill: none;
774
+ stroke: var(--paper);
775
+ stroke-width: 18;
776
+ stroke-linecap: round;
777
+ stroke-linejoin: round;
778
+ opacity: .01;
779
+ pointer-events: stroke;
780
+ vector-effect: non-scaling-stroke;
781
+ }
782
+ .typed-viewer-host .flow-detail-edge-group {
783
+ cursor: pointer;
784
+ outline: none;
785
+ }
786
+ .typed-viewer-host .flow-detail-edge.selected-link {
787
+ stroke: var(--violet);
788
+ stroke-width: 3;
789
+ marker-end: url(#typedArrowFocus);
790
+ opacity: 1;
791
+ }
792
+ .typed-viewer-host .flow-detail-edge.incident {
793
+ stroke: var(--cyan);
794
+ stroke-width: 2.6;
795
+ marker-end: url(#typedArrowFocus);
796
+ opacity: .98;
797
+ }
798
+ .typed-viewer-host .flow-detail-edge.dimmed,
799
+ .typed-viewer-host .flow-detail-label.dimmed {
800
+ opacity: .18;
801
+ }
802
+ .typed-viewer-host .flow-detail-label {
803
+ cursor: pointer;
804
+ outline: none;
805
+ }
806
+ .typed-viewer-host .flow-detail-label rect {
807
+ fill: var(--chip);
808
+ stroke: var(--line-strong);
809
+ stroke-width: 1;
810
+ }
811
+ .typed-viewer-host .flow-detail-label text {
812
+ fill: var(--muted);
813
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
814
+ font-size: 9px;
815
+ font-weight: 700;
816
+ letter-spacing: .04em;
817
+ text-transform: uppercase;
818
+ pointer-events: none;
819
+ }
820
+ .typed-viewer-host .detail-node .detail-shape {
821
+ fill: var(--node-fill);
822
+ stroke: var(--blue);
823
+ stroke-width: 2.2;
824
+ filter: url(#typedNodeShadow);
825
+ vector-effect: non-scaling-stroke;
826
+ }
827
+ .typed-viewer-host .detail-node {
828
+ cursor: pointer;
829
+ }
830
+ .typed-viewer-host .detail-node.movable {
831
+ cursor: grab;
832
+ }
833
+ .typed-viewer-host .detail-node.movable.dragging {
834
+ cursor: grabbing;
835
+ }
836
+ .typed-viewer-host .detail-node.selected .detail-shape {
837
+ stroke: var(--cyan);
838
+ stroke-width: 3.4;
839
+ filter: url(#typedNodeLift);
840
+ }
841
+ .typed-viewer-host .detail-node.dimmed {
842
+ opacity: .28;
843
+ }
844
+ .typed-viewer-host .detail-node.decision .detail-shape {
845
+ fill: var(--fill-decision);
846
+ stroke: var(--amber);
847
+ }
848
+ .typed-viewer-host .detail-node.call .detail-shape {
849
+ fill: var(--fill-call);
850
+ stroke: var(--violet);
851
+ }
852
+ .typed-viewer-host .detail-node.terminal .detail-shape {
853
+ fill: var(--fill-terminal);
854
+ stroke: var(--cyan);
855
+ }
856
+ .typed-viewer-host .detail-node text {
857
+ pointer-events: none;
858
+ }
859
+ .typed-viewer-host .detail-kind {
860
+ fill: var(--muted);
861
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
862
+ font-size: 9px;
863
+ font-weight: 700;
864
+ letter-spacing: .05em;
865
+ text-transform: uppercase;
866
+ }
867
+ .typed-viewer-host .detail-label {
868
+ fill: var(--ink);
869
+ font-size: 12px;
870
+ font-weight: 700;
871
+ }
872
+ .typed-viewer-host .detail-meta {
873
+ fill: var(--muted);
874
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
875
+ font-size: 8px;
876
+ font-weight: 600;
877
+ letter-spacing: .03em;
878
+ text-transform: uppercase;
879
+ }
880
+ .typed-viewer-host .codedebrief-viewer[data-lod="overview"] .node .meta,
881
+ .typed-viewer-host .codedebrief-viewer[data-lod="overview"] .detail-kind,
882
+ .typed-viewer-host .codedebrief-viewer[data-lod="overview"] .detail-meta,
883
+ .typed-viewer-host .codedebrief-viewer[data-lod="overview"] .flow-detail-label {
884
+ opacity: 0;
885
+ pointer-events: none;
886
+ }
887
+ .typed-viewer-host .codedebrief-viewer[data-lod="overview"] .flow-detail-edge:not(.selected-link):not(.incident),
888
+ .typed-viewer-host .codedebrief-viewer[data-lod="overview"] .flow-call-link:not(.selected-link):not(.incident) {
889
+ opacity: .48;
890
+ }
891
+ .typed-viewer-host .codedebrief-viewer[data-lod="overview"] .node text:not(.meta),
892
+ .typed-viewer-host .codedebrief-viewer[data-lod="overview"] .detail-label {
893
+ opacity: .82;
894
+ }
895
+ .empty {
896
+ position: absolute;
897
+ inset: 0;
898
+ display: none;
899
+ place-items: center;
900
+ text-align: center;
901
+ color: var(--muted);
902
+ }
903
+ .legend {
904
+ margin-top: auto;
905
+ border-top: 1px solid var(--line);
906
+ padding: 14px 20px;
907
+ display: grid;
908
+ grid-template-columns: 1fr 1fr;
909
+ gap: 8px;
910
+ color: var(--muted);
911
+ font-size: 10px;
912
+ }
913
+ .legend span { display: flex; align-items: center; }
914
+ .legend span::before {
915
+ content: "";
916
+ width: 9px;
917
+ height: 9px;
918
+ border-radius: 3px;
919
+ display: inline-block;
920
+ margin-right: 8px;
921
+ background: var(--blue);
922
+ }
923
+ .legend .decision::before { background: var(--amber); transform: rotate(45deg); }
924
+ .legend .call::before { background: var(--violet); }
925
+ .legend .outcome::before { background: var(--cyan); border-radius: 50%; }
926
+ .legend .gap::before { background: var(--coral); border-radius: 50%; }
927
+ .node { cursor: grab; }
928
+ .node.static { cursor: pointer; }
929
+ .node.dragging { cursor: grabbing; }
930
+ .node .shape {
931
+ fill: var(--node-fill);
932
+ stroke: var(--blue);
933
+ stroke-width: 2;
934
+ filter: url(#nodeShadow);
935
+ transition: filter .14s ease, stroke-width .14s ease, opacity .18s ease;
936
+ }
937
+ .node.entry .shape { fill: var(--fill-entry); stroke: var(--blue); }
938
+ .node.decision .shape { fill: var(--fill-decision); stroke: var(--amber); }
939
+ .node.call .shape { fill: var(--fill-call); stroke: var(--violet); }
940
+ .node.terminal .shape { fill: var(--fill-terminal); stroke: var(--cyan); }
941
+ .node.error .shape { fill: var(--fill-error); stroke: var(--coral); }
942
+ .node:hover .shape, .node:focus-visible .shape { filter: url(#nodeLift); stroke-width: 2.6; }
943
+ /* Keyboard focus remains visible without drawing rectangular CSS boxes around SVG nodes. */
944
+ .node:focus-visible { outline: none; }
945
+ .node:focus-visible .shape {
946
+ stroke-width: 3.2;
947
+ filter: url(#nodeLift);
948
+ }
949
+ .node.dragging .shape { filter: url(#nodeLift); }
950
+ .node.selected .shape { stroke-width: 3.4; filter: url(#nodeLift); }
951
+ .node.edge-source .shape {
952
+ stroke: var(--amber);
953
+ stroke-width: 3.4;
954
+ filter: url(#nodeLift);
955
+ }
956
+ .node.edge-target .shape {
957
+ stroke: var(--cyan);
958
+ stroke-width: 3.4;
959
+ filter: url(#nodeLift);
960
+ }
961
+ .node.dimmed { opacity: .26; }
962
+ .node-kind-badge {
963
+ pointer-events: none;
964
+ }
965
+ .node text {
966
+ fill: var(--ink);
967
+ font-size: 13px;
968
+ font-weight: 650;
969
+ pointer-events: none;
970
+ }
971
+ .node .node-kind-badge rect {
972
+ fill: var(--panel);
973
+ stroke: var(--line);
974
+ stroke-width: 1;
975
+ filter: url(#nodeShadow);
976
+ }
977
+ .node .node-kind-badge text {
978
+ fill: var(--muted);
979
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
980
+ font-size: 9px;
981
+ font-weight: 800;
982
+ letter-spacing: .06em;
983
+ text-transform: uppercase;
984
+ }
985
+ .node .meta {
986
+ fill: var(--muted);
987
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
988
+ font-size: 9px;
989
+ font-weight: 500;
990
+ letter-spacing: .05em;
991
+ text-transform: uppercase;
992
+ }
993
+ .arrow { fill: var(--edge); transition: fill .18s ease; }
994
+ .arrow-focus { fill: var(--violet); }
995
+ .edge {
996
+ fill: none;
997
+ stroke: var(--edge);
998
+ stroke-width: 2;
999
+ stroke-linecap: round;
1000
+ stroke-linejoin: round;
1001
+ marker-end: url(#arrow);
1002
+ transition: stroke .18s ease, opacity .18s ease;
1003
+ }
1004
+ .edge-hit {
1005
+ cursor: pointer;
1006
+ pointer-events: all;
1007
+ outline: none;
1008
+ }
1009
+ .edge-hit:focus,
1010
+ .edge-hit:focus-visible,
1011
+ .edge-hit-segment:focus,
1012
+ .edge-hit-segment:focus-visible { outline: none; }
1013
+ .edge-hit-segment {
1014
+ fill: #fff;
1015
+ opacity: 0;
1016
+ stroke: none;
1017
+ pointer-events: all;
1018
+ }
1019
+ .edge.incident { stroke: var(--blue); stroke-width: 2.6; }
1020
+ .root-scope-link,
1021
+ .scope-entry-link {
1022
+ stroke: color-mix(in srgb, var(--edge) 74%, var(--blue));
1023
+ stroke-width: 1.8;
1024
+ opacity: .82;
1025
+ }
1026
+ .edge.selected-link {
1027
+ stroke: var(--violet);
1028
+ stroke-width: 3.2;
1029
+ opacity: 1;
1030
+ }
1031
+ .edge.focus-hidden { opacity: .12; }
1032
+ .edge.dimmed { opacity: .18; }
1033
+ .edge-focus {
1034
+ fill: none;
1035
+ stroke: transparent;
1036
+ stroke-width: 0;
1037
+ stroke-linecap: round;
1038
+ marker-end: url(#arrowFocus);
1039
+ pointer-events: none;
1040
+ opacity: 0;
1041
+ transition: stroke .18s ease, opacity .18s ease;
1042
+ }
1043
+ .edge-focus.selected-link {
1044
+ stroke: var(--violet);
1045
+ stroke-width: 3.2;
1046
+ opacity: 1;
1047
+ }
1048
+ .edge-label-wrap { transition: opacity .18s ease; pointer-events: none; }
1049
+ .edge-label-wrap[role="button"] { pointer-events: auto; cursor: pointer; }
1050
+ .edge-label-wrap.selected-link .edge-label-bg {
1051
+ stroke: var(--violet);
1052
+ stroke-width: 2;
1053
+ }
1054
+ .edge-label-wrap.selected-link .edge-label {
1055
+ fill: var(--violet);
1056
+ }
1057
+ .edge-label-wrap.dimmed { opacity: .18; }
1058
+ .edge-label-bg {
1059
+ fill: var(--panel);
1060
+ stroke: var(--line);
1061
+ stroke-width: 1;
1062
+ opacity: .94;
1063
+ }
1064
+ .branch-exit-chip .edge-label-bg {
1065
+ fill: var(--chip);
1066
+ stroke: var(--line-strong);
1067
+ }
1068
+ .edge-label {
1069
+ fill: var(--ink);
1070
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1071
+ font-size: 10px;
1072
+ font-weight: 800;
1073
+ letter-spacing: .03em;
1074
+ text-transform: uppercase;
1075
+ }
1076
+ .decision-spine { stroke: var(--cyan); opacity: .16; stroke-width: 5; stroke-dasharray: 3 10; }
1077
+
1078
+ /* --- Codebase canvas (L0 scopes / L1 files+flows) ------------------------- */
1079
+ .breadcrumb {
1080
+ position: absolute;
1081
+ top: 16px;
1082
+ left: 16px;
1083
+ z-index: 2;
1084
+ display: flex;
1085
+ align-items: center;
1086
+ gap: 4px;
1087
+ max-width: calc(100% - 220px);
1088
+ flex-wrap: wrap;
1089
+ }
1090
+ .crumb {
1091
+ border: 1px solid var(--line);
1092
+ border-radius: 999px;
1093
+ background: var(--tool);
1094
+ color: var(--muted);
1095
+ padding: 5px 12px;
1096
+ font-size: 11px;
1097
+ font-weight: 650;
1098
+ cursor: pointer;
1099
+ backdrop-filter: blur(8px);
1100
+ box-shadow: 0 8px 18px rgba(24, 40, 76, .12);
1101
+ transition: border-color .15s ease, color .15s ease;
1102
+ }
1103
+ .crumb:hover { border-color: var(--blue); color: var(--ink); }
1104
+ /* Keyboard focus must be distinct from hover: a theme-aware ring, correct in both
1105
+ themes, instead of folding focus into the hover border. */
1106
+ .crumb:focus-visible {
1107
+ color: var(--ink);
1108
+ outline: 2px solid var(--blue);
1109
+ outline-offset: 2px;
1110
+ }
1111
+ .crumb.current { color: var(--ink); border-color: var(--line-strong); cursor: default; }
1112
+ .crumb-sep { color: var(--muted); font-size: 11px; }
1113
+ .dimmed-edge { opacity: .34; }
1114
+ .progressive-row-rule {
1115
+ stroke: var(--line);
1116
+ stroke-width: 1;
1117
+ stroke-dasharray: 2 10;
1118
+ opacity: .62;
1119
+ }
1120
+ .progressive-row-label {
1121
+ fill: var(--muted);
1122
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1123
+ font-size: 10px;
1124
+ font-weight: 800;
1125
+ letter-spacing: .08em;
1126
+ text-transform: uppercase;
1127
+ pointer-events: none;
1128
+ }
1129
+ .tree-empty-state {
1130
+ padding: 12px 14px;
1131
+ color: var(--muted);
1132
+ font-size: 12px;
1133
+ line-height: 1.45;
1134
+ }
1135
+ /* Flow node (L1). All visible canvas blocks can be hand-positioned; reset restores
1136
+ the automatic progressive layout. */
1137
+ .flow-node { cursor: pointer; }
1138
+ .flow-node.movable { cursor: grab; }
1139
+ .flow-node.movable.dragging { cursor: grabbing; }
1140
+ .flow-node.entry .shape { fill: var(--node-fill); stroke: var(--blue); }
1141
+ .flow-node.action .shape { fill: var(--node-fill); stroke: var(--blue); }
1142
+ .flow-node.subflow .shape { rx: 8; }
1143
+ .flow-node.route-flow .shape {
1144
+ fill: var(--node-fill);
1145
+ stroke: var(--cyan);
1146
+ stroke-width: 2.6;
1147
+ }
1148
+ .flow-node.active-area .shape { stroke-width: 2.4; }
1149
+ /* A flow node whose decision sub-graph is unfolded in place (L2 inline). */
1150
+ .flow-node.flow-open .shape {
1151
+ fill: var(--node-fill);
1152
+ stroke: var(--violet);
1153
+ stroke-width: 3;
1154
+ }
1155
+ .flow-node.edge-source .shape {
1156
+ stroke: var(--amber);
1157
+ stroke-width: 3;
1158
+ }
1159
+ .flow-node.edge-target .shape {
1160
+ stroke: var(--cyan);
1161
+ stroke-width: 3;
1162
+ }
1163
+ .flow-source-tag {
1164
+ fill: var(--muted);
1165
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1166
+ font-size: 9px;
1167
+ font-weight: 650;
1168
+ letter-spacing: .03em;
1169
+ pointer-events: none;
1170
+ }
1171
+ .flow-expand-pill rect {
1172
+ fill: var(--tool);
1173
+ stroke: var(--line);
1174
+ stroke-width: 1;
1175
+ opacity: .96;
1176
+ }
1177
+ .flow-expand-pill text {
1178
+ fill: var(--muted);
1179
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1180
+ font-size: 9px;
1181
+ font-weight: 800;
1182
+ letter-spacing: .04em;
1183
+ text-transform: uppercase;
1184
+ pointer-events: none;
1185
+ }
1186
+ .flow-node.flow-open .flow-expand-pill rect,
1187
+ .flow-node.route-flow .flow-expand-pill rect {
1188
+ stroke: var(--violet);
1189
+ }
1190
+
1191
+ /* Inline L2: the expanded flow's workflow flowchart, drawn under its node inside the
1192
+ L1 canvas. The backing panel groups it; the connector ties it to the flow node. */
1193
+ .inline-flow-link {
1194
+ fill: none;
1195
+ stroke: var(--violet);
1196
+ stroke-width: 2;
1197
+ stroke-dasharray: 4 5;
1198
+ opacity: .7;
1199
+ }
1200
+ /* --- Right column: analysis health and source panels --------------------- */
1201
+ /* The rail splits vertically into panels that each scroll independently, so a long
1202
+ snippet scrolls inside its panel and never pushes the others off-screen. */
1203
+ .right-rail .rail-inner { display: flex; flex-direction: column; min-height: 0; }
1204
+ .detail-drawer-head {
1205
+ display: flex;
1206
+ align-items: center;
1207
+ justify-content: space-between;
1208
+ gap: 12px;
1209
+ padding: 12px 16px;
1210
+ border-bottom: 1px solid var(--line);
1211
+ color: var(--muted);
1212
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1213
+ font-size: 11px;
1214
+ font-weight: 800;
1215
+ letter-spacing: .08em;
1216
+ text-transform: uppercase;
1217
+ flex: none;
1218
+ }
1219
+ .panel-stack-tools {
1220
+ display: inline-flex;
1221
+ align-items: center;
1222
+ gap: 4px;
1223
+ margin-left: auto;
1224
+ padding: 2px;
1225
+ border: 1px solid var(--line);
1226
+ border-radius: var(--radius-sm);
1227
+ background: var(--panel-2);
1228
+ }
1229
+ .panel-stack-control {
1230
+ display: grid;
1231
+ place-items: center;
1232
+ width: 28px;
1233
+ height: 28px;
1234
+ border: 1px solid transparent;
1235
+ border-radius: var(--radius-sm);
1236
+ background: var(--panel);
1237
+ color: var(--ink);
1238
+ cursor: pointer;
1239
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1240
+ font-size: 15px;
1241
+ font-weight: 850;
1242
+ line-height: 1;
1243
+ }
1244
+ .panel-stack-control:hover,
1245
+ .panel-stack-control:focus-visible {
1246
+ background: var(--active);
1247
+ border-color: var(--blue);
1248
+ color: var(--ink);
1249
+ outline: none;
1250
+ }
1251
+ .panel-close {
1252
+ width: 32px;
1253
+ height: 32px;
1254
+ border: 1px solid var(--line);
1255
+ border-radius: 999px;
1256
+ background: var(--panel);
1257
+ color: var(--ink);
1258
+ cursor: pointer;
1259
+ font-size: 20px;
1260
+ line-height: 1;
1261
+ display: grid;
1262
+ place-items: center;
1263
+ }
1264
+ .panel-close:hover,
1265
+ .panel-close:focus-visible {
1266
+ border-color: var(--blue);
1267
+ color: var(--blue);
1268
+ outline: none;
1269
+ }
1270
+ .panel {
1271
+ display: flex;
1272
+ flex-direction: column;
1273
+ min-height: 0;
1274
+ overflow: hidden;
1275
+ }
1276
+ .panel-quality { flex: 0 0 auto; max-height: 260px; border-bottom: 1px solid var(--line); }
1277
+ .panel-source { flex: 1 1 48%; border-bottom: 1px solid var(--line); }
1278
+ .panel[data-collapsed] {
1279
+ flex: 0 0 auto;
1280
+ max-height: none;
1281
+ }
1282
+ .panel-head {
1283
+ display: flex;
1284
+ align-items: center;
1285
+ gap: 10px;
1286
+ padding: 14px 18px 10px;
1287
+ border-bottom: 1px solid var(--line);
1288
+ flex: none;
1289
+ transition: background .14s ease;
1290
+ }
1291
+ .panel-head[data-panel-heading] {
1292
+ cursor: pointer;
1293
+ user-select: none;
1294
+ position: relative;
1295
+ }
1296
+ .panel-head[data-panel-heading]:hover {
1297
+ background: var(--hover);
1298
+ }
1299
+ .panel-head[data-panel-heading]:focus-visible {
1300
+ outline: 2px solid var(--blue);
1301
+ outline-offset: -2px;
1302
+ }
1303
+ .panel[data-collapsed] .panel-head { border-bottom: 0; }
1304
+ .panel[data-collapsed] .panel-head[data-panel-heading] {
1305
+ background: var(--panel);
1306
+ }
1307
+ .panel-head .rail-title { margin: 0; }
1308
+ .panel-collapse-toggle {
1309
+ flex: none;
1310
+ display: grid;
1311
+ place-items: center;
1312
+ width: 30px;
1313
+ height: 30px;
1314
+ border: 1px solid var(--blue);
1315
+ border-radius: 8px;
1316
+ background: var(--active);
1317
+ color: var(--blue);
1318
+ cursor: pointer;
1319
+ }
1320
+ .panel-head[data-panel-heading]:hover .panel-collapse-toggle {
1321
+ background: var(--blue);
1322
+ border-color: var(--blue);
1323
+ color: var(--on-accent);
1324
+ }
1325
+ .panel-collapse-toggle:hover,
1326
+ .panel-collapse-toggle:focus-visible {
1327
+ border-color: var(--blue);
1328
+ color: var(--blue);
1329
+ outline: none;
1330
+ }
1331
+ .panel-chevron {
1332
+ display: block;
1333
+ width: 9px;
1334
+ height: 9px;
1335
+ border: solid currentColor;
1336
+ border-width: 0 2.5px 2.5px 0;
1337
+ transform: translateY(-1px) rotate(45deg);
1338
+ transition: transform .14s ease;
1339
+ }
1340
+ .panel[data-collapsed] .panel-chevron {
1341
+ transform: translateX(-1px) rotate(-45deg);
1342
+ }
1343
+ .panel-file {
1344
+ margin-left: auto;
1345
+ color: var(--muted);
1346
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1347
+ font-size: 10px;
1348
+ overflow: hidden;
1349
+ text-overflow: ellipsis;
1350
+ white-space: nowrap;
1351
+ max-width: 60%;
1352
+ }
1353
+ .panel-count {
1354
+ margin-left: auto;
1355
+ flex: none;
1356
+ min-width: 20px;
1357
+ padding: 1px 8px;
1358
+ border-radius: 999px;
1359
+ background: var(--chip);
1360
+ color: var(--muted);
1361
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1362
+ font-size: 10px;
1363
+ font-weight: 700;
1364
+ text-align: center;
1365
+ }
1366
+ .panel-action {
1367
+ margin-left: auto;
1368
+ flex: none;
1369
+ min-height: 24px;
1370
+ border: 1px solid var(--line);
1371
+ border-radius: var(--radius-sm);
1372
+ background: var(--panel);
1373
+ color: var(--muted);
1374
+ padding: 3px 8px;
1375
+ font-size: 10px;
1376
+ font-weight: 750;
1377
+ letter-spacing: .05em;
1378
+ text-transform: uppercase;
1379
+ cursor: pointer;
1380
+ }
1381
+ .panel-action:hover,
1382
+ .panel-action:focus-visible,
1383
+ .panel-action.active {
1384
+ border-color: var(--blue);
1385
+ color: var(--blue);
1386
+ outline: none;
1387
+ }
1388
+ .panel-head .panel-action + .panel-count { margin-left: 0; }
1389
+ .panel-body { overflow: auto; min-height: 0; flex: 1 1 auto; padding: 14px 16px; }
1390
+ .panel-empty { color: var(--muted); font-size: 12px; line-height: 1.55; margin: 6px 0; }
1391
+ .quality-scroll {
1392
+ display: grid;
1393
+ gap: 10px;
1394
+ padding: 12px 16px 14px;
1395
+ }
1396
+ .quality-metrics {
1397
+ display: grid;
1398
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1399
+ gap: 8px;
1400
+ }
1401
+ .quality-metric,
1402
+ .quality-signal {
1403
+ min-width: 0;
1404
+ border: 1px solid var(--line);
1405
+ border-radius: var(--radius-sm);
1406
+ background: var(--panel-2);
1407
+ }
1408
+ .quality-metric {
1409
+ display: grid;
1410
+ gap: 3px;
1411
+ padding: 8px 10px;
1412
+ }
1413
+ .quality-metric strong {
1414
+ min-width: 0;
1415
+ overflow: hidden;
1416
+ text-overflow: ellipsis;
1417
+ white-space: nowrap;
1418
+ color: var(--ink);
1419
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1420
+ font-size: 16px;
1421
+ font-weight: 800;
1422
+ line-height: 1.1;
1423
+ }
1424
+ .quality-label {
1425
+ min-width: 0;
1426
+ overflow: hidden;
1427
+ text-overflow: ellipsis;
1428
+ white-space: nowrap;
1429
+ color: var(--muted);
1430
+ font-size: 10px;
1431
+ font-weight: 750;
1432
+ letter-spacing: .05em;
1433
+ text-transform: uppercase;
1434
+ }
1435
+ .quality-signals {
1436
+ display: grid;
1437
+ gap: 6px;
1438
+ }
1439
+ .quality-signal {
1440
+ display: flex;
1441
+ align-items: center;
1442
+ justify-content: space-between;
1443
+ gap: 8px;
1444
+ padding: 7px 9px;
1445
+ }
1446
+ .quality-signal.attention,
1447
+ .quality-metric.attention {
1448
+ border-color: color-mix(in srgb, var(--amber) 55%, var(--line));
1449
+ background: color-mix(in srgb, var(--amber) 10%, var(--panel-2));
1450
+ }
1451
+ .quality-value {
1452
+ flex: none;
1453
+ max-width: 45%;
1454
+ overflow: hidden;
1455
+ text-overflow: ellipsis;
1456
+ white-space: nowrap;
1457
+ color: var(--ink);
1458
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1459
+ font-size: 11px;
1460
+ font-weight: 800;
1461
+ }
1462
+ .quality-chips {
1463
+ display: flex;
1464
+ flex-wrap: wrap;
1465
+ gap: 5px;
1466
+ }
1467
+ .quality-chip {
1468
+ display: inline-flex;
1469
+ align-items: center;
1470
+ min-height: 20px;
1471
+ max-width: 100%;
1472
+ padding: 2px 7px;
1473
+ border: 1px solid var(--line);
1474
+ border-radius: 999px;
1475
+ background: var(--chip);
1476
+ color: var(--muted);
1477
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1478
+ font-size: 10px;
1479
+ font-weight: 750;
1480
+ white-space: nowrap;
1481
+ }
1482
+
1483
+ /* Source code: monospace lines with a gutter. Source text lives ONLY in text nodes
1484
+ (panels.js never uses innerHTML for it). pre-wrap keeps long lines readable without
1485
+ a horizontal scroll fight; the panel scrolls vertically. */
1486
+ .source-scroll { padding: 8px 0 8px; }
1487
+ .source-scroll .panel-empty { margin: 6px 16px; }
1488
+ .code-block {
1489
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
1490
+ font-size: 12px;
1491
+ line-height: 1.55;
1492
+ }
1493
+ .code-line {
1494
+ display: grid;
1495
+ grid-template-columns: 30px 1fr;
1496
+ gap: 8px;
1497
+ width: 100%;
1498
+ text-align: left;
1499
+ border: 0;
1500
+ background: transparent;
1501
+ color: var(--ink);
1502
+ padding: 0 10px 0 6px;
1503
+ margin: 0;
1504
+ font: inherit;
1505
+ cursor: default;
1506
+ }
1507
+ .code-line.has-node { cursor: pointer; }
1508
+ .code-line.has-node:hover { background: var(--hover); }
1509
+ .code-line.has-node:focus-visible {
1510
+ outline: 2px solid var(--blue);
1511
+ outline-offset: -2px;
1512
+ }
1513
+ /* The selected block's source line(s) -- the shared accent, same --blue as the canvas
1514
+ block and tree file. A left bar makes a multi-line span read as one selection. */
1515
+ .code-line.selected {
1516
+ background: var(--active);
1517
+ box-shadow: inset 3px 0 0 var(--blue);
1518
+ }
1519
+ .code-gutter {
1520
+ color: var(--muted);
1521
+ text-align: right;
1522
+ user-select: none;
1523
+ font-variant-numeric: tabular-nums;
1524
+ }
1525
+ .code-text { white-space: pre-wrap; overflow-wrap: anywhere; }
1526
+ .code-line.selected .code-gutter { color: var(--blue); }
1527
+
1528
+ /* "N more lines not shown" marker under an elided (capped) source snippet. */
1529
+ .code-elided {
1530
+ margin: 8px 14px 0;
1531
+ padding-top: 8px;
1532
+ border-top: 1px dashed var(--line);
1533
+ color: var(--muted);
1534
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1535
+ font-size: 11px;
1536
+ }
1537
+ /* Visually-hidden aria-live status region: present for screen readers, off-screen for
1538
+ sighted users. Announces source/detail changes on selection change. */
1539
+ .sr-only {
1540
+ position: absolute;
1541
+ width: 1px;
1542
+ height: 1px;
1543
+ padding: 0;
1544
+ margin: -1px;
1545
+ overflow: hidden;
1546
+ clip: rect(0, 0, 0, 0);
1547
+ white-space: nowrap;
1548
+ border: 0;
1549
+ }
1550
+
1551
+ /* --- Full-screen canvas (Phase 4.5) --------------------------------------- */
1552
+ /* The toggle is aria-pressed; show its pressed state distinctly. */
1553
+ /* In-page maximize fallback (when the Fullscreen API is unavailable or rejected):
1554
+ the <main> canvas covers the viewport and the side rails are hidden. The browser
1555
+ Fullscreen API path maximizes <main> itself, so the same panel-hiding applies via
1556
+ :fullscreen too. Selection state is untouched, so the panels are correct on exit. */
1557
+ body[data-fullscreen] .left-rail,
1558
+ body[data-fullscreen] .right-rail { display: none; }
1559
+ body[data-fullscreen] #menuButton { display: none; }
1560
+ body[data-fullscreen] main {
1561
+ position: fixed;
1562
+ inset: 0;
1563
+ z-index: 50;
1564
+ background:
1565
+ linear-gradient(var(--grid) 1px, transparent 1px),
1566
+ linear-gradient(90deg, var(--grid) 1px, transparent 1px),
1567
+ var(--paper);
1568
+ background-size: 26px 26px;
1569
+ }
1570
+ /* When the real Fullscreen API maximizes <main>, give it the paper background too
1571
+ (the element is lifted out of the normal flow against a black backdrop otherwise). */
1572
+ main:fullscreen {
1573
+ background:
1574
+ linear-gradient(var(--grid) 1px, transparent 1px),
1575
+ linear-gradient(90deg, var(--grid) 1px, transparent 1px),
1576
+ var(--paper);
1577
+ background-size: 26px 26px;
1578
+ }
1579
+
1580
+ @media (min-width: 1051px) {
1581
+ body[data-nav-closed] .shell {
1582
+ grid-template-columns: 0 minmax(0, 1fr) var(--right-rail-width);
1583
+ }
1584
+ body[data-detail-closed] .shell {
1585
+ grid-template-columns: var(--left-rail-width) minmax(0, 1fr) 0;
1586
+ }
1587
+ body[data-nav-closed][data-detail-closed] .shell {
1588
+ grid-template-columns: 0 minmax(0, 1fr) 0;
1589
+ }
1590
+ body[data-nav-closed] .left-rail,
1591
+ body[data-detail-closed] .right-rail {
1592
+ border: 0;
1593
+ overflow: hidden;
1594
+ pointer-events: none;
1595
+ }
1596
+ body[data-nav-closed] .left-rail .rail-inner,
1597
+ body[data-nav-closed] .left-rail .rail-resizer,
1598
+ body[data-detail-closed] .right-rail .rail-inner,
1599
+ body[data-detail-closed] .right-rail .rail-resizer {
1600
+ visibility: hidden;
1601
+ }
1602
+ }
1603
+
1604
+ @media (max-width: 1050px) {
1605
+ .shell { grid-template-columns: minmax(240px, min(var(--left-rail-width), 42vw)) minmax(0,1fr); }
1606
+ .rail-resizer { display: none; }
1607
+ body[data-nav-closed] .shell {
1608
+ grid-template-columns: 0 minmax(0, 1fr);
1609
+ }
1610
+ body[data-nav-closed] .left-rail {
1611
+ border: 0;
1612
+ overflow: hidden;
1613
+ pointer-events: none;
1614
+ }
1615
+ body[data-nav-closed] .left-rail .rail-inner {
1616
+ visibility: hidden;
1617
+ }
1618
+ body[data-detail-open] main::after,
1619
+ body[data-nav-open] main::after {
1620
+ content: "";
1621
+ position: absolute;
1622
+ inset: 0;
1623
+ z-index: 3;
1624
+ background: var(--drawer-backdrop);
1625
+ pointer-events: none;
1626
+ }
1627
+ .right-rail {
1628
+ position: fixed;
1629
+ right: 0;
1630
+ top: 66px;
1631
+ bottom: 0;
1632
+ width: min(var(--right-rail-width), calc(100vw - 72px));
1633
+ z-index: 5;
1634
+ box-shadow: var(--shadow);
1635
+ transform: translateX(100%);
1636
+ transition: transform .2s ease;
1637
+ }
1638
+ .right-rail.open { transform: translateX(0); }
1639
+ .detail-drawer-head { display: flex; }
1640
+ .metrics { display: none; }
1641
+ .detail-tool { display: grid; place-items: center; }
1642
+ }
1643
+ @media (min-width: 701px) and (max-width: 1050px) {
1644
+ body[data-detail-open] .canvas-toolbar {
1645
+ left: 14px;
1646
+ right: calc(min(var(--right-rail-width), calc(100vw - 72px)) + 14px);
1647
+ z-index: 4;
1648
+ flex-wrap: wrap;
1649
+ align-items: flex-start;
1650
+ justify-content: flex-end;
1651
+ }
1652
+ }
1653
+ @media (max-width: 700px) {
1654
+ .shell { grid-template-columns: 1fr; }
1655
+ header { padding: 0 14px; }
1656
+ .brand { min-width: 0; }
1657
+ .brand small, .flow-heading { display: none; }
1658
+ .canvas-toolbar {
1659
+ top: 14px;
1660
+ right: 10px;
1661
+ gap: 5px;
1662
+ }
1663
+ .tool-group { gap: 1px; padding: 2px; }
1664
+ .tool {
1665
+ min-width: 36px;
1666
+ height: 36px;
1667
+ border-radius: 9px;
1668
+ }
1669
+ .breadcrumb {
1670
+ top: 14px;
1671
+ left: 10px;
1672
+ max-width: calc(100% - 170px);
1673
+ }
1674
+ .crumb {
1675
+ padding: 5px 9px;
1676
+ font-size: 10px;
1677
+ }
1678
+ .left-rail {
1679
+ position: fixed;
1680
+ left: 0;
1681
+ top: 78px;
1682
+ bottom: 0;
1683
+ width: min(var(--left-rail-width), calc(100vw - 48px));
1684
+ z-index: 5;
1685
+ box-shadow: var(--shadow);
1686
+ transform: translateX(-100%);
1687
+ }
1688
+ .left-rail.open { transform: translateX(0); }
1689
+ .right-rail {
1690
+ left: 0;
1691
+ right: 0;
1692
+ top: auto;
1693
+ bottom: 0;
1694
+ width: 100%;
1695
+ height: min(68vh, 640px);
1696
+ border-left: 0;
1697
+ border-top: 1px solid var(--line);
1698
+ border-radius: 16px 16px 0 0;
1699
+ transform: translateY(100%);
1700
+ }
1701
+ .right-rail.open { transform: translateY(0); }
1702
+ .right-rail .rail-inner { max-height: 100%; }
1703
+ .panel-head { padding: 12px 16px 8px; }
1704
+ .panel-body { padding: 10px 12px; }
1705
+ .source-scroll { padding: 6px 0; }
1706
+ .code-line {
1707
+ grid-template-columns: 34px 1fr;
1708
+ gap: 9px;
1709
+ padding: 0 10px;
1710
+ }
1711
+ .panel-source { flex: 1 1 58%; }
1712
+ }
1713
+ @media (prefers-reduced-motion: reduce) {
1714
+ *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; }
1715
+ }