@worca/app 1.0.0 → 1.2.0-rc.1

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 (143) hide show
  1. package/README.md +30 -9
  2. package/agents/clarify.meta.json +4 -4
  3. package/agents/decomposer.meta.json +5 -5
  4. package/agents/implementer.meta.json +15 -5
  5. package/agents/manualTestsChecklist.meta.json +5 -4
  6. package/agents/manualWebUiTesting.meta.json +9 -4
  7. package/agents/planReviewer.meta.json +12 -4
  8. package/agents/planner.meta.json +12 -5
  9. package/agents/refiner.meta.json +15 -4
  10. package/agents/reviewer.meta.json +14 -4
  11. package/agents/worca-cc-clarify.md +7 -0
  12. package/agents/worca-cc-code-reviewer.md +11 -6
  13. package/agents/worca-cc-decomposer.md +7 -0
  14. package/agents/worca-cc-implementer.md +9 -0
  15. package/agents/worca-cc-manual-tests-checklist.md +8 -5
  16. package/agents/worca-cc-manual-web-ui-testing.md +10 -6
  17. package/agents/worca-cc-plan-refiner.md +11 -6
  18. package/agents/worca-cc-plan-reviewer.md +10 -7
  19. package/agents/worca-cc-planner.md +9 -0
  20. package/agents/worca-cc-workspace-reviewer.md +11 -4
  21. package/agents/worca-cc-workspace-scanner.md +8 -4
  22. package/agents/workspaceReviewer.meta.json +15 -4
  23. package/agents/workspaceScanner.meta.json +5 -4
  24. package/package.json +8 -2
  25. package/skills/worca/SKILL.md +5 -5
  26. package/src/cli/render.mjs +148 -0
  27. package/src/cli/worca-cc.mjs +386 -56
  28. package/src/core/agent-gen.mjs +69 -31
  29. package/src/core/agent-registry.mjs +124 -144
  30. package/src/core/agent-store.mjs +164 -4
  31. package/src/core/artifacts.mjs +199 -23
  32. package/src/core/ask/attachment-kind.mjs +95 -0
  33. package/src/core/ask/catalog.mjs +111 -0
  34. package/src/core/ask/comment-deps.mjs +55 -0
  35. package/src/core/ask/events.mjs +545 -0
  36. package/src/core/ask/follow.mjs +113 -0
  37. package/src/core/ask/git-allowlist.mjs +226 -0
  38. package/src/core/ask/limits.mjs +57 -0
  39. package/src/core/ask/mcp-stdio.mjs +135 -0
  40. package/src/core/ask/models.mjs +125 -0
  41. package/src/core/ask/prompt.mjs +286 -0
  42. package/src/core/ask/proposal.mjs +170 -0
  43. package/src/core/ask/redact.mjs +30 -0
  44. package/src/core/ask/spawn.mjs +156 -0
  45. package/src/core/ask/store.mjs +438 -0
  46. package/src/core/ask/tool-deps.mjs +87 -0
  47. package/src/core/ask/tools.mjs +879 -0
  48. package/src/core/ask/turn.mjs +462 -0
  49. package/src/core/ask/worktree-deps.mjs +27 -0
  50. package/src/core/ask/worktrees.mjs +285 -0
  51. package/src/core/chat/command-router.mjs +28 -7
  52. package/src/core/chat/notifier.mjs +6 -1
  53. package/src/core/chat/renderers.mjs +15 -8
  54. package/src/core/claude-runner.mjs +541 -62
  55. package/src/core/config.mjs +310 -44
  56. package/src/core/cost-budget.mjs +29 -2
  57. package/src/core/db.mjs +773 -53
  58. package/src/core/diff-anchor.mjs +213 -0
  59. package/src/core/diff-comments.mjs +273 -0
  60. package/src/core/engine-select.mjs +32 -0
  61. package/src/core/failure-policy.mjs +201 -0
  62. package/src/core/git-info.mjs +49 -10
  63. package/src/core/graph/builtin-workflows.mjs +51 -0
  64. package/src/core/graph/executor.mjs +894 -0
  65. package/src/core/graph/registry-ports.mjs +12 -0
  66. package/src/core/graph/scheduler.mjs +1072 -0
  67. package/src/core/graph/seed-templates.mjs +318 -0
  68. package/src/core/host-guard.mjs +271 -0
  69. package/src/core/model-env.mjs +180 -8
  70. package/src/core/model-test.mjs +79 -0
  71. package/src/core/orchestrator.mjs +994 -4097
  72. package/src/core/overview-agent.mjs +15 -3
  73. package/src/core/phases.mjs +208 -537
  74. package/src/core/pipeline-delete.mjs +13 -2
  75. package/src/core/plugin-api.mjs +8 -3
  76. package/src/core/plugin-config.mjs +178 -28
  77. package/src/core/plugin-inventory.mjs +6 -2
  78. package/src/core/plugin-manifest.mjs +199 -11
  79. package/src/core/plugin-models.mjs +1 -0
  80. package/src/core/plugin-repo.mjs +16 -4
  81. package/src/core/plugin-shim-child.mjs +9 -3
  82. package/src/core/plugin-shim.mjs +80 -17
  83. package/src/core/plugin-store.mjs +236 -29
  84. package/src/core/plugin-workflows.mjs +90 -41
  85. package/src/core/preflight.mjs +135 -3
  86. package/src/core/projects.mjs +7 -5
  87. package/src/core/protocol.mjs +8 -35
  88. package/src/core/recoverable-error.mjs +1 -1
  89. package/src/core/run-harness.mjs +3934 -0
  90. package/src/core/run-manifest.mjs +5 -1
  91. package/src/core/settings.mjs +184 -13
  92. package/src/core/skills.mjs +10 -3
  93. package/src/core/source-bindings.mjs +175 -0
  94. package/src/core/sources.mjs +87 -25
  95. package/src/core/stats.mjs +25 -6
  96. package/src/core/title.mjs +51 -4
  97. package/src/core/workflows.mjs +358 -259
  98. package/src/core/workspace-scan.mjs +4 -0
  99. package/src/core/worktree.mjs +98 -7
  100. package/src/shared/graph/agent-meta.mjs +278 -0
  101. package/src/shared/graph/constants.mjs +105 -0
  102. package/src/shared/graph/geometry.mjs +157 -0
  103. package/src/shared/graph/layout.mjs +134 -0
  104. package/src/shared/graph/loops.mjs +130 -0
  105. package/src/shared/graph/manifest.mjs +257 -0
  106. package/src/shared/graph/ports.mjs +153 -0
  107. package/src/shared/graph/route.mjs +397 -0
  108. package/src/shared/graph/template.mjs +165 -0
  109. package/src/shared/graph/thumbnail.mjs +67 -0
  110. package/src/shared/graph/validate.mjs +491 -0
  111. package/src/shared/graph/verdict.mjs +41 -0
  112. package/ui/public/app.js +4240 -1682
  113. package/ui/public/ask-markdown.mjs +145 -0
  114. package/ui/public/ask-model.mjs +317 -0
  115. package/ui/public/ask-panel.mjs +2129 -0
  116. package/ui/public/chat-settings-view.mjs +6 -2
  117. package/ui/public/diff-view.mjs +66 -11
  118. package/ui/public/file-tree.mjs +305 -0
  119. package/ui/public/graph/composer.mjs +889 -0
  120. package/ui/public/graph/inspector.mjs +183 -0
  121. package/ui/public/graph/model.mjs +37 -0
  122. package/ui/public/graph/palette.mjs +144 -0
  123. package/ui/public/graph/run-decor.mjs +410 -0
  124. package/ui/public/graph/run-hosts.mjs +201 -0
  125. package/ui/public/graph/save-dialog.mjs +56 -0
  126. package/ui/public/graph/view.mjs +858 -0
  127. package/ui/public/guardrails-view.mjs +4 -2
  128. package/ui/public/hljs-loader.mjs +180 -0
  129. package/ui/public/index.html +311 -265
  130. package/ui/public/log-filter.mjs +22 -4
  131. package/ui/public/log-line.mjs +45 -19
  132. package/ui/public/models-view.mjs +171 -9
  133. package/ui/public/plugins-view.mjs +106 -4
  134. package/ui/public/source-pane.mjs +190 -8
  135. package/ui/public/stats-view.mjs +81 -1
  136. package/ui/public/style.css +1487 -229
  137. package/ui/public/syntax-highlight.mjs +270 -0
  138. package/ui/public/thinking-orb.mjs +110 -0
  139. package/ui/server.mjs +1894 -104
  140. package/src/core/channels.mjs +0 -302
  141. package/src/core/runners.mjs +0 -167
  142. package/src/core/workflow-validator.mjs +0 -185
  143. package/ui/public/composer-core.mjs +0 -211
@@ -43,6 +43,7 @@
43
43
  --r-ctrl:14px;
44
44
  --shadow:0 6px 28px rgba(25,25,27,.05), 0 1px 2px rgba(25,25,27,.04);
45
45
  --shadow-soft:0 2px 12px rgba(25,25,27,.04);
46
+ --ask-dock-clearance:96px;
46
47
 
47
48
  --sans:'Poppins',system-ui,-apple-system,BlinkMacSystemFont,sans-serif;
48
49
  --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
@@ -65,6 +66,15 @@ body{
65
66
  .app{display:flex;height:100vh;}
66
67
 
67
68
  /* ---------- Sidebar ---------- */
69
+ /* `width` is inert while .app is a flex row — flex-basis determines the used
70
+ main size, and that is what actually animates (measured: 298 -> 270.8 ->
71
+ 125.1 -> 76 over 200ms). `width` is listed too so the fallback path animates
72
+ if the flex context ever changes. It must stay a TRANSITION: the blanket at
73
+ :905 kills every transition under prefers-reduced-motion, but only four
74
+ selectors' animations.
75
+ This comment is deliberately OUTSIDE the braces. ruleBody() in
76
+ test/ui-pinned-sidebar.test.mjs:16-20 captures up to the first closing brace,
77
+ so a comment inside a rule body truncates the capture. */
68
78
  .sidebar{
69
79
  width:298px;flex:0 0 298px;
70
80
  background:var(--panel);
@@ -72,8 +82,10 @@ body{
72
82
  display:flex;flex-direction:column;
73
83
  border-right:1px solid var(--line);
74
84
  overflow-y:auto;
85
+ transition:flex-basis .2s cubic-bezier(.65,.02,.28,1),
86
+ width .2s cubic-bezier(.65,.02,.28,1);
75
87
  }
76
- .brand{display:flex;align-items:center;padding:0 8px;margin-bottom:24px;}
88
+ .brand{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 8px;margin-bottom:24px;}
77
89
  .brand .logo{height:36px;width:auto;display:block;}
78
90
 
79
91
  .nav{display:flex;flex-direction:column;gap:4px;flex:1 1 auto;}
@@ -234,7 +246,7 @@ body.view-history .main{display:flex;flex-direction:column;overflow:hidden;paddi
234
246
  /* .main's 32px gutter goes with padding:0, so the compact top-nav (<1080px, a
235
247
  SIBLING of the views inside .main) must re-apply it or it lands 20px left of
236
248
  the list content, which keeps its own 32px on .hist-screen. flex:0 0 auto
237
- stops the wrapped 12-button nav from being squeezed once .main is a column. */
249
+ stops the wrapped 9-button nav from being squeezed once .main is a column. */
238
250
  body.view-history .topnav{margin:12px 32px 0;flex:0 0 auto;}
239
251
  .topbar{display:flex;align-items:flex-start;justify-content:space-between;
240
252
  gap:24px;margin-bottom:22px;}
@@ -293,6 +305,7 @@ body.view-history .topnav{margin:12px 32px 0;flex:0 0 auto;}
293
305
  .field > label .opt,.field .opt{color:var(--ink-3);font-weight:500;font-style:normal;}
294
306
  .hint{display:block;color:var(--ink-3);font-size:12px;margin-top:7px;line-height:1.45;}
295
307
  .hint.err{color:var(--red-ink);}
308
+ .hint.warn{color:var(--amber-ink);}
296
309
 
297
310
  .input,.select,.textarea,.ta-hl-back,
298
311
  input[type="text"],input[type="number"],textarea,select{
@@ -355,6 +368,14 @@ input[type="file"]{font-size:12.5px;color:var(--ink-3);background:transparent;bo
355
368
  .add-project{margin-top:10px;display:flex;flex-direction:column;gap:9px;padding:14px;
356
369
  border:1.5px solid var(--line-2);border-radius:var(--r-ctrl);background:var(--field);}
357
370
  .add-project-actions{display:flex;gap:9px;justify-content:flex-end;}
371
+ /* Settings → Chat history: a text-style destructive action so its label sits on
372
+ the same left edge as the "Chat history" heading and the counts line under it
373
+ (a pill's inner padding indented the text). Same shape as .hd-cmt-btn. */
374
+ .ask-history-delete{border:none;background:none;padding:0;color:var(--red-ink);
375
+ font:600 12.5px var(--sans);cursor:pointer;line-height:1.45;}
376
+ .ask-history-delete:hover:not(:disabled){text-decoration:underline;}
377
+ .ask-history-delete:disabled{opacity:.45;cursor:not-allowed;}
378
+ .ask-history-delete:focus-visible{outline:2px solid var(--ink);outline-offset:2px;border-radius:3px;}
358
379
  /* path row (input + trailing button) — shared by add-project, project modal, plugin add */
359
380
  .path-row{display:flex;gap:9px;align-items:center;}
360
381
  .path-row input{flex:1 1 auto;min-width:0;}
@@ -367,6 +388,12 @@ input[type="file"]{font-size:12.5px;color:var(--ink-3);background:transparent;bo
367
388
  .seg button.on{background:#fff;color:var(--ink);box-shadow:var(--shadow-soft);}
368
389
  .seg button:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
369
390
 
391
+ /* Settings tab strip. .seg gives the pill chrome; this only spaces it from the
392
+ first pane's .topbar, which brings its own 22px bottom margin. */
393
+ .settings-tabs{margin-bottom:18px;}
394
+ /* A pane is a plain block — it is deliberately NOT .view (see index.html). */
395
+ .settings-pane{display:block;}
396
+
370
397
  /* task-source fieldset (old markup) */
371
398
  fieldset.source{border:1.5px solid var(--line);border-radius:var(--r-ctrl);padding:16px;margin:0 0 18px;}
372
399
  fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--ink);}
@@ -496,6 +523,9 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
496
523
  .form-msg{margin:12px 0 0;font-size:12.5px;min-height:16px;}
497
524
  .form-msg.ok{color:var(--green-ink);}
498
525
  .form-msg.err{color:var(--red-ink);}
526
+ /* saved, but with a caveat the user must act on (agent-port edit stranding a
527
+ saved pipeline — app.js saveAgentEdit reads PUT /api/agents/:key `warnings`). */
528
+ .form-msg.warn{color:var(--amber-ink);}
499
529
 
500
530
  /* ---------- Pipeline config ---------- */
501
531
  /* The always-open .stage-cfg card is gone: every per-agent control now lives in
@@ -706,6 +736,14 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
706
736
  .log-filters .log-search{flex:1 1 160px;max-width:340px;}
707
737
  .log-filters .log-copy{flex:0 0 auto;margin-left:auto;cursor:pointer;}
708
738
  .log-filters .log-copy:hover{background:var(--line);}
739
+ /* execution chip (P6b): hidden until a footer-row click narrows the log to ONE
740
+ execution; any click on it clears that axis. Scoped under .log-filters so it
741
+ out-ranks the .log-f pill rule above (an unscoped .log-f-exec would lose). */
742
+ .log-filters .log-f-exec{display:inline-flex;align-items:center;gap:6px;max-width:220px;padding:2px 8px;border-radius:999px;
743
+ border:1px solid var(--line);background:var(--field);font-size:11px;font-weight:600;color:var(--ink-2);cursor:pointer;}
744
+ .log-filters .log-f-exec[hidden]{display:none;}
745
+ .log-filters .log-f-exec .lfe-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
746
+ .log-filters .log-f-exec .lfe-x{color:var(--ink-3);font-weight:800;}
709
747
  /* run card: pills drop to a full-width second row under "Live log" (order puts
710
748
  the bar after the autoscroll switch so row 1 stays label ⟷ switch) */
711
749
  .run-log-head .log-filters{order:1;flex-basis:100%;}
@@ -827,6 +865,7 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
827
865
  .badge.running{background:var(--peach-bg);color:var(--peach-ink);}
828
866
  .badge.red,.badge.error,.badge.stopped{background:var(--red-bg);color:var(--red-ink);}
829
867
  .badge.waiting{background:var(--amber-bg);color:var(--amber-ink);}
868
+ .badge.amber{background:var(--amber-bg);color:var(--amber-ink);}
830
869
  .badge.paused{background:var(--amber-bg);color:var(--amber-ink);}
831
870
  .badge.hist-retained-badge{background:var(--amber-bg);color:var(--amber-ink);}
832
871
  .badge.hist-retained-badge[hidden]{display:none;}
@@ -1017,143 +1056,6 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1017
1056
  .pl-remove:disabled{opacity:.6;cursor:default;}
1018
1057
  .pl-remove svg{display:block;}
1019
1058
 
1020
- /* ========================================================================== */
1021
- /* Pipeline Composer — drag/drop canvas, wires, palette, saved list */
1022
- /* (ported from docs/pipeline-composer/mockups; reuses :root tokens) */
1023
- /* ========================================================================== */
1024
- .builder-card{padding:0;overflow:hidden;}
1025
- /* No top padding: sticky pins to the scrollport's PADDING edge, so a 20px inset
1026
- here would leave a see-through band above the pinned header for cards to
1027
- scroll through. The header row re-applies the inset itself (same fix shape as
1028
- body.view-history .main + .hist-filter). */
1029
- .palette{display:flex;flex-wrap:wrap;gap:10px;align-items:center;padding:0 24px 20px;border-bottom:1px solid var(--line);background:var(--panel);max-height:320px;overflow-y:auto;}
1030
- .palette .p-label{display:flex;align-items:center;gap:8px;font-size:11px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);width:100%;margin-bottom:-10px;padding-top:20px;padding-bottom:14px;position:sticky;top:0;z-index:2;background:var(--panel);}
1031
- .palette .p-label svg{width:14px;height:14px;stroke:var(--ink-3);stroke-width:2;}
1032
- .pal-filter{margin-left:auto;width:200px;padding:6px 10px;border:1.5px solid transparent;border-radius:10px;background:var(--field);color:var(--ink);font-family:inherit;font-size:12px;font-weight:500;text-transform:none;letter-spacing:normal;}
1033
- .pal-filter::placeholder{color:var(--ink-3);}
1034
- .pal-filter:focus{outline:none;background:#fff;border-color:var(--ink);}
1035
- .pal-filter:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
1036
- /* min-width:0 lets the grid item shrink under its min-content width and
1037
- overflow:hidden makes the card the last line of defence: without both, a
1038
- single unbroken 100-char description paints across its neighbours. */
1039
- .agent-pill{display:flex;flex-direction:column;align-items:flex-start;gap:6px;min-width:0;overflow:hidden;background:#fff;border:1.5px solid var(--line-2);border-radius:12px;padding:10px 12px;font-weight:600;font-size:12.5px;color:var(--ink);cursor:grab;box-shadow:var(--shadow-soft);transition:transform .12s,box-shadow .12s,border-color .12s;user-select:none;}
1040
- .agent-pill:hover{transform:translateY(-1px);box-shadow:var(--shadow);}
1041
- .agent-pill:active{cursor:grabbing;}
1042
- .agent-pill.dragging{opacity:.35;}
1043
- /* Dot pins to the FIRST name line (flex-start + optical nudge), because the
1044
- name may wrap to a second line — a centered dot would float between them. */
1045
- .agent-pill .pdotc{width:11px;height:11px;border-radius:50%;flex:0 0 auto;margin-top:3px;}
1046
- .agent-pill .phead{display:inline-flex;align-items:flex-start;gap:9px;max-width:100%;min-width:0;overflow-wrap:anywhere;}
1047
- .agent-pill .pname{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-width:0;overflow-wrap:anywhere;line-height:1.3;}
1048
- /* align-self:stretch overrides the card's align-items:flex-start — otherwise
1049
- .pdesc shrink-to-fits to the full string width and its own overflow:hidden
1050
- clips against that oversized box instead of the card. */
1051
- .agent-pill .pdesc{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;align-self:stretch;max-width:100%;min-width:0;overflow-wrap:anywhere;font-weight:400;font-size:11.5px;line-height:1.45;color:var(--ink-2);}
1052
- /* 3-line text budget: a name that wrapped to 2 lines (.name-2l, toggled by a
1053
- ResizeObserver in app.js) leaves only 1 line for the description. */
1054
- .agent-pill.name-2l .pdesc{-webkit-line-clamp:1;}
1055
- .agent-pill .pdesc.derived{font-style:italic;}
1056
-
1057
- .composer-hint {
1058
- margin: 0 0 10px;
1059
- padding: 8px 12px;
1060
- font-size: 12px;
1061
- color: var(--ink-3);
1062
- background: color-mix(in srgb, var(--blue) 8%, transparent);
1063
- border-radius: 8px;
1064
- }
1065
- .canvas-wrap{position:relative;overflow:auto;background-color:#FBFBF9;min-height:500px;max-height:640px;background-image:radial-gradient(circle, var(--line-2) 1.1px, transparent 1.1px);background-size:22px 22px;}
1066
- .flow{position:relative;display:flex;align-items:center;gap:0;padding:70px 30px 50px;min-height:500px;width:max-content;min-width:100%;}
1067
- .wires{position:absolute;top:0;left:0;overflow:visible;pointer-events:none;}
1068
-
1069
- .strip{flex:0 0 16px;align-self:stretch;border-radius:12px;margin:40px 0;position:relative;transition:flex-basis .14s,background .14s;}
1070
- .strip.full{flex:1;}
1071
- .strip.over{flex-basis:78px;background:rgba(140,127,214,.12);outline:2px dashed var(--violet);outline-offset:-5px;}
1072
- .strip.over::after{content:"+ step";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:10.5px;font-weight:700;letter-spacing:.05em;color:var(--violet-ink);white-space:nowrap;text-transform:uppercase;}
1073
-
1074
- .col{display:flex;flex-direction:column;gap:18px;align-items:center;justify-content:center;align-self:center;padding:14px 10px;border-radius:20px;position:relative;z-index:1;transition:background .14s,outline-color .14s;outline:2px dashed transparent;outline-offset:0;}
1075
- .col.over{background:rgba(91,166,204,.10);outline-color:var(--blue);}
1076
- /* left:10px tracks .col padding:14px 10px — tag left edge lines up with card left edge (was centered, causing feedback arcs to cut through text) */
1077
- .col .col-tag{position:absolute;top:8px;left:10px;transform:translateY(-100%);font-size:10px;font-weight:700;letter-spacing:.07em;color:var(--ink-3);text-transform:uppercase;white-space:nowrap;}
1078
- .col .col-tag em{font-style:normal;color:var(--blue-ink);}
1079
- .col .par-hint{position:absolute;bottom:6px;left:50%;transform:translate(-50%,100%);font-size:10.5px;font-weight:700;letter-spacing:.05em;color:var(--blue-ink);text-transform:uppercase;opacity:0;transition:opacity .14s;white-space:nowrap;}
1080
- .col.over .par-hint{opacity:1;}
1081
-
1082
- .node{position:relative;width:230px;background:#fff;border:1.5px solid var(--line-2);border-radius:16px;box-shadow:var(--shadow-soft);padding:13px 14px 13px 18px;display:flex;align-items:center;gap:12px;z-index:2;transition:box-shadow .12s,transform .12s;}
1083
- .node:hover{box-shadow:var(--shadow);}
1084
- .node::before{content:"";position:absolute;left:0;top:11px;bottom:11px;width:5px;border-radius:0 5px 5px 0;background:var(--c,#ccc);}
1085
- .node .nic{width:36px;height:36px;border-radius:11px;display:grid;place-items:center;flex:0 0 auto;}
1086
- .node .nic svg{width:19px;height:19px;stroke-width:1.85;}
1087
- .node .nmeta{min-width:0;flex:1;}
1088
- .node .nmeta b{display:block;font-weight:600;font-size:13.5px;line-height:1.2;letter-spacing:-.01em;}
1089
- .node .nmeta small{display:block;color:var(--ink-3);font-size:11px;margin-top:1px;}
1090
- .flow .node .nmeta small{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
1091
- .node .nx,.node .loop,.node .selfloop{position:absolute;border-radius:50%;background:#fff;border:1.5px solid var(--line-2);display:grid;place-items:center;cursor:pointer;opacity:0;transition:.12s;box-shadow:var(--shadow-soft);z-index:3;}
1092
- .node .nx{top:-9px;right:-9px;width:22px;height:22px;color:var(--ink-3);font-size:12px;}
1093
- .node .loop{right:-10px;bottom:-10px;width:25px;height:25px;}
1094
- .node .selfloop{top:-10px;left:-10px;width:25px;height:25px;}
1095
- .node:hover .nx,.node:hover .loop,.node:hover .selfloop{opacity:1;}
1096
- .node .nx:hover{color:var(--red-ink);border-color:var(--red);}
1097
- .node .loop svg{width:13px;height:13px;stroke:var(--ink-2);stroke-width:2;}
1098
- .node .loop:hover{border-color:var(--amber);}
1099
- .node .loop:hover svg{stroke:var(--amber-ink);}
1100
- .node .selfloop svg{width:14px;height:14px;stroke:var(--ink-2);stroke-width:2;}
1101
- .node .selfloop:hover{border-color:var(--violet);}
1102
- .node .selfloop:hover svg{stroke:var(--violet-ink);}
1103
- /* active self-cycle: always visible, violet ring (mirrors the lit toggle in the mockup) */
1104
- .node .selfloop.on{opacity:1;background:var(--violet-bg);border-color:var(--violet);box-shadow:0 0 0 3px var(--violet-bg);}
1105
- .node .selfloop.on svg{stroke:var(--violet-ink);}
1106
- .node.linking{outline:2px solid var(--amber);outline-offset:3px;}
1107
- .node.linking .loop{opacity:1;border-color:var(--amber);background:var(--amber-bg);}
1108
- .node.link-target{outline:2px dashed var(--amber);outline-offset:3px;cursor:pointer;}
1109
- .node.link-target:hover{background:var(--amber-bg);}
1110
-
1111
- .builder-foot{display:flex;align-items:center;gap:14px;padding:16px 24px;border-top:1px solid var(--line);background:var(--panel);flex-wrap:wrap;}
1112
- .legend{display:flex;align-items:center;gap:20px;font-size:12px;color:var(--ink-2);font-weight:600;}
1113
- .legend .lg{display:flex;align-items:center;gap:8px;}
1114
- .legend .ll{width:12px;height:12px;border-radius:50%;background:var(--seq);display:inline-block;flex:none;}
1115
- .legend .ll.fb{background:var(--amber);}
1116
- .legend .ll.sl{background:var(--violet);}
1117
- .builder-foot .sp{flex:1;}
1118
- .link-banner{display:inline-flex;align-items:center;gap:11px;background:var(--amber-bg);color:var(--amber-ink);font-weight:600;font-size:12.5px;padding:8px 9px 8px 15px;border-radius:999px;}
1119
- .link-banner[hidden]{display:none;}
1120
- .link-banner button{background:#fff;border:none;border-radius:999px;padding:5px 12px;font-family:inherit;font-weight:600;font-size:11.5px;color:var(--amber-ink);cursor:pointer;}
1121
- .empty-flow{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:var(--ink-3);font-weight:600;font-size:14px;pointer-events:none;text-align:center;}
1122
- .empty-flow svg{width:36px;height:36px;stroke:var(--line-2);stroke-width:1.6;}
1123
- .empty-flow small{font-weight:500;font-size:12.5px;}
1124
-
1125
- .saved-card{margin-top:22px;padding:0;overflow:hidden;}
1126
- .saved-head{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid var(--line);}
1127
- .saved-head b{font-size:15px;font-weight:600;letter-spacing:-.01em;}
1128
- .saved-head .cnt{color:var(--ink-3);font-size:12.5px;font-weight:600;margin-left:9px;}
1129
- .saved-list{display:flex;flex-direction:column;}
1130
- .pl-empty{padding:34px 24px;text-align:center;color:var(--ink-3);font-size:13px;font-weight:500;}
1131
- .pl-item{border-bottom:1px solid var(--line);}
1132
- .pl-item:last-child{border-bottom:none;}
1133
- .pl-row{display:flex;align-items:center;gap:15px;padding:16px 22px;cursor:pointer;transition:background .12s;}
1134
- .pl-row:hover{background:var(--panel);}
1135
- .pl-caret{width:17px;height:17px;flex:0 0 auto;color:var(--ink-3);transition:transform .18s;margin-top:2px;align-self:flex-start;}
1136
- .pl-item.open .pl-caret{transform:rotate(90deg);}
1137
- .pl-main{flex:1;min-width:0;}
1138
- .pl-name{font-weight:600;font-size:14px;letter-spacing:-.01em;color:var(--ink);}
1139
- .pl-meta{font-size:11px;font-weight:600;color:var(--ink-3);text-transform:uppercase;letter-spacing:.05em;margin-top:5px;}
1140
- .pl-meta em{font-style:normal;color:var(--amber-ink);}
1141
- .pl-chips{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:9px;}
1142
- .pl-chip{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--ink-2);background:var(--field);border:1px solid var(--line);border-radius:999px;padding:3px 9px 3px 7px;}
1143
- .pl-chip .d{width:8px;height:8px;border-radius:50%;flex:0 0 auto;}
1144
- .pl-del{flex:0 0 auto;width:34px;height:34px;border-radius:10px;border:1.5px solid var(--line-2);background:#fff;display:grid;place-items:center;cursor:pointer;color:var(--ink-3);transition:.12s;align-self:flex-start;}
1145
- .pl-del:hover{border-color:var(--red);color:var(--red-ink);background:var(--red-bg);}
1146
- .pl-del svg{width:16px;height:16px;stroke-width:1.9;}
1147
- .pl-body{display:none;border-top:1px dashed var(--line-2);}
1148
- .pl-item.open .pl-body{display:block;}
1149
- .pl-readonly-tag{display:flex;align-items:center;gap:8px;padding:9px 24px;background:var(--panel);font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);border-top:1px dashed var(--line-2);}
1150
- .pl-readonly-tag svg{width:13px;height:13px;stroke:var(--ink-3);stroke-width:2;}
1151
- .ro-scroll{overflow:auto;background-color:#FBFBF9;background-image:radial-gradient(circle, var(--line-2) 1.1px, transparent 1.1px);background-size:22px 22px;max-height:520px;}
1152
- .ro-flow{position:relative;display:flex;align-items:center;padding:26px 30px;min-height:360px;width:max-content;min-width:100%;}
1153
- .ro-flow .strip{flex:0 0 58px;align-self:stretch;margin:40px 0;}
1154
- .ro-flow .col{pointer-events:none;}
1155
- .ro-flow .node{width:218px;}
1156
-
1157
1059
  /* Per-agent fan-out toggle in the New Pipeline rows. */
1158
1060
  .fanout-toggle {
1159
1061
  display: inline-flex;
@@ -1244,33 +1146,14 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1244
1146
  .hist-pill.ws.active::before{background:rgba(255,255,255,.22);color:#fff;}
1245
1147
 
1246
1148
  /* ========================================================================== */
1247
- /* Run-flow — composer-style node graph for Running + History cards */
1248
- /* (ported from the mockup; reuses :root tokens. Node base look accent bar, */
1249
- /* border, radius, shadow is inherited from the unscoped `.node` rule. */
1250
- /* --c is set per-node inline by runNode(); these rules read it via var(--c).) */
1149
+ /* Run-flow — the SHARED decor the v2 graph renderer sits inside. The .run-flow */
1150
+ /* SCOPE STAYS: the v2 graph is hosted inside .run-flow on every page, and */
1151
+ /* de-scoping drops `.run-flow .node.is-active` from (0,3,0) to (0,2,0), which */
1152
+ /* ties `.gv-world .node` and loses on source order silently killing the v2 */
1153
+ /* status border colours. */
1251
1154
  /* ========================================================================== */
1252
1155
  .run-flow-wrap{position:relative;margin:0 0 18px;border-radius:18px;border:1px solid var(--line);overflow-x:auto;overflow-y:hidden;background-color:#FBFBF9;background-image:radial-gradient(circle, var(--line-2) 1.1px, transparent 1.1px);background-size:22px 22px;}
1253
1156
  .run-flow{position:relative;display:flex;align-items:center;gap:44px;padding:66px 30px 52px;width:max-content;min-width:100%;}
1254
- .run-flow .strip{flex:0 0 44px;align-self:stretch;margin:40px 0;background:none;outline:none;}
1255
- .run-flow .col{padding:14px 8px;}
1256
- .run-flow .col .col-tag{color:var(--ink-3);}
1257
-
1258
- /* Node sizing overrides — base look (accent bar ::before, border, radius,
1259
- soft shadow, flex layout) is inherited from the unscoped `.node` rule. */
1260
- .run-flow .node{width:198px;padding:11px 13px 11px 16px;cursor:default;transition:opacity .25s, box-shadow .25s, border-color .25s, transform .25s;}
1261
- .run-flow .node .nic{width:33px;height:33px;border-radius:10px;}
1262
- .run-flow .node .nic svg{width:17px;height:17px;}
1263
- .run-flow .node .nmeta b{font-size:12.5px;}
1264
- .run-flow .node .nmeta small{font-size:10px;}
1265
- .run-flow .node:hover{box-shadow:var(--shadow-soft);transform:none;}
1266
-
1267
- /* Always-visible per-node duration · cost (our decision; not in the mockup). */
1268
- .run-flow .node .nrun{display:flex;align-items:center;gap:5px;margin-top:3px;font-family:var(--mono);font-size:10px;font-weight:600;color:var(--ink-3);}
1269
- .run-flow .node .nrun .dur,.run-flow .node .nrun .cost{font-family:var(--mono);color:var(--ink-3);}
1270
- .run-flow .node .nrun .dur:empty,.run-flow .node .nrun .cost:empty{display:none;}
1271
- /* Separator rendered only when cost is present, so a lone duration (e.g. "0s") has no dangling "·". */
1272
- .run-flow .node .nrun .cost:not(:empty)::before{content:"·";margin-right:4px;}
1273
-
1274
1157
  /* Status badge (top-right pip). */
1275
1158
  .run-flow .nstat{position:absolute;top:-8px;right:-8px;width:21px;height:21px;border-radius:50%;display:grid;place-items:center;box-shadow:var(--shadow-soft);z-index:3;}
1276
1159
  .run-flow .nstat svg{width:12px;height:12px;}
@@ -1278,19 +1161,9 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1278
1161
  .run-flow .nstat.paused{background:var(--amber);} .run-flow .nstat.paused svg{fill:#fff;}
1279
1162
  .run-flow .nstat.stopped{background:var(--red);} .run-flow .nstat.stopped svg{stroke:#fff;stroke-width:3;}
1280
1163
 
1281
- /* Per-status node states. */
1282
- .run-flow .node.is-pending{opacity:.5;}
1283
- .run-flow .node.is-pending .nmeta small{color:var(--ink-3);}
1284
- .run-flow .node.is-done .nmeta small{color:var(--green-ink);font-weight:600;}
1285
1164
  .run-flow .node.is-active{border-color:color-mix(in srgb, var(--c) 55%, var(--line-2));animation:nodeGlow 2.2s ease-in-out infinite;}
1286
- .run-flow .node.is-active .nmeta small{color:var(--c);font-weight:600;}
1287
1165
  .run-flow .node.is-paused{border-color:color-mix(in srgb, var(--amber) 55%, var(--line-2));animation:nodeGlowAmber 2.2s ease-in-out infinite;}
1288
- .run-flow .node.is-paused .nmeta small{color:var(--amber-ink);font-weight:600;}
1289
1166
  .run-flow .node.is-stopped{border-color:color-mix(in srgb, var(--red) 50%, var(--line-2));box-shadow:0 0 0 3px color-mix(in srgb, var(--red) 13%, transparent),var(--shadow-soft);}
1290
- .run-flow .node.is-stopped .nmeta small{color:var(--red-ink);font-weight:600;}
1291
- /* Per-node model · effort sub-line: stays neutral grey across all statuses
1292
- (matches the New-pipeline .step-current caption) and truncates with ellipsis. */
1293
- .run-flow .node .nmeta small.nmodel{color:var(--ink-3);font-weight:600;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
1294
1167
  @keyframes nodeGlow{0%,100%{box-shadow:var(--shadow-soft);}50%{box-shadow:0 0 0 4px color-mix(in srgb, var(--c) 13%, transparent),0 8px 26px color-mix(in srgb, var(--c) 24%, transparent);}}
1295
1168
  @keyframes nodeGlowAmber{0%,100%{box-shadow:var(--shadow-soft);}50%{box-shadow:0 0 0 4px color-mix(in srgb, var(--amber) 16%, transparent),0 8px 26px color-mix(in srgb, var(--amber) 26%, transparent);}}
1296
1169
 
@@ -1299,7 +1172,6 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1299
1172
  AND NOWHERE ELSE: sqPulse attaches only to .run-flow .node .fan .sq.on, so
1300
1173
  the tree/legend squares (.led/.sq) never animate and history (terminal
1301
1174
  statuses -> no .on) never pulses. */
1302
- .run-flow .node.run-node{flex-wrap:wrap;}
1303
1175
  .run-flow .node .fan{flex:0 0 100%;display:flex;align-items:center;flex-wrap:wrap;gap:3px;margin-top:9px;padding-top:8px;border-top:1px solid var(--line);}
1304
1176
  .run-flow .node .fan .sq{width:7px;height:7px;border-radius:2px;background:var(--ink-3);opacity:.5;}
1305
1177
  .run-flow .node .fan .sq.on{background:var(--blue);opacity:1;animation:sqPulse 1.5s ease-in-out infinite;}
@@ -1346,24 +1218,21 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1346
1218
  name (cf. skill pills). Uses the shared violet token (defined :root, l.24) so it
1347
1219
  reads as agent metadata, distinct from the skill (blue) / MCP (green) pills. */
1348
1220
  .rd-ag-name .agent-type-pill,.hd-ag-row .agent-type-pill{font-size:10.5px;font-weight:700;letter-spacing:.01em;padding:2px 9px;border-radius:999px;white-space:nowrap;background:var(--violet-bg);color:var(--violet-ink);}
1221
+ /* Sub-agent run model (neutral outline) — the model a spawned child actually ran on.
1222
+ Deliberately the QUIETEST pill on the row: it is present on every sub-agent once a
1223
+ run records one, so a filled colour would out-shout the type + graphify badges that
1224
+ only some rows carry. Sits right after the violet type pill. The head scopes carry
1225
+ the SAME pill for the main agent's configured model · effort (stepModelPillHtml). */
1226
+ .rd-ag-name .sub-model-pill,.hd-ag-row .sub-model-pill,.rd-ag-head .sub-model-pill,.hd-ag-head .sub-model-pill{display:inline-block;font-size:10.5px;font-weight:600;letter-spacing:.01em;padding:1px 8px;border-radius:999px;white-space:nowrap;background:transparent;color:var(--ink-3);border:1px solid var(--line-2);}
1349
1227
  /* graphify-use count badge (amber) — how many times an agent / sub-agent invoked the
1350
1228
  graphify CLI. Rendered INLINE: in the phase/node header next to the status, and on a
1351
1229
  sub-agent row right after the violet type pill. inline-block + parent flex `gap` size
1352
1230
  it to its text; amber keeps it distinct from skill (blue) / MCP (green) / type (violet). */
1353
1231
  .graphify-pill{display:inline-block;font-size:10.5px;font-weight:700;letter-spacing:.01em;padding:2px 9px;border-radius:999px;white-space:nowrap;background:var(--amber-bg);color:var(--amber-ink);}
1354
1232
 
1355
- /* Loop-badge cycle-count text (renderer sizes font-size inline). */
1356
- .run-flow .loop-badge text{font-family:var(--sans);font-weight:700;}
1357
-
1358
- /* Wires — dimmed by default; the live (originating) loop marches. */
1359
- .run-flow .wires path.wire-dim{opacity:.26;}
1360
- .run-flow .wires path.wire-live{opacity:1;stroke-width:2.4;animation:wireFlow .9s linear infinite;}
1361
- @keyframes wireFlow{to{stroke-dashoffset:-18;}}
1362
-
1363
1233
  @media (prefers-reduced-motion: reduce){
1364
1234
  .run-flow .node.is-active,.run-flow .node.is-paused{animation:none;box-shadow:0 0 0 3px color-mix(in srgb, var(--c) 16%, transparent),var(--shadow-soft);}
1365
1235
  .run-flow .node.is-paused{box-shadow:0 0 0 3px color-mix(in srgb, var(--amber) 18%, transparent),var(--shadow-soft);}
1366
- .run-flow .wires path.wire-live{animation:none;}
1367
1236
  .run-flow .node .fan .sq.on{animation:none;}
1368
1237
  }
1369
1238
 
@@ -1379,6 +1248,30 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1379
1248
  .agent-chip.prod { background:#E2F3DF; }
1380
1249
  .agent-chip.cons { background:#DEEFF7; }
1381
1250
  .agent-io-none { font-size:11px; opacity:.45; line-height:20px; }
1251
+ .agent-chip.void { border-style:dashed; background:transparent; opacity:.75; }
1252
+ .agent-not-placeable { background:var(--amber-bg); color:var(--amber-ink); }
1253
+ .agent-not-placeable[hidden] { display:none; }
1254
+
1255
+ /* ---------- Agents-view port editor ---------- */
1256
+ .row-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;}
1257
+ .agent-ports{margin-bottom:16px;}
1258
+ .agent-ports-head{display:flex;align-items:center;gap:10px;margin-bottom:8px;}
1259
+ .agent-ports-head b{font-size:12.5px;font-weight:600;letter-spacing:.06em;}
1260
+ .agent-ports-head .pf-count{margin-left:auto;font-size:11px;}
1261
+ .agent-ports-list{display:flex;flex-direction:column;gap:9px;}
1262
+ .port-row{border:1px solid var(--line-2);border-radius:12px;padding:10px 12px;background:var(--panel);}
1263
+ .port-row-top{display:flex;align-items:flex-end;gap:9px;}
1264
+ .port-row-top .field{margin:0;flex:1 1 0;min-width:0;}
1265
+ .port-row-top .pf-f-type,.port-row-top .pf-f-as,.port-row-top .pf-f-when,.port-row-top .pf-f-store{flex:0 0 104px;}
1266
+ .port-row-top .field label{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);margin-bottom:4px;}
1267
+ .port-row-top .field[hidden]{display:none;}
1268
+ .port-row-flags{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-top:9px;}
1269
+ .pf-up,.pf-down,.pf-remove{flex:0 0 auto;font-size:13px;line-height:1;padding:8px 10px;}
1270
+ .pf-directive-wrap{margin-top:9px;}
1271
+ .pf-directive-wrap[hidden]{display:none;}
1272
+ .pf-hint,.pf-note{display:block;margin-top:6px;font-size:11px;}
1273
+ .pf-hint[hidden],.pf-note[hidden]{display:none;}
1274
+ .agent-caps,.agent-workspace{display:flex;flex-direction:column;gap:2px;}
1382
1275
 
1383
1276
  /* (2) space between the header pills and the expanded markdown body */
1384
1277
  .agent-detail { margin-top:14px; }
@@ -1389,8 +1282,17 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1389
1282
  .chip-select label { display:inline-flex; gap:6px; align-items:center; font-size:12.5px; border:1px solid var(--line); border-radius:999px; padding:4px 10px; cursor:pointer; }
1390
1283
 
1391
1284
  /* ===== Projects management view ===== */
1392
- /* Static rows (no expand) — neutralize the pipeline row's pointer cursor. */
1393
- #projects-list .pl-row{cursor:default;}
1285
+ /* The card reuses the saved-pipelines class names (.saved-card/.pl-item/.pl-row/
1286
+ .pl-main/.pl-name), but the composer's copies of those rules are scoped under
1287
+ .gv-saved, so this view owns its own layout: card padding 0 so the head and the
1288
+ rows share one left edge; flex rows so the bin button sits on the right.
1289
+ Static rows (no expand) — default cursor, no hover tint. */
1290
+ #projects-list .saved-card{padding:0;overflow:hidden;}
1291
+ #projects-list .pl-item{border-bottom:1px solid var(--line);}
1292
+ #projects-list .pl-item:last-child{border-bottom:none;}
1293
+ #projects-list .pl-row{display:flex;align-items:center;gap:15px;padding:16px 20px;cursor:default;}
1294
+ #projects-list .pl-main{flex:1 1 auto;min-width:0;}
1295
+ #projects-list .pl-name{font-weight:600;font-size:14px;letter-spacing:-.01em;color:var(--ink);}
1394
1296
  .proj-path{
1395
1297
  font-family:var(--mono);font-size:12px;color:var(--ink-2);
1396
1298
  margin-top:4px;word-break:break-all;
@@ -1504,10 +1406,29 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1504
1406
  .sp-row.sel{background:var(--green-bg);color:var(--green-ink);}
1505
1407
  .sp-row-meta{display:inline-flex;gap:6px;align-items:center;color:var(--ink-3);font-size:11.5px;flex:0 0 auto;}
1506
1408
  .sp-label{background:var(--chip-bg,#f0f0f4);border-radius:999px;padding:1px 8px;font-weight:600;}
1409
+ .sp-updated{white-space:nowrap;}
1410
+ /* Task identifier ahead of the title: reads as an id, and never wraps — a long
1411
+ title gives way before the key does. */
1412
+ .sp-key{font-family:var(--mono);color:var(--ink-3);white-space:nowrap;margin-right:7px;}
1507
1413
  .sp-preview{margin-top:8px;max-height:260px;overflow:auto;}
1508
1414
  .sp-preview .sp-prev-body{white-space:pre-wrap;font-size:12.5px;margin:6px 0 0;}
1509
1415
  .sp-pane .mono{font-family:var(--mono);}
1510
1416
  .sp-config-missing{display:flex;flex-direction:column;gap:8px;align-items:flex-start;margin-top:11px;}
1417
+ /* Profile gate: shown INSTEAD of the pane until the project is bound to one
1418
+ configuration of a multi-profile source. Same column shape as the
1419
+ not-configured box above — both are "answer this before you can browse". */
1420
+ .sp-profile-gate{display:flex;flex-direction:column;gap:8px;align-items:flex-start;margin-top:11px;}
1421
+ .sp-profile-gate .select{min-width:220px;}
1422
+ /* Standing profile row above the pane: which instance the listing below is
1423
+ coming from. One line, quiet, but never hidden. */
1424
+ .sp-profile-bar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0 0 10px;padding-bottom:9px;border-bottom:1px solid var(--line);}
1425
+ .sp-profile-bar label{font-weight:600;font-size:12.5px;color:var(--ink-2);}
1426
+ .sp-profile-bar .select{min-width:180px;}
1427
+ /* Profile roster in plugin settings: label, picker and the two actions on one
1428
+ row above the form they apply to. */
1429
+ .pl-profile-bar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:10px;}
1430
+ .pl-profile-bar label{font-weight:600;font-size:12.5px;color:var(--ink-2);}
1431
+ .pl-profile-bar .select{min-width:180px;}
1511
1432
 
1512
1433
  /* ---- Plugin provenance (results header, history cards) ---- */
1513
1434
  .src-badge{display:inline-flex;gap:6px;align-items:center;font-size:11.5px;background:var(--chip-bg,#f0f0f4);border-radius:999px;padding:2px 10px;}
@@ -1559,7 +1480,7 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1559
1480
 
1560
1481
  /* ---------- Statistics view ---------- */
1561
1482
  .stats-filter{display:flex;margin-bottom:18px;}
1562
- .stat-row{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:18px;}
1483
+ .stat-row{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:18px;}
1563
1484
  .stat-tile{padding:18px 20px;}
1564
1485
  .stat-label{display:flex;align-items:center;gap:8px;color:var(--ink-2);font-size:12.5px;font-weight:600;}
1565
1486
  .stat-ico{width:16px;height:16px;color:var(--ink-3);flex:0 0 auto;}
@@ -1639,6 +1560,12 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1639
1560
  In the v2 card it is a direct child of .h-meta on its own line. */
1640
1561
  .hist-pausenote{font-size:11.5px;font-weight:600;color:var(--amber-ink);}
1641
1562
  .hist-pausenote.total{color:var(--red-ink);}
1563
+ .hist-pausenote.error{color:var(--red-ink);}
1564
+ .pause-error-banner{border-radius:var(--r-ctrl);padding:12px 14px;margin-top:12px;font-size:13px;background:var(--red-bg);color:var(--red-ink);}
1565
+ .pause-error-banner b{display:block;margin-bottom:4px;}
1566
+ .pause-error-banner .peb-text{color:var(--ink);font-family:var(--mono);font-size:12px;word-break:break-word;}
1567
+ .pause-error-banner .peb-hint{color:var(--ink-2);margin-top:6px;}
1568
+ .hd-banners .pause-error-banner,.rd-banners .pause-error-banner{align-self:stretch;margin:0;}
1642
1569
 
1643
1570
  @media (max-width:1080px){
1644
1571
  .stat-row{grid-template-columns:repeat(2,1fr);}
@@ -1685,6 +1612,20 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1685
1612
  .mv-env-row .mv-env-copy{flex:0 0 auto;padding:6px 10px;}
1686
1613
  .mv-env-btns{display:flex;gap:10px;justify-content:flex-start;}
1687
1614
  .mv-env-btns .btn-ghost{padding:7px 12px;font-size:12px;}
1615
+ /* Pricing: the opt-in per-model cost override. Modes read like the effort row;
1616
+ the rate grid is hidden outside per-Mtok mode (explicit — a reset elsewhere
1617
+ must not be able to un-hide it). */
1618
+ .mv-cost-edit{display:grid;gap:10px;}
1619
+ .mv-cost-modes{display:flex;gap:14px;flex-wrap:wrap;}
1620
+ .mv-cost-mode{display:flex;align-items:center;gap:6px;font-size:13px;cursor:pointer;}
1621
+ .mv-cost-rates{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px 14px;}
1622
+ .mv-cost-rates[hidden]{display:none;}
1623
+ /* Label on its own row spanning both columns, then input + unit — five rates
1624
+ side by side leave a numeric input far too narrow to read its own value. */
1625
+ .mv-cost-rate{display:grid;grid-template-columns:1fr auto;align-items:center;gap:4px 8px;font-size:12.5px;}
1626
+ .mv-cost-rate-label{grid-column:1 / -1;}
1627
+ .mv-cost-rate .mv-cost-rate-in{min-width:0;font-family:var(--mono);font-size:12.5px;}
1628
+ .mv-cost-rate-unit{color:var(--muted,#6b7280);font-size:11.5px;white-space:nowrap;}
1688
1629
 
1689
1630
  /* ---- Model plugins (design §9.6): plugin cards + export wizard ---- */
1690
1631
  .mv-secret{display:block;margin:4px 0 0;}
@@ -1718,7 +1659,7 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1718
1659
  .hd .mono,.hist-card .mono,#shipit-modal .mono{font-family:var(--mono);}
1719
1660
 
1720
1661
  /* Card, not a full-bleed band: same border/radius/shadow language as `.card`, and
1721
- its own 20px/32px margin box supplies the inset the screen itself cannot —
1662
+ its own 20px/32px margin box supplies the inset the screen itself cannot —
1722
1663
  `.hist-screen-detail` keeps `padding:0` so the sticky `.hd-tabs` can still pin
1723
1664
  flush to the scrollport top. Inner padding drops 32 -> 24 so the title lands a
1724
1665
  card-like distance from the border instead of 56px from the viewport edge. */
@@ -1855,60 +1796,204 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
1855
1796
 
1856
1797
  /* ---------- History detail: Diff tab ---------- */
1857
1798
  .hd-diff{display:grid;grid-template-columns:minmax(280px,340px) 1fr;gap:18px;align-items:start;}
1799
+ .hd-diff{
1800
+ --hd-count-add:#176B4D;
1801
+ --hd-count-del:#B31D28;
1802
+ }
1803
+ .hd-diff .diff-add{color:var(--hd-count-add);}
1804
+ .hd-diff .diff-del{color:var(--hd-count-del);}
1858
1805
  @media (max-width:900px){.hd-diff{grid-template-columns:1fr;}}
1859
1806
  .hd-diff-list,.hd-diff-pane{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-card);overflow:hidden;}
1807
+ .hd-diff-pane,.ask-md{
1808
+ --hd-syntax-comment:#5B6167;
1809
+ --hd-syntax-keyword:#B31D28;
1810
+ --hd-syntax-type:#9A3E00;
1811
+ --hd-syntax-string:#176B4D;
1812
+ --hd-syntax-literal:#713BB8;
1813
+ --hd-syntax-title:#075C82;
1814
+ }
1860
1815
  .hd-diff-list-head,.hd-diff-pane-head{display:flex;align-items:center;justify-content:space-between;gap:10px;
1861
1816
  padding:15px 18px;border-bottom:1px solid var(--line);font-size:12.5px;}
1862
- .hd-diff-rows{padding:6px;max-height:520px;overflow-y:auto;}
1863
- .hd-diff-proj{padding:8px 12px 2px;color:var(--ink-3);font-size:11px;}
1864
- .hd-diff-file{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:9px;cursor:pointer;}
1865
- .hd-diff-file:hover,.hd-diff-file.active{background:var(--field);}
1866
- .hd-diff-file:focus-visible{outline:2px solid var(--ink);outline-offset:-2px;}
1867
- .hd-diff-file.deleted .hd-diff-path{opacity:.55;text-decoration:line-through;}
1868
- .hd-diff-file.new .hd-diff-path{color:var(--green-ink);}
1869
- /* direction:rtl ellipsizes at the START (the file name matters more than the
1870
- directory). It also reorders a LEADING NEUTRAL character to the end, so
1871
- `.github/workflows/ci.yml` would render as `github/workflows/ci.yml.` — the
1872
- ::before LRM (U+200E) is a strong LTR anchor that pins the dot in place while
1873
- keeping the start-ellipsis. It lives in CSS, not in textContent, so the
1874
- `/src\/a\.js/` assertions stay clean. */
1875
- .hd-diff-file .hd-diff-path{flex:1;min-width:0;font-size:12px;color:var(--ink-2);
1876
- white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;text-align:left;}
1877
- .hd-diff-file .hd-diff-path::before{content:"\200E";}
1817
+ .hd-diff-rows{padding:6px;max-height:860px;overflow-y:auto;}
1878
1818
  .hd-diff-counts{font-size:11.5px;white-space:nowrap;}
1879
- /* hdFileCountsHtml's binary chip is a `.hint`, and `.hint` (style.css:283) is
1819
+ /* Binary count chips carry `.hint`, and `.hint` (style.css:283) is
1880
1820
  `display:block;margin-top:7px` — inside these flex rows it would sit visibly
1881
1821
  low. Same reset for the pane head. */
1882
1822
  .hd-diff-counts .hint,.hd-diff-pane-head .hint{display:inline;margin-top:0;}
1883
- /* The `.hd-diff-path` ellipsis rule is scoped to `.hd-diff-file`, so the SAME class
1884
- in the pane head is unstyled and WRAPS: measured 49.8px -> 69px on a 78-char
1885
- path, while the identical path ellipsizes one column to the left. */
1886
- .hd-diff-pane-head .hd-diff-path{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
1823
+ .hd-diff-pane-head .hd-diff-path{
1824
+ margin:0;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
1825
+ font-size:12px;font-weight:400;
1826
+ }
1887
1827
  /* The patch is the tall thing on this screen, so it scrolls INSIDE its pane
1888
1828
  rather than pushing the whole detail screen down — same cap and mechanism as
1889
1829
  `.hd-diff-rows` opposite it, which keeps the two panes the same height.
1890
- Uncapped, a real patch measured 7479px and owned the screen's whole scroll.
1891
-
1892
- The grid is what makes the row TINT correct. `.hd-dl` used to size itself with
1893
- `width:max-content;min-width:100%`, but max-content is EACH ROW'S OWN content:
1894
- in a 336-row patch that produced 11 different row widths (708…1015px), so every
1895
- line shorter than the longest had its --green-bg/--red-bg band stop mid-pane
1896
- and the padding land against the text instead of the edge. A single column of
1897
- `minmax(max-content,1fr)` is sized by the WIDEST row (1fr keeps it filling the
1898
- pane when the patch is narrow) and stretch makes every row match it — measured
1899
- uniform at 1015px with 0 of 217 tinted rows stopping short. D4 forbids syntax
1900
- highlighting, so this tint is the pane's only visual signal — it has to hold. */
1901
- .hd-diff-body{padding:12px 0;font:400 12px/1.85 var(--mono);color:var(--ink-2);
1902
- display:grid;grid-template-columns:minmax(max-content,1fr);
1903
- max-height:520px;overflow-x:auto;overflow-y:auto;}
1904
- .hd-diff-body.hint,.hd-diff-none{padding:18px;}
1905
- .hd-dl{padding:0 18px;white-space:pre;}
1906
- .hd-dl-hunk{color:var(--ink-3);background:var(--field);}
1907
- .hd-dl-add{background:var(--green-bg);}
1908
- .hd-dl-del{background:var(--red-bg);}
1909
- .hd-diff-trunc{padding:10px 18px 0;}
1830
+ Uncapped, a real patch measured 7479px and owned the screen's whole scroll. */
1831
+ .hd-diff-body{
1832
+ --hd-gutter-width:calc(3ch + 16px);
1833
+ padding:12px 0;
1834
+ display:grid;
1835
+ grid-template-columns:var(--hd-gutter-width) var(--hd-gutter-width) minmax(max-content,1fr);
1836
+ max-height:860px;
1837
+ overflow:auto;
1838
+ font:400 12px/1.85 var(--mono);
1839
+ color:var(--ink-2);
1840
+ }
1841
+ .hd-diff-body.hint{display:block;margin-top:0;padding:18px;color:var(--ink-2);}
1842
+ .hd-diff-none{padding:18px;color:var(--ink-2);}
1843
+ .hd-dl-row{display:contents;}
1844
+ .hd-dl-n{
1845
+ position:sticky;
1846
+ z-index:2;
1847
+ padding:0 8px;
1848
+ text-align:right;
1849
+ color:var(--ink-2);
1850
+ background:var(--panel);
1851
+ user-select:none;
1852
+ }
1853
+ .hd-dl-n-old{left:0;}
1854
+ .hd-dl-n-new{left:var(--hd-gutter-width);}
1855
+ .hd-dl-code{min-width:100%;padding:0 18px 0 10px;white-space:pre;}
1856
+ .hd-dl-hunk,.hd-diff-note{grid-column:1/-1;padding-inline:18px;}
1857
+ /* Placeholder states are display:block bodies that pad 18px themselves. */
1858
+ .hd-diff-body.hint .hd-diff-note{padding-inline:0;}
1859
+ .hd-dl-hunk{white-space:pre;color:var(--ink-2);background:var(--field);}
1860
+ .hd-diff-trunc{padding-top:10px;color:var(--ink-2);}
1861
+ /* "N of M lines shown · Show K more lines": a real grid row spanning all three
1862
+ tracks (like the hunk header), never display:contents. */
1863
+ .hd-dl-more{grid-column:1/-1;display:flex;align-items:center;gap:12px;padding:8px 18px;user-select:none;
1864
+ border-top:1px solid var(--line);color:var(--ink-2);font:400 12px var(--sans);}
1865
+ .hd-dl-more-btn{border:none;background:none;color:var(--blue-ink);font:500 12.5px var(--sans);
1866
+ cursor:pointer;padding:0;}
1867
+ .hd-dl-more-btn:hover{text-decoration:underline;}
1868
+ .hd-dl-more-btn:focus-visible{outline:2px solid var(--ink);outline-offset:2px;border-radius:3px;}
1869
+ .hd-dl-row.hd-dl-add > *{background:var(--green-bg);}
1870
+ .hd-dl-row.hd-dl-del > *{background:var(--red-bg);}
1871
+ /* ---------- History detail: diff comments ---------- */
1872
+ /* A comment block is a REAL grid row spanning all three tracks — `.hd-dl-row` is
1873
+ display:contents, so a card can never live inside a row (same rule as
1874
+ `.hd-dl-hunk`/`.hd-diff-note` and `.hd-dl-more` above). */
1875
+ .hd-cmt-block,.hd-cmt-detached{grid-column:1/-1;display:flex;flex-direction:column;gap:8px;
1876
+ padding:10px 18px;background:var(--panel);border-top:1px solid var(--line);font:400 12px var(--sans);}
1877
+ .hd-cmt-detached{border-top:1px solid var(--line-2);}
1878
+ .hd-cmt-detached-head{color:var(--ink-2);font-weight:600;font-size:10.5px;letter-spacing:.04em;text-transform:uppercase;}
1879
+ .hd-cmt-card{display:flex;flex-direction:column;gap:6px;padding:11px 13px;border:1.5px solid var(--line);
1880
+ border-radius:16px;background:var(--panel);}
1881
+ .hd-cmt-card.resolved{opacity:.55;}
1882
+ .hd-cmt-head{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--ink-2);}
1883
+ .hd-cmt-author{padding:2px 8px;border-radius:999px;background:var(--blue-bg);color:var(--blue-ink);font-weight:600;}
1884
+ .hd-cmt-author.ask{background:var(--violet-bg);color:var(--violet-ink);}
1885
+ .hd-cmt-time{color:var(--ink-2);}
1886
+ .hd-cmt-tag{padding:2px 8px;border-radius:999px;background:var(--field);color:var(--ink-2);font-weight:600;}
1887
+ .hd-cmt-sent{color:var(--ink-2);}
1888
+ .hd-cmt-where{font-size:11px;color:var(--ink-2);}
1889
+ .hd-cmt-quote{padding:5px 9px;border-radius:8px;background:var(--field);color:var(--ink-2);
1890
+ font:400 11.5px var(--mono);white-space:pre-wrap;word-break:break-word;}
1891
+ .hd-cmt-body{color:var(--ink);white-space:pre-wrap;word-break:break-word;}
1892
+ .hd-cmt-card.resolved .hd-cmt-body{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
1893
+ .hd-cmt-actions{display:flex;align-items:center;gap:12px;}
1894
+ .hd-cmt-btn{border:none;background:none;padding:0;color:var(--blue-ink);
1895
+ font:500 11.5px var(--sans);cursor:pointer;}
1896
+ .hd-cmt-btn:hover{text-decoration:underline;}
1897
+ .hd-cmt-btn:focus-visible{outline:2px solid var(--ink);outline-offset:2px;border-radius:3px;}
1898
+ .hd-cmt-delete{color:var(--red-ink);}
1899
+ .hd-cmt-composer{display:flex;flex-direction:column;gap:8px;}
1900
+ .hd-cmt-input{width:100%;padding:8px 11px;border:1px solid var(--line-2);border-radius:10px;
1901
+ background:var(--panel);color:var(--ink);font:400 12px var(--sans);resize:vertical;}
1902
+ .hd-cmt-err{color:var(--red-ink);font-size:11.5px;}
1903
+ .hd-cmt-err:empty{display:none;}
1904
+ /* The `+` affordance rides inside the hovered row's code cell (one shared node), so
1905
+ the cell needs a positioning context. `left:0`, never a negative offset: a negative
1906
+ offset would overhang `.hd-dl-n-new`, which is `position:sticky; z-index:2` with an
1907
+ opaque `background:var(--panel)` and therefore paints OVER a z-index:1 button — the
1908
+ overhang would be invisible at rest, not just while scrolled.
1909
+ Be accurate about what `left:0` does: `.hd-dl-code` is `padding:0 18px 0 10px`, and
1910
+ an absolutely-positioned child resolves `left` against the PADDING box, so the 16px
1911
+ button spans 0-16px while the text starts at 10px — it overlaps the first ~6px of
1912
+ `.hd-dl-sign`. That is accepted, not accidental: the button is hover-only, it is
1913
+ what GitHub does, and the alternative (`left:10px`, or padding the cell wider)
1914
+ either hides it under the gutter or reflows every row. */
1915
+ .hd-dl-code{position:relative;}
1916
+ .hd-cmt-add{position:absolute;left:0;top:50%;transform:translateY(-50%);
1917
+ width:16px;height:16px;display:grid;place-items:center;padding:0;
1918
+ border:none;border-radius:5px;background:var(--blue);color:#fff;
1919
+ font:600 12px var(--sans);line-height:1;cursor:pointer;z-index:1;}
1920
+ .hd-cmt-add:focus-visible{outline:2px solid var(--ink);outline-offset:1px;}
1921
+ .hd-cmt-badge{flex:0 0 auto;min-width:17px;padding:1px 6px;border-radius:999px;
1922
+ background:var(--amber-bg);color:var(--amber-ink);font:600 10.5px var(--sans);text-align:center;}
1923
+ /* The pane head says a file is refused BEFORE the user writes anything. Not hidden
1924
+ and not a disabled button: the floor is a basename match, so it also catches
1925
+ ordinary files (src/secrets/README.md), and the run really did change this one.
1926
+ margin-left:auto so this third child does not re-space the head's existing pair. */
1927
+ .hd-diff-guarded{flex:0 0 auto;margin-left:auto;padding:1px 7px;border-radius:999px;
1928
+ background:var(--amber-bg);color:var(--amber-ink);font:600 10.5px var(--sans);white-space:nowrap;}
1929
+ /* Author `display` beats the UA [hidden] rule — every toggled class needs the twin. */
1930
+ .hd-cmt-block[hidden],.hd-cmt-detached[hidden],.hd-cmt-card[hidden],.hd-cmt-badge[hidden],.hd-cmt-add[hidden]{display:none;}
1931
+ .hd-tree{display:block;}
1932
+ .hd-tree-group{display:block;}
1933
+ .hd-tree-group[hidden]{display:none;}
1934
+ .hd-tree-project{padding:9px 12px 4px;color:var(--ink-2);font:600 11px var(--mono);}
1935
+ .hd-tree-dir,.hd-tree-file{
1936
+ width:100%;
1937
+ border:0;
1938
+ margin:0;
1939
+ padding-block:9px;
1940
+ padding-inline-end:12px;
1941
+ background:transparent;
1942
+ color:inherit;
1943
+ font:inherit;
1944
+ text-align:left;
1945
+ appearance:none;
1946
+ display:flex;
1947
+ align-items:center;
1948
+ gap:8px;
1949
+ cursor:pointer;
1950
+ border-radius:9px;
1951
+ }
1952
+ .hd-tree-dir,.hd-tree-file{padding-inline-start:var(--tree-indent,10px);}
1953
+ .hd-tree-dir:hover,.hd-tree-file:hover,.hd-tree-file.active{background:var(--field);}
1954
+ .hd-tree-dir:focus-visible,.hd-tree-file:focus-visible{
1955
+ outline:2px solid var(--ink);
1956
+ outline-offset:-2px;
1957
+ }
1958
+ .hd-tree-file .hd-diff-path{
1959
+ flex:1;
1960
+ min-width:0;
1961
+ font-size:12px;
1962
+ color:var(--ink);
1963
+ direction:ltr;
1964
+ text-align:left;
1965
+ white-space:nowrap;
1966
+ overflow:hidden;
1967
+ text-overflow:ellipsis;
1968
+ }
1969
+ .hd-tree-file .hd-diff-path::before{content:none;}
1970
+ .hd-tree-file.deleted .hd-diff-path{opacity:1;color:var(--ink);text-decoration:none;}
1971
+ .hd-tree-status,.hd-tree-chevron,.hd-tree-folder-icon{flex:0 0 auto;}
1972
+ .hd-tree-status{width:18px;height:18px;display:grid;place-items:center;color:var(--ink);}
1973
+ .hd-tree-file.add .hd-tree-status{color:var(--green-ink);}
1974
+ .hd-tree-file.del .hd-tree-status{color:var(--red-ink);}
1975
+ .hd-tree-file-icon,.hd-tree-folder-icon{width:18px;height:18px;display:block;}
1976
+ .hd-tree-chevron{width:12px;height:14px;color:var(--ink-2);transition:transform .15s;}
1977
+ .hd-tree-dir[aria-expanded="true"] .hd-tree-chevron{transform:rotate(90deg);}
1978
+ .hd-tree-folder-icon{color:var(--ink);}
1979
+ .hd-tree-dir-label{
1980
+ flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
1981
+ font-size:12px;color:var(--ink);
1982
+ }
1983
+ .hd-diff-pane :is(.hljs-comment,.hljs-quote,.hljs-punctuation){color:var(--hd-syntax-comment);}
1984
+ .hd-diff-pane :is(.hljs-keyword,.hljs-operator,.hljs-meta,.hljs-deletion){color:var(--hd-syntax-keyword);}
1985
+ .hd-diff-pane :is(.hljs-type,.hljs-built_in,.hljs-attr,.hljs-attribute,
1986
+ .hljs-selector-class,.hljs-selector-id,.hljs-selector-tag){color:var(--hd-syntax-type);}
1987
+ .hd-diff-pane :is(.hljs-string,.hljs-regexp,.hljs-addition){color:var(--hd-syntax-string);}
1988
+ .hd-diff-pane :is(.hljs-number,.hljs-literal,.hljs-symbol,.hljs-bullet,
1989
+ .hljs-variable,.hljs-template-variable){color:var(--hd-syntax-literal);}
1990
+ .hd-diff-pane :is(.hljs-title,.hljs-section,.hljs-name){color:var(--hd-syntax-title);}
1991
+ .hd-diff-pane :is(.hljs-comment,.hljs-quote){font-style:italic;}
1992
+ .hd-diff-pane .hint,.hd-diff-list .hint,.hd-tree .hint,.hd-diff-note{color:var(--ink-2);}
1910
1993
  .hd-diff-empty{margin-top:24px;padding:40px;text-align:center;color:var(--ink-2);
1911
1994
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r-card);}
1995
+ .hd-diff-partial{margin-bottom:14px;padding:10px 14px;background:var(--amber-bg);color:var(--amber-ink);
1996
+ border:1px solid var(--line);border-radius:var(--r-card);font:400 12.5px/1.5 var(--sans);}
1912
1997
 
1913
1998
  /* ---------- History detail: Overview tab ---------- */
1914
1999
  .hd-ov{display:flex;flex-direction:column;gap:18px;}
@@ -2070,6 +2155,10 @@ fieldset.source legend{font-weight:600;font-size:13px;padding:0 6px;color:var(--
2070
2155
  .hist-diff-pill .hist-diff{display:inline-flex;gap:6px;font-weight:700;}
2071
2156
  .hist-diff-pill .hist-diff[hidden],.hist-nodiff[hidden]{display:none;}
2072
2157
  .hist-nodiff{color:var(--ink-3);}
2158
+ .hist-cmt-pill{display:inline-flex;align-items:center;gap:8px;padding:4px 11px;
2159
+ border:1px solid var(--line);border-radius:999px;background:var(--panel);font-size:12px;color:var(--amber-ink);}
2160
+ .hist-cmt-pill[hidden]{display:none;} /* author display:inline-flex beats the UA [hidden] rule */
2161
+ .hist-cmt-pill .hist-cmt-count{font-weight:700;}
2073
2162
  .hist-open{display:flex;align-items:center;justify-content:center;width:30px;height:30px;
2074
2163
  border:1px solid var(--line);border-radius:9px;background:var(--panel);color:var(--ink-2);cursor:pointer;}
2075
2164
  .hist-open:hover{background:var(--ink);color:#fff;border-color:var(--ink);}
@@ -2456,7 +2545,7 @@ body.view-running .topnav{margin:12px 32px 0;flex:0 0 auto;}
2456
2545
  wire rule is `.run-flow .wires path.wire-live` and the fan rule is
2457
2546
  `.run-flow .node .fan .sq.on`. Each override below is one class deeper than the
2458
2547
  rule it beats, so it wins on specificity, not source order. */
2459
- .rd-graph.settled .run-flow .wires path.wire-live{animation:none;}
2548
+ .rd-graph.settled .run-flow .wires path.wire-live{animation:none;stroke-dashoffset:0;}
2460
2549
  /* Belt-and-braces: `runStatusOf` already returns 'stopped'/'done' for every node
2461
2550
  once the run is halted, so a terminal graph carries no `.is-active` — these two
2462
2551
  are no-ops today and exist only so a future adapter change cannot resurrect the
@@ -2504,6 +2593,681 @@ body.view-running .topnav{margin:12px 32px 0;flex:0 0 auto;}
2504
2593
  .stop-confirm:disabled{opacity:.6;cursor:default;}
2505
2594
  .stop-cancel:focus-visible,.stop-confirm:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
2506
2595
 
2596
+ /* ---------- Collapsible sidebar: the 76px icon rail ---------- */
2597
+ /* No rule here restyles anything the expanded 298px column already had: 21 of
2598
+ the 39 are scoped under `.sidebar.collapsed`, and the other 18
2599
+ (`.brand .logo-mark`, `.side-toggle*`, `.nav .rail-tile*`, `.spend-ring*`)
2600
+ style nodes that exist only because of this feature. The <1080px breakpoint
2601
+ (:920-922) still hides the sidebar outright and hands over to .topnav —
2602
+ collapse is a preference that only exists above it, and the two never overlap.
2603
+ HOUSE RULE for this whole block: no comment inside a rule body, and no
2604
+ closing brace in any comment — ruleBody() stops its capture at the first one
2605
+ and both would break the tests. */
2606
+ .brand .logo-mark{display:none;width:32px;height:32px;border-radius:50%;}
2607
+ .side-toggle{display:flex;align-items:center;justify-content:center;
2608
+ width:30px;height:30px;flex:0 0 auto;padding:0;border:0;border-radius:9px;
2609
+ background:transparent;color:var(--ink);cursor:pointer;}
2610
+ .side-toggle svg{width:23px;height:23px;}
2611
+ .side-toggle:hover{background:var(--field);color:var(--ink);}
2612
+ .side-toggle:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
2613
+
2614
+ /* 76 - 1px border-right - 36px padding = a 39px content box. But
2615
+ ::-webkit-scrollbar (:898) makes the vertical gutter SPACE-CONSUMING, and the
2616
+ rail's own scroll height is ~967px with four runs — on a 900px window that
2617
+ gutter takes another 10px and shoves the icon column 5px off centre. Suppress
2618
+ it here only; the rail still scrolls by wheel and keyboard, and the base
2619
+ overflow-y is deliberately NOT restated (a test asserts it isn't). */
2620
+ .sidebar.collapsed{width:76px;flex:0 0 76px;padding:22px 18px 18px;
2621
+ scrollbar-width:none;}
2622
+ .sidebar.collapsed::-webkit-scrollbar{width:0;height:0;}
2623
+ .sidebar.collapsed .brand{flex-direction:column;gap:6px;margin-bottom:8px;padding:0;}
2624
+ .sidebar.collapsed .logo{display:none;}
2625
+ .sidebar.collapsed .logo-mark{display:block;}
2626
+ .sidebar.collapsed .side-toggle{width:40px;height:40px;margin:10px 0 8px;border-radius:12px;}
2627
+ /* The glyph is a panel outline with a fixed left divider, so it must NOT be
2628
+ mirrored: scaleX(-1) would throw the divider to the right edge and claim the
2629
+ sidebar had moved. Only the inner chevron turns round, and applySidebarCollapsed
2630
+ (app.js:365) rewrites its `d` instead — the box stays put in both states.
2631
+ Both glyph sizes are the originals +30% (18 -> 23 beside the wordmark,
2632
+ 20 -> 26 on the rail). stroke-width lives in the markup, not here: at 1.2 in a
2633
+ 24-unit viewBox both sizes land on a ~1.2px hairline, so neither state needs
2634
+ walking back. */
2635
+ .sidebar.collapsed .side-toggle svg{width:26px;height:26px;}
2636
+
2637
+ /* `:not(.rail-tile)` is LOAD-BEARING on both rules below. Run tiles are
2638
+ <button>s inside .nav, so without the guard the first rule would force them
2639
+ to 40x40 and the second would hide their .child-dot and .child-q — the two
2640
+ things a tile exists to show. `:not()` contributes its argument's
2641
+ specificity, so the first rule is (0,4,1) and out-ranks `.nav button`
2642
+ (:91, 0,1,1), `.nav button:hover` (:100, 0,2,1) and `.nav button.nav-cta`
2643
+ (:109, 0,2,1). `flex:0 0 auto` is NOT decoration: .nav is a column flex
2644
+ container whose items default to shrinkable, and these boxes are fixed-size
2645
+ by design. */
2646
+ .sidebar.collapsed .nav{align-items:center;gap:6px;}
2647
+ .sidebar.collapsed .nav button:not(.rail-tile){position:relative;flex:0 0 auto;
2648
+ width:40px;height:40px;padding:0;gap:0;justify-content:center;border-radius:12px;}
2649
+ /* NOT display:none. This span is the ONLY source of an accessible name for
2650
+ every nav button — index.html carries no aria-label on any of the 12 and the
2651
+ SVGs carry no title element — and display:none removes it from the
2652
+ accessibility tree. Measured in Chrome: eleven buttons announced with no
2653
+ name, and Running announced as "4" (the count span survives, so
2654
+ name-from-contents wins). Same visually-hidden recipe as .sr-only
2655
+ (:1603-1604), plus the clip-path .sr-only does not carry — `clip` is
2656
+ deprecated and its successor is what modern engines honour. position:absolute
2657
+ takes it out of flow, so the 40px square still centres its glyph, and the
2658
+ button above is position:relative. Both
2659
+ nav-count badges carry .nav-count, so the two :not() guards keep this off
2660
+ them, and the child combinator keeps it off the nested nav-paused-count. */
2661
+ .sidebar.collapsed .nav button:not(.rail-tile) > span:not(.nav-count):not(.nav-rollup){
2662
+ position:absolute;width:1px;height:1px;padding:0;margin:-1px;
2663
+ overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;}
2664
+ /* New pipeline is the one filled control on the rail; outlined-at-rest only
2665
+ makes sense next to labels. These are (0,4,1) and (0,5,1), so they outrank
2666
+ `.nav button:hover` (:100, 0,2,1), `.nav button.nav-cta` (:109, 0,2,1),
2667
+ `.nav button.nav-cta:hover` (:111, 0,3,1) and `.nav button.nav-cta.active`
2668
+ (:112, 0,3,1). Without its own hover the rail's only filled control is
2669
+ hover-dead: the (0,4,1) fill would beat the (0,3,1) hover and nothing would
2670
+ change. brightness() keeps the mock's darker-on-hover intent inside the
2671
+ palette, and matches ten existing hover rules in this file. */
2672
+ .sidebar.collapsed .nav button.nav-cta{background:var(--ink);color:#fff;}
2673
+ .sidebar.collapsed .nav button.nav-cta svg{stroke:#fff;}
2674
+ .sidebar.collapsed .nav button.nav-cta:hover{filter:brightness(.55);}
2675
+
2676
+ /* Section headers keep their text node — ui-nav-sections.test.mjs:26-35 asserts
2677
+ the source order. (They are not exposed as named a11y nodes in either state,
2678
+ so that is NOT the reason.) 26px hairline, per the mock. */
2679
+ .sidebar.collapsed .nav-sect{width:26px;height:1px;margin:10px auto;padding:0;
2680
+ font-size:0;letter-spacing:0;background:var(--line);overflow:hidden;}
2681
+ .sidebar.collapsed .nav-sep{width:26px;margin-top:auto;}
2682
+
2683
+ .sidebar.collapsed .nav-count{position:absolute;top:-2px;right:-2px;margin:0;
2684
+ min-width:17px;height:17px;padding:0 4px;font-size:10px;
2685
+ border:2px solid var(--panel);}
2686
+ /* Two rules, not one grouped selector: grouped, the only thing a test can reach
2687
+ is the selector TEXT, and that assertion was proven vacuous. A grey zero
2688
+ pinned to a 40px square is noise, and the paused pill would land in the same
2689
+ corner as the live count. #nav-paused-badge has no rule of its own anywhere
2690
+ in this file today — it is styled by .nav-count.n-paused (:125) — so this is
2691
+ its first, and an id keeps it at (1,2,0), well clear of the badge rule above
2692
+ and of `.nav-count[hidden]`. The paused signal is not lost: every paused run
2693
+ still shows its amber dot on its own tile, and updateNavCounts puts the
2694
+ number in the button's title AND aria-label. */
2695
+ .sidebar.collapsed .nav-count.n-grey{display:none;}
2696
+ .sidebar.collapsed #nav-paused-badge{display:none;}
2697
+ /* The 2px ring is what both sibling markers on the rail get (`.child-dot` and
2698
+ `.child-q` below) and for the same reason. The dot's centre sits at 37,37 on a
2699
+ 40px square whose corner arc is centred 28,28 with r=12, and sqrt(9^2+9^2) is
2700
+ 12.73 — so more than half the dot overhangs the button, onto the white
2701
+ sidebar, in the #fff that `.nav button.active .nav-rollup` (:140) gives it
2702
+ while Running is the current view. Measured in Chrome: a faint crescent
2703
+ instead of an alert. Everything here is border-box (:52), so the ring eats
2704
+ into the 8px, it does not grow it. */
2705
+ .sidebar.collapsed .nav-rollup{position:absolute;right:-1px;bottom:-1px;margin:0;
2706
+ border:2px solid var(--panel);}
2707
+
2708
+ .sidebar.collapsed .side-foot{align-items:center;gap:8px;
2709
+ padding-top:12px;border-top:1px solid var(--line);}
2710
+
2711
+ /* ---- rail run tiles ---- */
2712
+ /* Scoped `.nav .rail-tile` (0,2,0) for the same reason `.nav .nav-child` is
2713
+ scoped (:149-155): it has to outrank the generic `.nav button` (:91, 0,1,1).
2714
+ `padding` and `gap` must be restated — the collapsed `padding:0` rule
2715
+ deliberately excludes `.rail-tile`, so a tile would otherwise inherit
2716
+ `padding:11px 13px` and hold its initials in an 8px content box.
2717
+ `flex:0 0 auto` keeps the fixed 36px box out of the column flex shrink. */
2718
+ .sidebar.collapsed .nav-children{align-items:center;gap:5px;margin-top:5px;}
2719
+ .nav .rail-tile{position:relative;display:flex;align-items:center;justify-content:center;
2720
+ width:36px;height:36px;flex:0 0 auto;padding:0;gap:0;
2721
+ border:1.5px solid var(--line-2);border-radius:11px;
2722
+ background:var(--panel);color:var(--ink-2);
2723
+ font-family:var(--mono);font-size:11.5px;font-weight:400;cursor:pointer;
2724
+ transition:border-color .15s,background .15s,color .15s;}
2725
+ /* `.nav button:hover` (:100) is (0,2,1) and OUT-SPECIFIES the (0,2,0) rule above,
2726
+ so background and color have to be restated here at (0,3,0) or a hovered tile
2727
+ turns var(--field) grey with var(--ink) initials. The mock's hover changes the
2728
+ border only. Specificity is decided before source order — putting this rule
2729
+ later cannot fix it. */
2730
+ .nav .rail-tile:hover{background:var(--panel);border-color:var(--ink);color:var(--ink-2);}
2731
+ /* Three (0,3,0) rules in a row, so SOURCE ORDER decides between them:
2732
+ `:hover` first, `.lingering` next (a hovered lingering tile stays grey), and
2733
+ `.active` last (a selected lingering tile must not render --ink-3 text on an
2734
+ --ink fill). `.active` also has to beat `.nav button.active` (:101, 0,2,1),
2735
+ which it does on class count. The expanded row gets the same precedence for
2736
+ free, on specificity: `.nav .nav-child.active` (:177) is (0,3,0) and
2737
+ `.nav-child.lingering` (:178) is only (0,2,0). */
2738
+ .nav .rail-tile.lingering{color:var(--ink-3);}
2739
+ .nav .rail-tile.active{background:var(--ink);border-color:var(--ink);color:#fff;}
2740
+ .nav .rail-tile:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
2741
+ /* No `box-sizing:content-box`: the global border-box (:52) makes this a 9px
2742
+ total — a 5px core inside a 2px panel ring, exactly the mock. content-box
2743
+ would make it 13px, and 15.3px at the dotpulse peak, on a 36px tile. The
2744
+ colour classes (:197-204) are UNSCOPED (0,2,0), so the tile's dot inherits its
2745
+ family and its pulse for free, and this (0,3,0) rule still wins on geometry.
2746
+ Reduced motion does NOT reach that pulse, here or on the expanded row:
2747
+ `.child-dot{animation:none}` in the block at :914-915 is (0,1,0) and loses to
2748
+ `.child-dot.peach{animation:dotpulse}` (0,2,0), and `.nav-rollup` is not on
2749
+ that kill list at all. Both are pre-existing and out of this change's scope. */
2750
+ .nav .rail-tile .child-dot{position:absolute;right:-2px;bottom:-2px;margin:0;
2751
+ width:9px;height:9px;border:2px solid var(--panel);}
2752
+ /* `margin:0` overrides the base .child-q's margin-left:6px (:207-211), which
2753
+ would otherwise shove this absolutely-positioned badge off the corner. The
2754
+ base `animation:pulse` is kept on purpose — a run waiting on you should
2755
+ pulse — and the block at :914-915 names .child-q, which DOES reach it: both
2756
+ are (0,1,0) and the kill list comes later in the file. */
2757
+ .nav .rail-tile .child-q{position:absolute;top:-5px;right:-5px;margin:0;
2758
+ display:flex;align-items:center;justify-content:center;
2759
+ width:15px;height:15px;border:2px solid var(--panel);border-radius:50%;
2760
+ background:var(--amber);color:#fff;font-size:9px;font-weight:700;}
2761
+
2762
+ /* ---- rail budget ring ---- */
2763
+ /* `.spend-ring` (0,1,0) ties `.spend-ind` (:1607-1608, 0,1,0) and wins the tie
2764
+ on source order, which is what lets it override display / width / text-align /
2765
+ border / border-radius / background / padding. The band rules at :1618-1622
2766
+ are `.spend-ind.warn .spend-ind-meter` and friends — they style DESCENDANTS
2767
+ the ring does not have, so they cannot reach it. `.spend-ind:focus-visible`
2768
+ (:1610) is already identical to what a `.spend-ring:focus-visible` would say,
2769
+ so no such rule is written. `:hover` is the one case order cannot fix. */
2770
+ .spend-ring{--ring-pct:0;--ring-fill:var(--blue-ink);--ring-track:var(--blue-bg);
2771
+ display:flex;align-items:center;justify-content:center;text-align:center;
2772
+ width:38px;height:38px;padding:0;border:0;border-radius:50%;cursor:pointer;
2773
+ background:conic-gradient(var(--ring-fill) 0% calc(var(--ring-pct) * 1%),
2774
+ var(--ring-track) 0%);}
2775
+ /* `.spend-ind:hover` (:1609, background:var(--line)) is (0,2,0) and
2776
+ OUT-SPECIFIES a bare `.spend-ring` (0,1,0) — measured in Chrome: hovering set
2777
+ background-image to `none` and the disc to a flat neutral fill, destroying the
2778
+ arc. Specificity is decided before source order, so the gradient has to be
2779
+ restated at (0,2,0). brightness() supplies the hover feedback the flat fill
2780
+ used to. */
2781
+ .spend-ring:hover{background:conic-gradient(var(--ring-fill) 0% calc(var(--ring-pct) * 1%),
2782
+ var(--ring-track) 0%);
2783
+ filter:brightness(.96);}
2784
+ .spend-ring.warn{--ring-fill:var(--amber-ink);--ring-track:var(--amber-bg);}
2785
+ .spend-ring.over{--ring-fill:var(--red-ink);--ring-track:var(--red-bg);}
2786
+ /* No total limit means no denominator: a flat neutral ring, and the centre shows
2787
+ the amount instead of a percentage nothing backs. var(--ink-3), not
2788
+ var(--line) — var(--line) on var(--panel) is ~1.1:1 across a 4.5px annulus, i.e.
2789
+ an absent ring, which is not the same thing as a neutral one. */
2790
+ .spend-ring.no-limit{--ring-fill:var(--ink-3);--ring-track:var(--ink-3);}
2791
+ .spend-ring-val{display:flex;align-items:center;justify-content:center;
2792
+ width:29px;height:29px;border-radius:50%;background:var(--panel);
2793
+ font-family:var(--mono);font-size:9.5px;color:var(--ink-2);}
2794
+
2795
+ /* ---------- Ask Worca (spec §10.3-§10.6; tokens only, no hex) ---------- */
2796
+ /* Fixed full-height dock over the content column; children restore pointer
2797
+ events. z-40 sits above sticky tab lists (5) and below .viewer-modal (50)
2798
+ and #confirm-modal (60) so page modals dim the sheet and confirmModal stays
2799
+ reusable. Left offsets track the rail: 298px expanded, 76px collapsed
2800
+ (.sidebar / .sidebar.collapsed own those widths). */
2801
+ .ask-dock{position:fixed;top:0;bottom:0;right:0;left:298px;z-index:40;pointer-events:none;
2802
+ display:flex;flex-direction:column;align-items:center;justify-content:flex-end;
2803
+ padding:0 28px 26px;transition:left .2s cubic-bezier(.65,.02,.28,1);}
2804
+ body.rail-collapsed .ask-dock{left:76px;}
2805
+ /* Below the rail breakpoint the sidebar is display:none while .collapsed may
2806
+ still be present — restate the higher-specificity selector or left:76px wins. */
2807
+ @media (max-width:1080px){
2808
+ .ask-dock,body.rail-collapsed .ask-dock{left:0;}
2809
+ }
2810
+
2811
+ .ask-pill{pointer-events:auto;display:flex;align-items:center;gap:10px;padding:11px 18px 11px 14px;
2812
+ border:1.5px solid var(--line-2);border-radius:999px;background:var(--panel);color:var(--ink);
2813
+ font-family:inherit;font-weight:600;font-size:13.5px;cursor:pointer;
2814
+ box-shadow:0 6px 28px rgba(25,25,27,.10),0 1px 2px rgba(25,25,27,.06);transition:border-color .15s;}
2815
+ .ask-pill:hover{border-color:var(--ink);}
2816
+ .ask-pill:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
2817
+ .ask-pill[hidden]{display:none;}
2818
+ .ask-pill-logo{width:22px;height:22px;border-radius:50%;}
2819
+ .ask-kbd{padding:3px 7px;border-radius:7px;background:var(--field);color:var(--ink-3);
2820
+ font-family:var(--mono);font-weight:400;font-size:10.5px;}
2821
+
2822
+ .ask-sheet{pointer-events:auto;position:relative;display:flex;flex-direction:column;overflow:hidden;
2823
+ width:min(782px,100%);height:min(669px,calc(100% - 20px));background:var(--panel);
2824
+ border:1px solid var(--line-2);border-radius:var(--r-card);
2825
+ box-shadow:0 18px 60px rgba(25,25,27,.14),0 2px 6px rgba(25,25,27,.06);
2826
+ animation:wr-rise .26s cubic-bezier(.2,.7,.3,1) both;}
2827
+ .ask-sheet[hidden]{display:none;}
2828
+
2829
+ .ask-header{display:flex;align-items:center;gap:10px;padding:13px 14px 13px 16px;border-bottom:1px solid var(--line);}
2830
+ .ask-header-logo{width:20px;height:20px;border-radius:50%;}
2831
+ .ask-title{font-weight:600;font-size:13px;color:var(--ink);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
2832
+ .ask-header-spacer,.ask-composer-spacer,.ask-activity-spacer,.ask-card-actions-spacer{flex:1 1 auto;}
2833
+ .ask-scope-btn{display:inline-flex;align-items:center;gap:5px;max-width:180px;height:30px;flex:none;padding:0 9px;
2834
+ border:1px solid var(--line);border-radius:999px;background:none;color:var(--ink-3);
2835
+ font:inherit;font-size:11.5px;cursor:pointer;}
2836
+ .ask-scope-btn:hover{background:var(--field);color:var(--ink);}
2837
+ .ask-scope-btn:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
2838
+ .ask-scope-btn.is-pinned{color:var(--ink);border-color:var(--line-2);font-weight:600;}
2839
+ .ask-scope-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
2840
+ .ask-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;
2841
+ border:0;border-radius:9px;background:transparent;color:var(--ink-2);cursor:pointer;transition:.15s;}
2842
+ .ask-icon-btn:hover{background:var(--field);color:var(--ink);}
2843
+ .ask-icon-btn:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
2844
+
2845
+ .ask-transcript{position:relative;flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
2846
+ padding:18px 20px 8px;display:flex;flex-direction:column;gap:16px;}
2847
+ .ask-msg{display:flex;flex-direction:column;gap:6px;animation:wr-rise .2s cubic-bezier(.2,.7,.3,1) both;}
2848
+ .ask-user-bubble{align-self:flex-end;max-width:78%;padding:10px 14px;border-radius:16px 16px 4px 16px;
2849
+ background:var(--field);color:var(--ink);font-size:13.5px;line-height:1.6;white-space:pre-wrap;overflow-wrap:anywhere;}
2850
+ .ask-user-pills{justify-content:flex-end;margin-top:0;}
2851
+ .ask-attachment-pill{padding-right:12px;}
2852
+ .ask-attachment-thumb-link{display:inline-block;line-height:0;}
2853
+ .ask-attachment-thumb{max-width:180px;max-height:130px;border-radius:8px;border:1px solid var(--line);display:block;}
2854
+
2855
+ .ask-activity{border-left:1.5px solid var(--line);padding:2px 0 2px 14px;display:flex;flex-direction:column;gap:7px;}
2856
+ .ask-activity-head{display:flex;align-items:center;gap:9px;min-width:0;}
2857
+ .ask-dot{width:6px;height:6px;flex:0 0 6px;border-radius:50%;background:var(--seq);}
2858
+ .ask-dot-run{background:var(--violet);animation:wr-pulse 1.6s ease-in-out infinite;}
2859
+ .ask-dot-done{background:var(--green);animation:none;}
2860
+ .ask-dot-live{background:var(--green);}
2861
+ .ask-activity-label{font-weight:500;font-size:12px;color:var(--ink-2);white-space:nowrap;}
2862
+ .ask-activity-elapsed{font-family:var(--mono);font-size:11px;color:var(--ink-3);}
2863
+ .ask-activity-meter{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);white-space:nowrap;}
2864
+ /* The live turn is marked by the orb at the bottom of the message, so the head
2865
+ dot only has to say running-or-not: green while the turn is live, grey once
2866
+ it has landed. Scoped to the head — the sub-agent rows keep their own
2867
+ violet/green vocabulary. */
2868
+ .ask-activity-head .ask-dot-run{background:var(--green);}
2869
+ .ask-activity-head .ask-dot-done{background:var(--seq);}
2870
+
2871
+ /* ---------- Ask Worca: the live thinking row (bottom of the last message) --- */
2872
+ .ask-thinking{display:flex;align-items:center;gap:12px;min-width:0;padding:2px 0 0;}
2873
+ .ask-orb{flex:0 0 auto;}
2874
+ /* The label is painted THROUGH the text: a 220%-wide gradient clipped to the
2875
+ glyphs and swept across them, so the highlight travels word to word. Needs
2876
+ `color:transparent` — which is why the reduced-motion block below has to put
2877
+ a real colour back rather than only stopping the animation. */
2878
+ .ask-thinking-label{font-weight:500;font-size:12.5px;white-space:nowrap;
2879
+ background:linear-gradient(100deg,var(--ink-3) 0%,var(--ink-3) 34%,var(--ink) 50%,var(--ink-3) 66%,var(--ink-3) 100%);
2880
+ background-size:220% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;
2881
+ animation:ask-shimmer 2.4s linear infinite;}
2882
+ .ask-thinking-meter{font-family:var(--mono);font-size:11px;color:var(--seq);white-space:nowrap;}
2883
+ @keyframes ask-shimmer{from{background-position:180% 0;}to{background-position:-60% 0;}}
2884
+ /* The `.ask-dock *` blanket at the end of this file already kills the sweep;
2885
+ this only restores a legible fill under the clipped background. */
2886
+ @media (prefers-reduced-motion: reduce){
2887
+ .ask-thinking-label{background:none;-webkit-background-clip:border-box;background-clip:border-box;color:var(--ink-2);}
2888
+ }
2889
+ .ask-tool-row{display:flex;align-items:center;gap:9px;min-width:0;}
2890
+ .ask-tool-op{width:38px;flex:0 0 38px;font-family:var(--mono);font-size:10px;letter-spacing:.06em;
2891
+ text-transform:uppercase;color:var(--ink-3);}
2892
+ .ask-tool-target{min-width:0;font-family:var(--mono);font-size:11.5px;color:var(--ink-2);
2893
+ white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
2894
+ .ask-tool-note{flex:0 0 auto;font-family:var(--mono);font-size:10.5px;color:var(--seq);}
2895
+
2896
+ .ask-agents{margin-top:3px;display:flex;flex-direction:column;gap:2px;}
2897
+ .ask-agents-cap{display:flex;align-items:center;gap:8px;padding:2px 0 3px;font-weight:600;font-size:9.5px;
2898
+ letter-spacing:.12em;text-transform:uppercase;color:var(--seq);}
2899
+ .ask-agents-count{padding:1px 6px;border-radius:999px;background:var(--field);color:var(--ink-3);
2900
+ font-family:var(--mono);font-weight:400;font-size:9.5px;letter-spacing:0;text-transform:none;}
2901
+ .ask-agent-row{display:flex;align-items:center;gap:9px;width:100%;padding:5px 7px;border:0;border-radius:9px;
2902
+ background:transparent;text-align:left;cursor:pointer;font-family:inherit;transition:.15s;}
2903
+ .ask-agent-row:hover{background:var(--field);}
2904
+ .ask-agent-row:focus-visible{outline:2px solid var(--ink);outline-offset:-2px;}
2905
+ .ask-agent-name{min-width:0;font-family:var(--mono);font-size:11.5px;color:var(--ink);
2906
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
2907
+ .ask-agent-model,.ask-agent-tokens,.ask-agent-cost{flex:0 0 auto;font-family:var(--mono);font-size:10px;color:var(--ink-3);}
2908
+ .ask-agent-status{flex:0 0 auto;margin-left:auto;font-weight:500;font-size:10.5px;color:var(--ink-3);}
2909
+ .ask-agent-status.is-done{color:var(--green-ink);}
2910
+ .ask-agent-log{margin:1px 0 6px 22px;border:1px solid var(--line);border-radius:11px;overflow:hidden;}
2911
+ .ask-agent-log-head{display:flex;align-items:center;gap:10px;padding:6px 10px;border-bottom:1px solid var(--line);
2912
+ background:var(--panel);font-family:var(--mono);font-size:10px;color:var(--ink-3);}
2913
+ .ask-agent-log-type{margin-left:auto;}
2914
+ .ask-agent-log-body{max-height:104px;overflow-y:auto;padding:7px 10px;background:var(--field);
2915
+ font-family:var(--mono);font-size:10.5px;line-height:1.75;color:var(--ink-2);}
2916
+ .ask-agent-log-line{display:flex;gap:8px;min-width:0;}
2917
+ .ask-agent-log-t{flex:0 0 auto;color:var(--seq);}
2918
+ .ask-agent-log-text{min-width:0;white-space:pre-wrap;overflow-wrap:anywhere;}
2919
+
2920
+ .ask-answer{max-width:92%;font-size:13.5px;line-height:1.72;color:var(--ink);}
2921
+ .ask-answer-plain{white-space:pre-wrap;overflow-wrap:anywhere;}
2922
+ .ask-md :is(h1,h2,h3,h4,h5,h6){margin:14px 0 6px;font-size:13.5px;font-weight:600;color:var(--ink);}
2923
+ .ask-md h1{font-size:15px;}
2924
+ .ask-md h2{font-size:14px;}
2925
+ .ask-md p{margin:6px 0;}
2926
+ .ask-md :is(ul,ol){margin:6px 0;padding-left:22px;}
2927
+ .ask-md li{margin:2px 0;}
2928
+ .ask-md blockquote{margin:8px 0;padding:2px 12px;border-left:2px solid var(--line-2);color:var(--ink-2);}
2929
+ .ask-md hr{border:0;border-top:1px solid var(--line);margin:12px 0;}
2930
+ .ask-md a{color:var(--blue-ink);}
2931
+ .ask-md code{font-family:var(--mono);font-size:11.5px;background:var(--field);border-radius:4px;padding:1px 4px;}
2932
+ .ask-md pre{margin:8px 0;padding:10px 12px;background:var(--field);border:1px solid var(--line);
2933
+ border-radius:10px;overflow-x:auto;}
2934
+ .ask-md pre code{background:transparent;padding:0;font-size:11px;line-height:1.6;display:block;white-space:pre;}
2935
+ .ask-md table{border-collapse:collapse;margin:8px 0;font-size:12.5px;}
2936
+ .ask-md :is(th,td){border:1px solid var(--line);padding:4px 9px;text-align:left;}
2937
+ .ask-md th{background:var(--field);font-weight:600;}
2938
+ .ask-md input[type="checkbox"]{margin-right:6px;}
2939
+
2940
+ .ask-notice{font-size:12.5px;color:var(--ink-3);}
2941
+ .ask-notice-link{color:var(--blue-ink);}
2942
+ .ask-error-line{font-size:12.5px;color:var(--red-ink);}
2943
+
2944
+ .ask-card{margin-top:2px;display:flex;flex-direction:column;gap:10px;padding:13px 15px;
2945
+ border:1.5px solid var(--line);border-radius:16px;background:var(--panel);}
2946
+ .ask-card-title{font-weight:600;font-size:12.5px;color:var(--ink);}
2947
+ .ask-card-seg{display:flex;gap:4px;}
2948
+ .ask-card-seg-btn{padding:5px 12px;border:1px solid var(--line-2);border-radius:999px;background:transparent;
2949
+ color:var(--ink-2);font-family:inherit;font-weight:600;font-size:11px;cursor:pointer;transition:.15s;}
2950
+ .ask-card-seg-btn.on{background:var(--ink);border-color:var(--ink);color:var(--panel);}
2951
+ .ask-card-target{display:flex;flex-direction:column;gap:10px;}
2952
+ .ask-card-field{display:flex;flex-direction:column;gap:4px;}
2953
+ .ask-card-label{font-weight:600;font-size:10.5px;letter-spacing:.04em;text-transform:uppercase;color:var(--ink-3);}
2954
+ .ask-card :is(select,input[type="text"],textarea){font-size:12.5px;padding:8px 11px;border-radius:10px;}
2955
+ .ask-card textarea.ask-card-brief{min-height:0;height:auto;max-height:160px;resize:none;line-height:1.5;}
2956
+ .ask-card-members{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);}
2957
+ .ask-card-members-src summary{font-weight:600;font-size:10.5px;color:var(--ink-3);cursor:pointer;}
2958
+ .ask-card-members-src-list{display:flex;flex-direction:column;gap:8px;margin-top:8px;}
2959
+ .ask-card-err{font-size:12px;color:var(--red-ink);min-height:0;}
2960
+ .ask-card-scope-warn{font-size:12px;color:var(--amber-ink);background:var(--amber-wash);
2961
+ border:1px solid var(--amber-line);border-radius:8px;padding:6px 9px;}
2962
+ .ask-card-err:empty{display:none;}
2963
+ .ask-card-actions{display:flex;align-items:center;gap:8px;}
2964
+ .ask-card-open-np{border:0;background:transparent;padding:0;color:var(--blue-ink);font-family:inherit;
2965
+ font-size:11.5px;cursor:pointer;}
2966
+ .ask-card-not-now{padding:8px 13px;border:0;border-radius:999px;background:transparent;color:var(--ink-2);
2967
+ font-family:inherit;font-weight:600;font-size:12px;cursor:pointer;transition:.15s;}
2968
+ .ask-card-not-now:hover{background:var(--field);color:var(--ink);}
2969
+ .ask-card-start{display:flex;align-items:center;gap:7px;padding:8px 15px;border:0;border-radius:999px;
2970
+ background:var(--ink);color:var(--panel);font-family:inherit;font-weight:600;font-size:12px;cursor:pointer;transition:.15s;}
2971
+ .ask-card-start:hover{filter:brightness(1.08);}
2972
+ .ask-card-start:disabled{opacity:.55;cursor:default;}
2973
+ .ask-card :is(.ask-card-seg-btn,.ask-card-not-now,.ask-card-start,.ask-card-open-np):focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
2974
+ .ask-card-stub{font-size:12.5px;color:var(--ink-3);}
2975
+ .ask-card-failed{color:var(--red-ink);}
2976
+ .ask-card-link{color:var(--blue-ink);}
2977
+
2978
+ .ask-jump{position:absolute;left:0;right:0;bottom:104px;margin:0 auto;width:max-content;
2979
+ display:flex;align-items:center;gap:7px;padding:6px 12px;border:1px solid var(--line-2);border-radius:999px;
2980
+ background:var(--panel);color:var(--ink);font-family:inherit;font-weight:600;font-size:11.5px;cursor:pointer;
2981
+ box-shadow:0 6px 18px rgba(25,25,27,.10);transition:border-color .15s;}
2982
+ .ask-jump:hover{border-color:var(--ink);}
2983
+ .ask-jump[hidden]{display:none;}
2984
+ .ask-jump:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
2985
+
2986
+ .ask-composer{position:relative;flex:0 0 auto;border-top:1px solid var(--line);padding:10px 12px 11px;
2987
+ display:flex;flex-direction:column;gap:4px;}
2988
+ .ask-chips{display:flex;flex-wrap:wrap;gap:6px;padding:2px 4px 4px;}
2989
+ .ask-chips[hidden]{display:none;}
2990
+ .ask-chip{display:inline-flex;align-items:center;gap:7px;padding:5px 8px 5px 9px;border:1px solid var(--line);
2991
+ border-radius:8px;background:var(--field);color:var(--ink-2);font-family:var(--mono);font-size:11px;max-width:240px;}
2992
+ .ask-chip-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
2993
+ .ask-chip-thumb{width:24px;height:24px;object-fit:cover;border-radius:4px;flex:none;}
2994
+ .ask-chip-x{border:0;background:transparent;color:var(--ink-3);cursor:pointer;font-family:inherit;
2995
+ font-size:13px;line-height:1;padding:1px 4px;border-radius:5px;transition:.15s;}
2996
+ .ask-chip-x:hover{background:var(--line);color:var(--ink);}
2997
+ .ask-composer textarea.ask-input{min-height:0;height:auto;max-height:120px;resize:none;border:0;border-radius:0;
2998
+ background:transparent;padding:6px 6px 8px;font-size:13.5px;line-height:1.6;}
2999
+ .ask-composer textarea.ask-input:focus{background:transparent;border-color:transparent;}
3000
+ .ask-composer textarea.ask-input:focus-visible{outline:none;}
3001
+ .ask-composer-msg{font-size:11.5px;color:var(--red-ink);padding:0 6px;}
3002
+ .ask-composer-msg[hidden]{display:none;}
3003
+ .ask-composer-row{display:flex;align-items:center;gap:8px;position:relative;}
3004
+ .ask-meter{display:flex;align-items:center;gap:7px;font-family:var(--mono);font-size:10.5px;
3005
+ color:var(--ink-3);white-space:nowrap;}
3006
+ .ask-meter-sep{color:var(--line-2);}
3007
+ .ask-meter-cost{color:var(--ink-2);}
3008
+ .ask-agents-btn{display:flex;align-items:center;gap:6px;padding:5px 8px;border:0;border-radius:8px;
3009
+ background:transparent;color:var(--ink-3);font-family:var(--mono);font-size:10.5px;white-space:nowrap;
3010
+ cursor:pointer;transition:.15s;}
3011
+ .ask-agents-btn:hover{background:var(--field);color:var(--ink);}
3012
+ .ask-model-btn{display:flex;align-items:center;gap:7px;padding:6px 10px;border:0;border-radius:9px;
3013
+ background:transparent;color:var(--ink);font-family:inherit;font-weight:600;font-size:12.5px;cursor:pointer;transition:.15s;}
3014
+ .ask-model-btn:hover{background:var(--field);}
3015
+ .ask-model-btn-effort{font-weight:500;color:var(--ink-3);}
3016
+ .ask-send,.ask-stop{display:inline-flex;align-items:center;justify-content:center;width:31px;height:31px;
3017
+ flex:0 0 31px;border-radius:50%;cursor:pointer;transition:.15s;}
3018
+ .ask-send{border:0;background:var(--ink);color:var(--panel);}
3019
+ .ask-send:hover{filter:brightness(1.08);}
3020
+ .ask-send[hidden]{display:none;}
3021
+ .ask-stop{border:1.5px solid var(--line);background:var(--panel);color:var(--ink);}
3022
+ .ask-stop:hover{border-color:var(--ink);}
3023
+ .ask-stop[hidden]{display:none;}
3024
+ .ask-composer :is(.ask-agents-btn,.ask-wt-btn,.ask-model-btn,.ask-send,.ask-stop,.ask-chip-x):focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
3025
+ /* The button shares .ask-agents-btn, which is display:flex — that AUTHOR rule
3026
+ outranks the UA [hidden]{display:none}, so without this twin the button never
3027
+ hides and shows a permanent "0 worktrees". */
3028
+ .ask-wt-btn[hidden]{display:none;}
3029
+
3030
+ .ask-pop{position:absolute;z-index:3;background:var(--panel);border:1px solid var(--line-2);
3031
+ padding:6px;box-shadow:0 12px 34px rgba(25,25,27,.12);border-radius:16px;}
3032
+ .ask-pop-threads{top:46px;right:76px;width:326px;}
3033
+ .ask-pop-scope{left:16px;bottom:44px;width:300px;max-height:min(420px,70%);overflow-y:auto;}
3034
+ .ask-pop-model{right:30px;bottom:42px;width:292px;border-radius:18px;}
3035
+ .ask-pop-runinfo{right:96px;bottom:44px;width:326px;z-index:4;}
3036
+ .ask-pop-caption,.ask-pop-caption-row .ask-pop-caption{padding:8px 10px 6px;font-weight:600;font-size:10px;
3037
+ letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3);}
3038
+ .ask-pop-caption-row{display:flex;align-items:baseline;gap:8px;}
3039
+ .ask-pop-caption-meter{margin-left:auto;padding-right:10px;font-family:var(--mono);font-size:10px;color:var(--ink-3);}
3040
+ .ask-pop-empty{padding:10px 10px 12px;font-size:12px;color:var(--ink-3);}
3041
+ .ask-pop-divider{height:1px;margin:6px 11px;background:var(--line);}
3042
+ .ask-pop-item{display:flex;align-items:center;gap:10px;width:100%;padding:9px 11px;border:0;border-radius:12px;
3043
+ background:transparent;text-align:left;font-family:inherit;cursor:pointer;transition:.15s;}
3044
+ .ask-pop-item:hover,.ask-pop-item:focus-visible{background:var(--field);outline:none;}
3045
+ .ask-model-name{font-weight:500;font-size:13.5px;color:var(--ink);}
3046
+ .ask-model-check{margin-left:auto;color:var(--blue-ink);font-weight:600;}
3047
+ .ask-pop-row-value{margin-left:auto;font-weight:400;font-size:12.5px;color:var(--ink-3);}
3048
+ .ask-pop-row-chev{color:var(--ink-3);}
3049
+ /* .ask-pop-model is a fixed 292px panel (256px of row content), so something in the
3050
+ row has to give: rigid badges win every negotiation, and measured in Chrome an
3051
+ origin badge -- "DS Fast" + "plugin: discretestack-models" + "secret not set" --
3052
+ starved the name to 0px and spilled the row 45px past the panel, which sets no
3053
+ overflow of its own (.ask-sheet then chops the check mark off). The row shows no
3054
+ provenance at all now, so its only variable-width text IS the name: it shrinks to
3055
+ a legible floor and never grows (badges keep hugging it), while the two status
3056
+ badges opt out of shrinking -- ellipsising "⚠cost" to a bare "…" would eat the
3057
+ warning itself. The shrink props on the base badge outlive the origin badge in
3058
+ case a plain one returns. */
3059
+ .ask-model-tag{flex:0 1 auto;min-width:0;max-width:45%;overflow:hidden;text-overflow:ellipsis;
3060
+ font-size:10.5px;font-weight:700;letter-spacing:.01em;padding:2px 8px;
3061
+ border-radius:999px;white-space:nowrap;background:var(--field);color:var(--ink-3);}
3062
+ .ask-model-tag.is-warn{flex:0 0 auto;max-width:none;background:var(--amber-bg);color:var(--amber-ink);}
3063
+ .ask-model-tag.is-err{flex:0 0 auto;max-width:none;background:var(--red-bg);color:var(--red-ink);}
3064
+ .ask-model-item .ask-model-name{flex:0 1 auto;min-width:4.5ch;overflow:hidden;text-overflow:ellipsis;
3065
+ white-space:nowrap;}
3066
+ /* The threads list is the only scrollport in a popover: /api/ask/threads returns up
3067
+ to 50 rows and .ask-sheet clips (overflow:hidden), so without a cap the tail is
3068
+ unreachable. 360px caps it on a tall window; the vh term keeps it inside the sheet
3069
+ on a short one -- the popover starts 46px down and the sheet is 100vh - 46px at most.
3070
+ The radius clips the rows so they do not bleed over the panel's own corners. */
3071
+ .ask-threads-list{max-height:min(360px,calc(100vh - 200px));overflow-y:auto;overscroll-behavior:contain;
3072
+ border-radius:10px;}
3073
+ .ask-thread-row{display:flex;align-items:center;border-radius:10px;}
3074
+ .ask-thread-row:hover{background:var(--field);}
3075
+ .ask-thread-pick{flex:1 1 auto;min-width:0;padding:8px 4px 8px 10px;border-radius:10px;}
3076
+ /* The date leads the meter line: bold and on --ink so it anchors the scan while
3077
+ the ctx/cost/agent figures beside it keep the meter's grey. An inline span, so
3078
+ the mono and the 10px come from .ask-thread-meter; nowrap is what now keeps it
3079
+ on one line, flex:0 0 auto having gone with the left-edge column. */
3080
+ .ask-thread-when{font-weight:700;color:var(--ink);white-space:nowrap;}
3081
+ /* Scoped to the threads rows -- .ask-dot elsewhere (agents popover, agent rows,
3082
+ the activity head) keeps showing its --seq dot. Here a dot means "this chat is
3083
+ live", so an idle row shows none; display, not visibility, so the 6px slot and
3084
+ the row's 10px gap collapse with it and the title moves to the left edge. */
3085
+ .ask-thread-dot{display:none;}
3086
+ .ask-thread-dot.ask-dot-live{display:block;}
3087
+ .ask-thread-col{display:flex;flex-direction:column;gap:1px;min-width:0;}
3088
+ .ask-thread-title{font-weight:500;font-size:12.5px;color:var(--ink);display:-webkit-box;
3089
+ -webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden;}
3090
+ .ask-thread-meter{font-family:var(--mono);font-size:10px;color:var(--ink-3);}
3091
+ .ask-thread-trash{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;
3092
+ flex:0 0 26px;margin-right:5px;border:0;border-radius:8px;background:transparent;color:var(--seq);
3093
+ cursor:pointer;transition:.15s;}
3094
+ .ask-thread-trash:hover{background:var(--red-bg);color:var(--red-ink);}
3095
+ .ask-thread-trash:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
3096
+ .ask-runinfo-row{display:flex;align-items:center;gap:9px;padding:7px 10px;border-radius:10px;}
3097
+ .ask-runinfo-row:hover{background:var(--field);}
3098
+ .ask-runinfo-col{display:flex;flex-direction:column;gap:1px;min-width:0;}
3099
+ .ask-runinfo-name{font-family:var(--mono);font-size:11.5px;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
3100
+ .ask-runinfo-sub{font-family:var(--mono);font-size:10px;color:var(--ink-3);}
3101
+ .ask-runinfo-elapsed{margin-left:auto;font-family:var(--mono);font-size:10px;color:var(--seq);}
3102
+ /* .ask-pop-worktrees inherits .ask-pop-runinfo's right/bottom offset, which is
3103
+ calibrated for the AGENTS button; the worktrees button sits to its left. */
3104
+ .ask-pop-worktrees{min-width:340px;right:187px;}
3105
+ .ask-wt-row .ask-runinfo-sub.ask-wt-path{cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:260px;}
3106
+ .ask-wt-row .ask-thread-trash{flex:none;}
3107
+
3108
+ /* ---------- Ask Worca dock clearance ---------- */
3109
+ /* The dock is a fixed overlay -- .ask-dock above: 26px dock padding-bottom plus the
3110
+ ~47px pill occludes a ~73px strip at the bottom of every scrollport, while the page
3111
+ gutters reserve only 40px. One late override lifts the BOTTOM padding of all five
3112
+ scroll gutters to --ask-dock-clearance: 96px = 73px strip + breathing room. Source
3113
+ order is load-bearing -- this must follow the base rules at :234, :1718, :1742,
3114
+ :2312, :2335 and precede the reduced-motion block below. The -list screen classes
3115
+ are targeted, NOT .run-screen/.hist-screen, so the -detail scrollports keep
3116
+ padding:0 for their sticky tab bars and take clearance via .rd-body/.hd-body
3117
+ instead; body.view-history/.view-running .main keep padding:0 by specificity. */
3118
+ .main,.run-screen-list,.hist-screen-list,.rd-body,.hd-body{padding-bottom:var(--ask-dock-clearance);}
3119
+
3120
+ /* ---------- native control skins (checkbox / radio / switch / rows) -------- */
3121
+ /* Everything the browser used to draw for itself. The mark is the one .qopt
3122
+ already draws (:761): a 1.5px --radio-ring hairline on --panel that fills
3123
+ --green behind a white glyph, so "chosen" reads identically on a question
3124
+ option, a checkbox, a chip and a whole row.
3125
+ Styling the REAL <input> through appearance:none — rather than hiding it
3126
+ behind a span — is what lets every existing emitter keep its markup:
3127
+ :checked, :disabled, :indeterminate, label clicks and keyboard all keep
3128
+ working with no JS. NOTHING here sets `display`, so the hidden inputs behind
3129
+ the segmented controls (#mock, [name=target], [name=source]) stay hidden. */
3130
+
3131
+ /* Net for anything never hand-skinned (a future <progress>, a date input, the
3132
+ OS spell-check UI): brand green instead of the OS accent. */
3133
+ :root{accent-color:var(--green);}
3134
+
3135
+ input[type="checkbox"],
3136
+ input[type="radio"]{
3137
+ appearance:none;-webkit-appearance:none;
3138
+ flex:0 0 auto;width:18px;height:18px;margin:0;padding:0;
3139
+ border:1.5px solid var(--radio-ring);border-radius:6px;
3140
+ background:var(--panel);cursor:pointer;vertical-align:middle;
3141
+ transition:background-color .14s,border-color .14s;}
3142
+ input[type="radio"]{border-radius:50%;}
3143
+ input[type="checkbox"]:hover:not(:disabled),
3144
+ input[type="radio"]:hover:not(:disabled){border-color:var(--ink-3);}
3145
+ /* the identical white tick .qopt.sel uses */
3146
+ input[type="checkbox"]:checked{border-color:var(--green);
3147
+ background:var(--green) center/11px 11px no-repeat url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.6' stroke-linecap='round' stroke-linejoin='round'><path d='M5 13l4 4L19 7'/></svg>");}
3148
+ /* "some of these are on" — set from JS, there is no HTML attribute for it */
3149
+ input[type="checkbox"]:indeterminate{border-color:var(--green);
3150
+ background:var(--green) center/11px 11px no-repeat url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.6' stroke-linecap='round'><path d='M6 12h12'/></svg>");}
3151
+ /* ring, white gap and dot from one inset shadow — no pseudo-element, which a
3152
+ replaced element cannot be relied on to render */
3153
+ input[type="radio"]:checked{border-color:var(--green);background:var(--green);
3154
+ box-shadow:inset 0 0 0 3.5px var(--panel);}
3155
+ /* disabled reads as "fixed" (a manifest locked it), never as "broken" */
3156
+ input[type="checkbox"]:disabled,
3157
+ input[type="radio"]:disabled{background:var(--field);border-color:var(--line-2);cursor:not-allowed;}
3158
+ /* the :disabled rule above uses the `background` shorthand, which drops the
3159
+ glyph :checked painted — so a locked-on box has to restate it */
3160
+ input[type="checkbox"]:disabled:checked{border-color:var(--seq);
3161
+ background:var(--seq) center/11px 11px no-repeat url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.6' stroke-linecap='round' stroke-linejoin='round'><path d='M5 13l4 4L19 7'/></svg>");}
3162
+ input[type="radio"]:disabled:checked{background:var(--seq);border-color:var(--seq);
3163
+ box-shadow:inset 0 0 0 3.5px var(--panel);}
3164
+ input[type="checkbox"]:focus-visible,
3165
+ input[type="radio"]:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
3166
+ /* markdown task lists (:3019 sets the gap; the blanket margin:0 above would
3167
+ otherwise win on source order) */
3168
+ .ask-md input[type="checkbox"]{margin-right:6px;}
3169
+
3170
+ @media (prefers-contrast:more),(forced-colors:active){
3171
+ input[type="checkbox"],input[type="radio"]{border-color:CanvasText;}
3172
+ input[type="checkbox"]:checked,input[type="radio"]:checked{background-color:Highlight;border-color:Highlight;}
3173
+ }
3174
+
3175
+ /* ---- switch driven by a real checkbox ----
3176
+ .switch (:470) is a <div role="switch"> mirrored to a hidden input by hand
3177
+ (app.js#wireMockSwitch). One sibling selector lets the SAME pixels be driven
3178
+ by a real <input type=checkbox>, so a new switch needs no JS and keeps its
3179
+ semantics for free. Both spellings work; the existing .on emitters are
3180
+ untouched. */
3181
+ .sw-input{position:absolute;width:1px;height:1px;opacity:0;margin:0;pointer-events:none;}
3182
+ /* The checkbox is position:absolute; without a positioned wrapper its
3183
+ containing block is the viewport, so its static position sits near the top
3184
+ of the page — clicking the switch focuses it and the browser scrolls that
3185
+ into view, jerking the whole viewport up. Anchor it to its own row. */
3186
+ :where(label, div, span):has(> .sw-input){position:relative;}
3187
+ .switch.on,
3188
+ .sw-input:checked + .switch{background:var(--green);}
3189
+ .switch.on::after,
3190
+ .sw-input:checked + .switch::after{left:21px;}
3191
+ .sw-input:focus-visible + .switch{outline:2px solid var(--ink);outline-offset:2px;}
3192
+ .sw-input:disabled + .switch{opacity:.5;cursor:not-allowed;}
3193
+ /* A 20px track needs more room between rows than the 13px box it replaced. */
3194
+ .chat-event-row,.chat-channel-row{margin:8px 0;gap:10px;}
3195
+ .switch.switch-sm{width:34px;height:20px;}
3196
+ .switch.switch-sm::after{width:15px;height:15px;top:2.5px;left:2.5px;}
3197
+ .sw-input:checked + .switch.switch-sm::after{left:16.5px;}
3198
+
3199
+ /* ---- checkbox rows inside a .field ----
3200
+ .check-row carried NO rule, so `.field > label` (:304) claimed it: block,
3201
+ 600 weight, 13px, 8px bottom margin — which is why "No cap" rendered as a
3202
+ heading welded to its box. Equal specificity class-count, so the qualified
3203
+ selectors are what win. */
3204
+ .check-row{display:inline-flex;align-items:center;gap:9px;cursor:pointer;user-select:none;
3205
+ font-weight:500;font-size:12.5px;color:var(--ink-2);}
3206
+ .field > label.check-row,
3207
+ .field > label.fanout-toggle{display:inline-flex;margin-bottom:0;font-weight:500;}
3208
+
3209
+ /* ---- selectable rows ----
3210
+ .qopt generalised: when a choice carries more than a word (a name plus a
3211
+ badge, a path, a model id) the row itself is the target and the whole row
3212
+ shows the selection. Appended here, so it wins the source-order tie against
3213
+ the .grv-source-row / .wiz-proj / .mvx-model base rules those emitters keep
3214
+ for their own layout; their qualified rules (.wiz-proj.missing) still win. */
3215
+ .opt-row{display:flex;align-items:center;gap:11px;width:100%;
3216
+ padding:12px 15px;background:var(--panel);border:1.5px solid var(--line);
3217
+ border-radius:13px;cursor:pointer;font-size:13.5px;font-weight:500;
3218
+ color:var(--ink);transition:background .14s,border-color .14s;}
3219
+ .opt-row:hover{border-color:var(--ink-3);}
3220
+ .opt-row:has(input:checked){background:var(--green-bg);border-color:var(--green);}
3221
+ .opt-row:has(input:disabled){opacity:.55;cursor:not-allowed;background:var(--field);}
3222
+ .opt-row:has(input:focus-visible){outline:2px solid var(--ink);outline-offset:2px;}
3223
+ .opt-row .opt-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
3224
+ .opt-row .opt-sub{flex:0 0 auto;font:400 11.5px var(--mono);color:var(--ink-3);}
3225
+ .wiz-proj-list{gap:8px;}
3226
+
3227
+ /* ---- chips ----
3228
+ .chip-select (:1400) already drew the pill and never had a selected state,
3229
+ so the box inside was carrying it alone. */
3230
+ .chip-select label{transition:background .14s,border-color .14s;}
3231
+ .chip-select label:hover{border-color:var(--ink-3);}
3232
+ .chip-select label:has(input:checked){background:var(--green-bg);border-color:var(--green);}
3233
+ .chip-select label:has(input:disabled){opacity:.55;cursor:not-allowed;}
3234
+ .chip-select input{width:14px;height:14px;border-radius:5px;}
3235
+
3236
+ /* ---- confirm/prompt modal internals ---- */
3237
+ .confirm-field{margin-bottom:14px;}
3238
+ .confirm-field > label{display:block;font-weight:600;font-size:12.5px;margin-bottom:6px;}
3239
+ .confirm-field .input{font-size:13.5px;padding:11px 14px;}
3240
+ .confirm-field .hint.err{color:var(--red-ink);}
3241
+ .confirm-checkbox{display:flex;align-items:flex-start;gap:10px;margin:0 0 4px;padding:12px 14px;
3242
+ background:var(--field);border-radius:12px;font-size:12.5px;color:var(--ink-2);
3243
+ cursor:pointer;line-height:1.45;}
3244
+ .confirm-checkbox.hidden{display:none;}
3245
+ .confirm-modal .card h2.danger{color:var(--red-ink);}
3246
+
3247
+ /* ---- the rest of the UA chrome ---- */
3248
+ /* min/max/step are there to validate, not to be clicked */
3249
+ input[type="number"]{-moz-appearance:textfield;}
3250
+ input[type="number"]::-webkit-outer-spin-button,
3251
+ input[type="number"]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0;}
3252
+ /* the WebKit search "x" is grey-on-grey and off-grid; .field-clear replaces it */
3253
+ input[type="search"]::-webkit-search-cancel-button,
3254
+ input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}
3255
+ .field-clear{position:absolute;right:10px;top:50%;transform:translateY(-50%);
3256
+ width:18px;height:18px;border:none;border-radius:50%;background:var(--line-2);
3257
+ color:var(--ink-2);font:600 11px/1 var(--sans);cursor:pointer;display:grid;
3258
+ place-items:center;padding:0;}
3259
+ .field-clear:hover{background:var(--ink-3);color:#fff;}
3260
+ .field-clear[hidden]{display:none;}
3261
+ /* the OS disclosure triangle, everywhere — .advanced (:601) already did it locally */
3262
+ .disclosure > summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:8px;}
3263
+ .disclosure > summary::-webkit-details-marker{display:none;}
3264
+ .disclosure > summary::before{content:"";width:0;height:0;flex:0 0 auto;
3265
+ border:4.5px solid transparent;border-left-color:var(--ink-3);
3266
+ transition:transform .16s;transform-origin:2px 50%;}
3267
+ .disclosure[open] > summary::before{transform:rotate(90deg);}
3268
+ /* Firefox has no ::-webkit-scrollbar, so :899 never reaches it */
3269
+ html{scrollbar-color:var(--line-2) transparent;scrollbar-width:thin;}
3270
+
2507
3271
  /* ---------- reduced motion for the Running redesign ---------- */
2508
3272
  /* MUST be the LAST block in the file: @media contributes no specificity, so a
2509
3273
  (0,1,0) `animation:none` here would LOSE the source-order tie against any
@@ -2524,5 +3288,499 @@ body.view-running .topnav{margin:12px 32px 0;flex:0 0 auto;}
2524
3288
  Tasks 3/6/7/8 and silently rot the day one of them is renamed. The run-graph's
2525
3289
  own reduced-motion block (:1253-1258) is unaffected — it substitutes
2526
3290
  box-shadows, and only its redundant `animation:none` is overridden. */
3291
+ /* Ask Worca: every element inside the dock, same blanket rationale as the
3292
+ .run-shell one below. */
3293
+ .ask-dock *{animation:none !important;}
2527
3294
  .run-shell *{animation:none !important;}
2528
3295
  }
3296
+
3297
+ /* ========================================================================== */
3298
+ /* v2 node-graph canvas — EVERY geometry number comes from the --gv-* custom */
3299
+ /* properties injectGeometry() writes on .gv-stage at mount, so the CSS box */
3300
+ /* model cannot drift from nodeSize()/portAnchor(). Never hard-code a px */
3301
+ /* geometry value here (test/ui-graph-css.test.mjs enforces it). */
3302
+ /* Scoped under .gv-world because style.css still declares `.run-flow .node` */
3303
+ /* status rules the v2 graph is hosted inside; the resets below neutralise */
3304
+ /* every property they leak. (The UNSCOPED v1 `.node` rules are gone.) */
3305
+ /* ========================================================================== */
3306
+ .gv-canvas{position:relative;overflow:hidden;touch-action:none;user-select:none;-webkit-user-select:none;
3307
+ background:#FBFBF9 radial-gradient(circle,var(--line-2) 1.1px,transparent 1.1px);background-size:22px 22px;}
3308
+ .gv-stage{position:absolute;inset:0;outline:none;}
3309
+ .gv-stage.panning{cursor:grabbing;}
3310
+ .gv-stage.space{cursor:grab;}
3311
+ .gv-world{position:absolute;left:0;top:0;width:0;height:0;transform-origin:0 0;will-change:transform;}
3312
+ .gv-wires{position:absolute;left:0;top:0;overflow:visible;pointer-events:none;z-index:1;}
3313
+ /* THE rule that makes a filled ghost blob structurally impossible. */
3314
+ .gv-wires path{fill:none;stroke:var(--seq);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
3315
+ .gv-wires path.loop{stroke:var(--amber);}
3316
+ .gv-wires path.sel{stroke:var(--ink);stroke-width:2.5;}
3317
+ .gv-wires path.bad{stroke:var(--red);}
3318
+ .gv-wires path.ghost{stroke:var(--ink-3);stroke-dasharray:5 4;visibility:hidden;}
3319
+ .gv-wires path.ghost.on{visibility:visible;}
3320
+ .gv-wires path.ghost.legal{stroke:var(--green);stroke-dasharray:none;}
3321
+ .gv-wires path.ghost.illegal{stroke:var(--red);}
3322
+ .gv-world .node{position:absolute;left:0;top:0;display:block;padding:0;gap:0;align-items:stretch;z-index:2;
3323
+ width:var(--gv-node-w);background:#fff;border:var(--gv-border) solid var(--line-2);border-radius:16px;
3324
+ box-shadow:var(--shadow-soft);cursor:grab;transition:box-shadow .12s;}
3325
+ .gv-world .node::before{content:none;}
3326
+ .gv-world .node:hover{box-shadow:var(--shadow);}
3327
+ .gv-world .node.sel{outline:2px solid var(--ink);outline-offset:2px;}
3328
+ .gv-world .node.dragging{cursor:grabbing;transition:none;}
3329
+ .gv-static .node,.gv-static .wbadge{pointer-events:none;}
3330
+ .gv-world .nhead{height:var(--gv-head-h);display:flex;align-items:center;gap:8px;padding:0 11px;
3331
+ border-radius:14.5px 14.5px 0 0;font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
3332
+ .gv-world .nhead svg{width:15px;height:15px;flex:0 0 auto;stroke-width:1.85;}
3333
+ .gv-world .nhead .tt{overflow:hidden;text-overflow:ellipsis;}
3334
+ .gv-world .h-flow{background:var(--ink);color:#fff;}
3335
+ .gv-world .h-violet{background:var(--violet-bg);color:var(--violet-ink);}
3336
+ .gv-world .h-blue{background:#DEEFF7;color:#2E6E8E;}
3337
+ .gv-world .h-green{background:#E2F3DF;color:#3B7C3B;}
3338
+ .gv-world .h-red{background:var(--red-bg);color:var(--red-ink);}
3339
+ .gv-world .h-peach{background:#FCEEDA;color:#8A5A16;}
3340
+ .gv-world .h-amber{background:var(--amber-bg);color:var(--amber-ink);}
3341
+ .gv-world .nbody{padding:var(--gv-pad-t) 0 var(--gv-pad-b);}
3342
+ .gv-world .prow{position:relative;height:var(--gv-row-h);display:flex;align-items:center;gap:7px;padding:0 13px;}
3343
+ .gv-world .prow.out{justify-content:flex-end;}
3344
+ .gv-world .prow .pn{font-size:12.5px;font-weight:500;}
3345
+ .gv-world .prow .pt{font:10px/1.3 var(--mono);color:var(--ink-3);}
3346
+ .gv-world .prow .pt.cond{color:var(--amber-ink);}
3347
+ .gv-world .prow .mla{margin-left:auto;}
3348
+ /* The global .chip (:692) is a 7px/13px page pill — ~28px tall inside a 24px
3349
+ --gv-row-h port row, so it spilled over the neighbouring rows of every card
3350
+ with a loop / fan-out input (the "loop" / "⤫N" pills seen on running cards).
3351
+ Same override the palette needs (.ap .chip); metrics mirror the loop wire's
3352
+ .wbadge so "loop" on the port and "2×" on the wire read as one family. */
3353
+ .gv-world .prow .chip{flex:0 0 auto;font:700 10px/1.4 var(--mono);padding:1px 7px;border-radius:999px;
3354
+ white-space:nowrap;background:var(--field);color:var(--ink-3);}
3355
+ .gv-world .prow .chip.am{background:var(--amber-bg);color:var(--amber-ink);}
3356
+ .gv-world .prow .chip.fan{background:var(--blue-bg);color:var(--blue-ink);}
3357
+ .gv-world .psep{height:var(--gv-sep-h);position:relative;}
3358
+ .gv-world .psep::after{content:"";position:absolute;left:13px;right:13px;top:4px;height:1px;background:var(--line);}
3359
+ /* dot centre = row centre: top = (ROW_H − DOT)/2; x overhang = DOT/2 + BORDER,
3360
+ so the anchor lands exactly on node.x / node.x + NODE_W. */
3361
+ .gv-world .dot,.gv-world .gdot{position:absolute;top:calc((var(--gv-row-h) - var(--gv-dot)) / 2);
3362
+ width:var(--gv-dot);height:var(--gv-dot);border-radius:50%;}
3363
+ .gv-world .prow.in .dot,.gv-world .prow.in .gdot{left:calc(-1 * (var(--gv-dot) / 2 + var(--gv-border)));}
3364
+ .gv-world .prow.out .dot{right:calc(-1 * (var(--gv-dot) / 2 + var(--gv-border)));}
3365
+ .gv-world .dot.md{background:var(--ink);}
3366
+ .gv-world .dot.json{background:var(--blue);}
3367
+ .gv-world .dot.void{background:#fff;border:2px solid var(--ink-3);}
3368
+ .gv-world .dot.any{background:#fff;border:2px solid var(--ink);}
3369
+ .gv-world .dia{position:absolute;top:8px;right:calc(-1 * (4px + var(--gv-border)));width:8px;height:8px;
3370
+ background:var(--amber);border-radius:2px;transform:rotate(45deg);}
3371
+ .gv-world .gdot{background:var(--field);border:2px dashed var(--seq);}
3372
+ .gv-world .prow.gate .pn,.gv-world .prow.gate .pt{color:var(--ink-3);}
3373
+ .gv-world .prow.gate.wired .gdot{background:#fff;border-color:var(--ink);}
3374
+ .gv-world .prow.gate.wired .pn{color:var(--ink);}
3375
+ .gv-world .prow.drop-ok .dot,.gv-world .prow.drop-ok .gdot{transform:scale(1.35);box-shadow:0 0 0 3px #D8EFD6;border-color:var(--green);}
3376
+ .gv-world .prow.drop-bad .dot,.gv-world .prow.drop-bad .gdot{box-shadow:0 0 0 3px var(--red-bg);border-color:var(--red);}
3377
+ .gv-world .npip{position:absolute;top:-7px;left:-7px;width:14px;height:14px;border-radius:50%;
3378
+ background:var(--red);border:2px solid #fff;cursor:pointer;z-index:3;}
3379
+ .gv-world .wbadge{position:absolute;transform:translate(-50%,-50%);z-index:2;background:var(--amber-bg);
3380
+ color:var(--amber-ink);border:1px solid var(--amber);border-radius:999px;padding:1px 7px;
3381
+ font:10px/1.4 var(--mono);pointer-events:none;}
3382
+ /* Run-mode executions footer (P6 fills it through view.setFooter, which is the
3383
+ ONE writer of a run card's height). Heights MUST stay var(--gv-foot-h) /
3384
+ var(--gv-exec-row-h): nodeSize bills FOOT_H for the first footer LINE and
3385
+ EXEC_ROW_H for each extra one (a wrapped fan line, a stacked exec row's extra
3386
+ lines), and test/ui-graph-css.test.mjs asserts style.css uses the FULL --gv-*
3387
+ set injectGeometry writes — dropping them fails that assertion. */
3388
+ .gv-world .xfoot{position:absolute;left:0;right:0;bottom:0;border-top:1px solid var(--line);
3389
+ border-radius:0 0 14.5px 14.5px;overflow:hidden;background:var(--panel);}
3390
+ .gv-world .xfoot>.fan,.gv-world .xfoot>.xtoggle,.gv-world .xfoot>.xresult{height:var(--gv-exec-row-h);
3391
+ display:flex;align-items:center;gap:7px;padding:0 11px;width:100%;box-sizing:border-box;}
3392
+ .gv-world .xfoot>:first-child{height:var(--gv-foot-h);} /* the first line is the tall one nodeSize bills */
3393
+ .gv-world .xfoot>.fan.f2{height:calc(var(--gv-foot-h) + var(--gv-exec-row-h));} /* two wrapped square lines */
3394
+ .gv-world .xfoot>.xtoggle{border:none;background:none;font:inherit;color:inherit;cursor:pointer;text-align:left;}
3395
+ .gv-world .xfoot>.xrow{height:var(--gv-exec-row-h);display:flex;align-items:center;gap:7px;padding:0 11px;}
3396
+ /* stacked exec rows: dur · cost on its own right-aligned line (.stack), the
3397
+ label on up to two clamped lines above it (.l2) — run-decor execBandLayout
3398
+ bills exactly these line counts, so the grid rows must stay --gv-exec-row-h */
3399
+ .gv-world .xfoot>.xrow.stack{height:calc(2*var(--gv-exec-row-h));display:grid;
3400
+ grid-template-columns:auto minmax(0,1fr);grid-auto-rows:var(--gv-exec-row-h);column-gap:7px;row-gap:0;}
3401
+ .gv-world .xfoot>.xrow.stack.l2{height:calc(3*var(--gv-exec-row-h));}
3402
+ .gv-world .xfoot>.xrow.stack .led{grid-row:1;grid-column:1;}
3403
+ .gv-world .xfoot>.xrow.stack .xl{grid-row:1;grid-column:2;}
3404
+ .gv-world .xfoot>.xrow.stack .xr{grid-row:2;grid-column:2;}
3405
+ .gv-world .xfoot>.xrow.stack.l2 .xl{grid-row:1 / span 2;white-space:normal;display:-webkit-box;
3406
+ -webkit-box-orient:vertical;-webkit-line-clamp:2;line-height:var(--gv-exec-row-h);}
3407
+ .gv-world .xfoot>.xrow.stack.l2 .xr{grid-row:3;}
3408
+ /* fan squares wrap inside a fixed --gv-fan-w column (FAN_PER_ROW per line) so
3409
+ they keep their 7px and the ×N tail keeps its right-hand column */
3410
+ .gv-world .xfoot .fsq{display:flex;flex-wrap:wrap;flex:0 0 var(--gv-fan-w);max-width:var(--gv-fan-w);
3411
+ column-gap:3px;row-gap:7px;}
3412
+ .gv-world .xfoot .xsq{display:flex;gap:3px;flex:0 0 auto;}
3413
+ .gv-world .xfoot .xq,.gv-world .xfoot .sq{width:7px;height:7px;border-radius:2px;background:var(--line-2);flex:0 0 auto;}
3414
+ .gv-world .xfoot .xq.is-active,.gv-world .xfoot .sq.on{background:var(--blue);}
3415
+ .gv-world .xfoot .xq.is-done{background:var(--green);}
3416
+ .gv-world .xfoot .xq.is-error{background:var(--red);}
3417
+ .gv-world .xfoot .xsum,.gv-world .xfoot .xl{font-size:11.5px;color:var(--ink-2);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
3418
+ .gv-world .xfoot .xr,.gv-world .xfoot .fl{margin-left:auto;font:10px/1.4 var(--mono);color:var(--ink-3);white-space:nowrap;flex:0 0 auto;}
3419
+ .gv-world .xfoot .chev{width:13px;height:13px;margin-left:auto;flex:0 0 auto;}
3420
+ .gv-world .xfoot .led{width:8px;height:8px;border-radius:50%;background:var(--line-2);flex:0 0 auto;}
3421
+ .gv-world .xfoot .xrow.is-active .led{background:var(--blue);}
3422
+ .gv-world .xfoot .xrow.is-done .led{background:var(--green);}
3423
+ .gv-world .xfoot .xrow.is-error .led{background:var(--red);}
3424
+ .gv-world .ngate{position:absolute;top:-7px;right:-7px;width:15px;height:15px;border-radius:50%;
3425
+ background:var(--amber-bg);color:var(--amber-ink);border:1px solid var(--amber);
3426
+ font:9px/13px var(--sans);text-align:center;z-index:3;}
3427
+ .gv-world .nrun{position:absolute;top:-9px;right:16px;display:flex;gap:6px;background:#fff;
3428
+ border:1px solid var(--line-2);border-radius:999px;padding:1px 8px;font:10px/1.4 var(--mono);color:var(--ink-3);}
3429
+ .gv-world .wbadge .wfired{margin-left:5px;font-weight:700;}
3430
+ .gv-chip{position:absolute;z-index:6;pointer-events:none;background:var(--red-bg);color:var(--red-ink);
3431
+ border:1px solid var(--red);border-radius:8px;padding:4px 8px;font-size:11px;font-weight:600;white-space:nowrap;}
3432
+ .gv-chip[hidden]{display:none;}
3433
+ @media (prefers-reduced-motion:reduce){.gv-world .node{transition:none;}}
3434
+
3435
+ /* ========================================================================== */
3436
+ /* v2 composer shell — header bar / canvas / agents card / saved list. */
3437
+ /* The three cards run EDGE TO EDGE (padding:0 + overflow:hidden), the way the */
3438
+ /* v1 builder card did: every band below owns its own inset, so a card keeping */
3439
+ /* the default .card 24px padding would double it and leave the head bars */
3440
+ /* floating inside a white gutter. */
3441
+ /* ========================================================================== */
3442
+ .gv-editor,.gv-saved{padding:0;overflow:hidden;}
3443
+ /* The composer view stacks its cards directly, with no .col wrapper to space
3444
+ them — so the editor carries the house 22px card gap itself. */
3445
+ .gv-editor{margin-bottom:22px;}
3446
+
3447
+ /* ---------- header bar ---------- */
3448
+ .gv-head{display:flex;align-items:center;gap:9px;padding:13px 18px;border-bottom:1px solid var(--line);}
3449
+ .gv-head .sp{flex:1 1 auto;}
3450
+ /* The pipeline name is a TITLE, not a form field: without `width:auto` it
3451
+ inherits the global `input[type="text"]{width:100%}` rule and eats the bar,
3452
+ pushing the actions into a 90px column where their labels wrap. */
3453
+ input.gv-name{width:auto;flex:0 1 340px;min-width:140px;border:1.5px solid transparent;background:var(--field);
3454
+ font:600 15px var(--sans);letter-spacing:-.01em;color:var(--ink);padding:8px 11px;border-radius:11px;
3455
+ text-overflow:ellipsis;transition:background .15s,border-color .15s;}
3456
+ input.gv-name:focus{background:#fff;border-color:var(--ink);outline:none;}
3457
+ input.gv-name:focus-visible{outline:none;}
3458
+ .gv-errchip{background:var(--red-bg);color:var(--red-ink);border:1px solid var(--red);border-radius:999px;
3459
+ padding:4px 11px;font:600 11.5px var(--sans);cursor:pointer;flex:0 0 auto;white-space:nowrap;}
3460
+ .gv-errchip:hover{filter:brightness(.97);}
3461
+ .gv-errchip[hidden]{display:none;}
3462
+ /* Toolbar density: .btn-ghost/.btn-go are PAGE-level pills (13–14px of vertical
3463
+ padding, 20–28px horizontal). In a 52px bar they wrap their own labels, so
3464
+ the bar re-states them at .btn-mini metrics — same skin, toolbar size. */
3465
+ .gv-head .btn-ghost,.gv-head .btn-go{padding:8px 15px;font-size:12.5px;white-space:nowrap;}
3466
+ .gv-head .btn-go{padding:8px 18px;}
3467
+ .gv-head .btn-go:disabled{opacity:.4;cursor:not-allowed;transform:none;filter:none;}
3468
+
3469
+ /* ---------- canvas + the floating rail (agents + inspector) ---------- */
3470
+ /* 340px / 28px are INSET_OPEN / INSET_COLLAPSED in composer.mjs — the editor
3471
+ subtracts them when it decides whether a drop landed on the canvas. Change
3472
+ one and you MUST change the other (scripts/verify-composer-cdp.mjs too). */
3473
+ #gv-canvas{height:clamp(486px,63vh,729px);}
3474
+ /* A COLUMN, not a row: the tab bar is the panel's own top row when open, and
3475
+ the bare toggle rides at the top of the grey strip when collapsed — never a
3476
+ full-height gutter beside the content. */
3477
+ .gv-ins-rail{position:absolute;top:0;right:0;bottom:0;width:340px;z-index:6;background:var(--panel);
3478
+ border-left:1px solid var(--line-2);display:flex;flex-direction:column;overflow:hidden;}
3479
+ .gv-ins-rail[data-open="collapsed"]{width:28px;background:var(--field);}
3480
+ .gv-ins-rail[data-open="collapsed"] .gv-ins-pane,
3481
+ .gv-ins-rail[data-open="collapsed"] .gv-tabs{display:none;}
3482
+ /* `data-tab` shows exactly one pane. Direct-child selectors: the panes are the
3483
+ rail's own children, and nothing nested may answer to the same switch. */
3484
+ .gv-ins-rail[data-tab="agents"] > .gv-ins-body{display:none;}
3485
+ .gv-ins-rail[data-tab="info"] > .gv-agents-pane{display:none;}
3486
+
3487
+ .gv-ins-bar{flex:0 0 auto;display:flex;align-items:center;gap:7px;padding:5px 9px 5px 0;
3488
+ border-bottom:1px solid var(--line);}
3489
+ .gv-ins-rail[data-open="collapsed"] .gv-ins-bar{padding:0;border-bottom:none;}
3490
+ .gv-ins-toggle{flex:0 0 34px;width:34px;height:34px;display:grid;place-items:center;
3491
+ border:none;border-radius:9px;background:transparent;color:var(--ink-3);cursor:pointer;
3492
+ line-height:1;font-family:inherit;transition:background .12s,color .12s;}
3493
+ .gv-ins-toggle:hover{background:var(--field);color:var(--ink);}
3494
+ /* The icon POINTS where the click sends the rail: right to collapse it into the
3495
+ edge, left to pull it back out. One element, turned by the state — never two
3496
+ glyphs that can disagree with `data-open`. */
3497
+ .gv-ins-toggle svg{display:block;flex:0 0 auto;transition:transform .15s;}
3498
+ .gv-ins-rail[data-open="collapsed"] .gv-ins-toggle svg{transform:rotate(180deg);}
3499
+ .gv-ins-rail[data-open="collapsed"] .gv-ins-toggle{flex-basis:28px;width:28px;border-radius:0;}
3500
+ .gv-ins-rail[data-open="collapsed"] .gv-ins-toggle:hover{background:var(--line);color:var(--ink);}
3501
+ .gv-ins-toggle:focus-visible{outline:2px solid var(--ink);outline-offset:-2px;}
3502
+
3503
+ /* Segmented control, not two buttons: one track, the selected half raised. */
3504
+ .gv-tabs{flex:1 1 auto;display:flex;gap:2px;padding:3px;border-radius:10px;background:var(--field);}
3505
+ .gv-tab{flex:1 1 0;padding:5px 0;border:none;border-radius:8px;background:transparent;color:var(--ink-3);
3506
+ font:600 12px var(--sans);font-family:inherit;cursor:pointer;transition:background .12s,color .12s;}
3507
+ .gv-tab:hover{color:var(--ink);}
3508
+ .gv-tab[aria-selected="true"]{background:#fff;color:var(--ink);box-shadow:var(--shadow-soft);}
3509
+ .gv-tab:focus-visible{outline:2px solid var(--ink);outline-offset:-2px;}
3510
+
3511
+ .gv-ins-pane{flex:1 1 auto;min-width:0;min-height:0;overflow:auto;}
3512
+ .gv-ins-body{padding:13px 15px 20px;}
3513
+ /* The agents pane does NOT scroll as a whole: the filter is pinned and only the
3514
+ palette below it moves, so the search field can never scroll out of reach. */
3515
+ .gv-agents-pane{display:flex;flex-direction:column;overflow:hidden;}
3516
+ .gv-pal-head{flex:0 0 auto;padding:10px 13px;border-bottom:1px solid var(--line);}
3517
+
3518
+ /* ---------- inspector panels (inspector.mjs markup) ---------- */
3519
+ .ins-panel{display:flex;flex-direction:column;}
3520
+ .ins-head{padding-bottom:11px;margin-bottom:13px;border-bottom:1px solid var(--line);}
3521
+ .ins-name{font-weight:600;font-size:14px;letter-spacing:-.01em;color:var(--ink);
3522
+ overflow:hidden;text-overflow:ellipsis;}
3523
+ .ins-sub{margin-top:4px;font:10.5px/1.4 var(--mono);color:var(--ink-3);overflow-wrap:anywhere;}
3524
+ .ins-body-in{display:flex;flex-direction:column;gap:13px;}
3525
+ .ins-f{display:flex;flex-direction:column;gap:6px;}
3526
+ .ins-label{font-weight:600;font-size:11.5px;color:var(--ink-2);}
3527
+ /* Compact restatement of the global field skin — the page metrics (13px/15px of
3528
+ padding, 14px text) are far too heavy for a 308px rail. */
3529
+ .ins-panel .ins-select,.ins-panel .ins-number{width:100%;padding:8px 11px;font-size:12.5px;border-radius:10px;
3530
+ background:var(--field);border:1.5px solid transparent;color:var(--ink);font-family:inherit;}
3531
+ .ins-panel .ins-select:focus,.ins-panel .ins-number:focus{background:#fff;border-color:var(--ink);outline:none;}
3532
+ /* `select{appearance:none}` (global) strips the native arrow and the product
3533
+ draws its own with .select-wrap::after — which the rail has no wrapper for,
3534
+ so the chevron rides in as a background image instead. */
3535
+ .ins-panel .ins-select{padding-right:30px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%235C5C63' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
3536
+ background-repeat:no-repeat;background-position:right 11px center;}
3537
+ .ins-tog{display:flex;align-items:flex-start;gap:10px;padding:9px 11px;
3538
+ border:1px solid var(--line);border-radius:12px;background:var(--field);}
3539
+ .ins-tog input[type="checkbox"]{width:15px;height:15px;flex:0 0 auto;margin:1px 0 0;accent-color:var(--ink);cursor:pointer;}
3540
+ .ins-tog input:disabled{cursor:not-allowed;}
3541
+ .ins-tog:has(input:disabled){opacity:.55;}
3542
+ .ins-panel .ins-f:has(.ins-select:disabled){opacity:.55;}
3543
+ .ins-tog-b{display:flex;flex-direction:column;gap:2px;min-width:0;}
3544
+ .ins-tog-t{font-weight:600;font-size:12.5px;color:var(--ink);}
3545
+ .ins-tog-h{font-size:11px;line-height:1.4;color:var(--ink-3);}
3546
+ .ins-blurb{margin:0;font-size:12px;line-height:1.55;color:var(--ink-2);}
3547
+ .ins-hint{display:block;font-size:11px;line-height:1.45;color:var(--ink-3);margin-top:-6px;}
3548
+ .ins-resolved{font:11px/1.4 var(--mono);color:var(--ink-2);background:var(--field);
3549
+ border-radius:9px;padding:6px 9px;}
3550
+ .ins-result{font:11px/1.5 var(--mono);color:var(--ink-3);overflow-wrap:anywhere;}
3551
+ .ins-result:empty{display:none;}
3552
+ .ins-sep{height:1px;background:var(--line);margin:3px 0 1px;}
3553
+ /* Port listing: the same dot vocabulary as the canvas cards, read-only. */
3554
+ .ins-zone{font-size:10px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
3555
+ color:var(--ink-3);margin:0 0 7px;}
3556
+ .ins-plist{display:flex;flex-direction:column;gap:4px;}
3557
+ .ins-plist + .ins-zone{margin-top:13px;}
3558
+ .ins-pitem{display:flex;align-items:center;gap:8px;padding:6px 9px;border-radius:9px;background:var(--field);}
3559
+ .ins-pitem .dot{flex:0 0 auto;width:9px;height:9px;border-radius:50%;}
3560
+ .ins-pitem .dot.md{background:var(--ink);}
3561
+ .ins-pitem .dot.json{background:var(--blue);}
3562
+ .ins-pitem .dot.any{background:#fff;border:2px solid var(--ink);}
3563
+ .ins-pitem .dot.void{background:#fff;border:2px solid var(--ink-3);}
3564
+ .ins-pitem .gdot{flex:0 0 auto;width:9px;height:9px;border-radius:50%;background:#fff;border:2px dashed var(--seq);}
3565
+ .ins-pitem .pn{font-size:12px;font-weight:500;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
3566
+ .ins-pitem .pt{font:10px/1.4 var(--mono);color:var(--ink-3);white-space:nowrap;}
3567
+ .ins-pitem .mla{margin-left:auto;}
3568
+ .ins-pitem.gate .pn{color:var(--ink-3);}
3569
+ .ins-empty .ins-blurb{color:var(--ink-3);text-align:center;padding:22px 6px;}
3570
+
3571
+ /* ---------- the rail's agents pane ---------- */
3572
+ /* Restored from the retired v1 palette: a bare <input type="search"> matches
3573
+ none of the global field selectors (they list text/number only), which is
3574
+ why it fell back to the UA skin. */
3575
+ .pal-filter{display:block;width:100%;-webkit-appearance:none;appearance:none;
3576
+ padding:7px 11px;border:1.5px solid transparent;border-radius:10px;background:var(--field);
3577
+ color:var(--ink);font-family:inherit;font-size:12.5px;font-weight:500;}
3578
+ .pal-filter::placeholder{color:var(--ink-3);}
3579
+ .pal-filter::-webkit-search-decoration,.pal-filter::-webkit-search-cancel-button{-webkit-appearance:none;}
3580
+ .pal-filter:focus{outline:none;background:#fff;border-color:var(--ink);}
3581
+ .pal-filter:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
3582
+ .gv-palette{flex:1 1 auto;min-height:0;overflow:auto;padding:13px 13px 18px;}
3583
+ .gv-pal-err{padding:22px 4px;text-align:center;font-size:13px;color:var(--ink-3);}
3584
+ .gv-retry{border:1.5px solid var(--line-2);background:#fff;color:var(--ink);font-family:inherit;
3585
+ font-weight:600;font-size:12px;padding:5px 13px;border-radius:999px;cursor:pointer;}
3586
+ .gv-retry:hover{background:var(--field);}
3587
+
3588
+ .pal-group[hidden]{display:none;}
3589
+ /* Each domain is its OWN disclosure — there is no chip row above the list, so a
3590
+ group can only be folded away through the header that names it. */
3591
+ .pal-grp{display:flex;align-items:center;gap:8px;width:100%;margin:13px 0 8px;padding:4px 6px;
3592
+ border:none;border-radius:9px;background:transparent;color:inherit;font-family:inherit;
3593
+ text-align:left;cursor:pointer;transition:background .12s;}
3594
+ .pal-group:first-of-type .pal-grp{margin-top:0;}
3595
+ .pal-grp:hover{background:var(--field);}
3596
+ .pal-grp:focus-visible{outline:2px solid var(--ink);outline-offset:-2px;}
3597
+ .pal-grp .chev{flex:0 0 auto;color:var(--ink-3);transition:transform .15s;}
3598
+ .pal-grp[aria-expanded="false"] .chev{transform:rotate(-90deg);}
3599
+ .pal-grp .lab{font:600 11px var(--sans);letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3);}
3600
+ /* The global .chip is a 7px/13px page pill; a group header needs a count badge. */
3601
+ .pal-grp .chip{background:var(--field);color:var(--ink-3);font-weight:700;font-size:10.5px;
3602
+ padding:1px 8px;border-radius:999px;}
3603
+ .pal-grp:hover .chip{background:var(--line);}
3604
+ .pal-grp .pinned-tag{background:transparent;border:1px solid var(--line-2);}
3605
+ .pal-pinned{border-top:1px solid var(--line);margin-top:16px;padding-top:4px;}
3606
+ /* One pill per row: the rail is a single column, and a two-up grid at 340px
3607
+ clips the port line every pill's second line carries. */
3608
+ .pills{display:flex;flex-direction:column;gap:7px;}
3609
+ .pills[hidden]{display:none;}
3610
+ .ap{display:flex;align-items:center;gap:9px;min-width:0;border:1.5px solid var(--line-2);border-radius:12px;
3611
+ background:#fff;padding:9px 11px;cursor:grab;text-align:left;font-family:inherit;color:var(--ink);
3612
+ box-shadow:var(--shadow-soft);transition:transform .12s,box-shadow .12s,border-color .12s;user-select:none;}
3613
+ .ap:hover:not(:disabled){transform:translateY(-1px);box-shadow:var(--shadow);}
3614
+ .ap:active:not(:disabled){cursor:grabbing;transform:none;}
3615
+ .ap:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
3616
+ .ap[hidden]{display:none;}
3617
+ .ap:disabled,.ap.dim{opacity:.45;cursor:default;box-shadow:none;}
3618
+ .ap .d{width:9px;height:9px;border-radius:50%;background:var(--seq);flex:0 0 auto;}
3619
+ .ap .d[data-color="flow"]{background:var(--ink);}
3620
+ .ap .b{min-width:0;flex:1 1 auto;}
3621
+ .ap .n{display:block;font:600 12.5px var(--sans);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
3622
+ .ap .p{display:block;margin-top:2px;font:10.5px/1.4 var(--mono);color:var(--ink-3);
3623
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
3624
+ .ap .chip{flex:0 0 auto;background:var(--field);color:var(--ink-3);font-weight:700;font-size:10px;padding:1px 7px;}
3625
+ .gv-drag-ghost{position:fixed;z-index:99;pointer-events:none;border:1.5px dashed var(--ink-3);border-radius:12px;
3626
+ background:#fff;padding:6px 10px;font:600 12px var(--sans);opacity:.9;box-shadow:var(--shadow);}
3627
+
3628
+ /* ---------- the editor's legend footer ---------- */
3629
+ /* The key describes the CANVAS, so it belongs to the editor card as a whole —
3630
+ canvas and rail — not to a palette card that no longer exists. */
3631
+ .gv-foot{display:flex;align-items:center;justify-content:flex-end;gap:10px;
3632
+ padding:9px 18px;border-top:1px solid var(--line);background:var(--panel);}
3633
+ .gv-legend{font:11px/1.5 var(--mono);color:var(--ink-3);text-align:right;}
3634
+
3635
+ /* ---------- saved pipelines ---------- */
3636
+ .saved-head{display:flex;align-items:center;justify-content:space-between;gap:12px;
3637
+ padding:16px 20px;border-bottom:1px solid var(--line);}
3638
+ .saved-head b{font-size:15px;font-weight:600;letter-spacing:-.01em;}
3639
+ .saved-head .cnt{color:var(--ink-3);font-size:12.5px;font-weight:600;margin-left:9px;}
3640
+ .saved-list{display:flex;flex-direction:column;}
3641
+ .gv-saved .pl-item{border-bottom:1px solid var(--line);}
3642
+ .gv-saved .pl-item:last-child{border-bottom:none;}
3643
+ .gv-saved .pl-row{display:flex;align-items:center;gap:14px;padding:12px 20px;transition:background .12s;}
3644
+ .gv-saved .pl-row:hover{background:var(--field);}
3645
+ /* The thumbnail is a fixed TILE at the head of the row: the SVG carries its own
3646
+ width/height, so a percentage box would leave it stranded in dead space. */
3647
+ .gv-saved .pl-thumb{flex:0 0 auto;width:140px;height:54px;display:grid;place-items:center;overflow:hidden;
3648
+ border:1px solid var(--line-2);border-radius:10px;background:#FBFBF9;}
3649
+ .gv-saved .pl-thumb svg{display:block;}
3650
+ .gv-saved .pl-thumb.is-empty{border-style:dashed;color:var(--ink-3);font:10px/1 var(--mono);
3651
+ letter-spacing:.08em;text-transform:uppercase;}
3652
+ .gv-saved .pl-main{flex:1 1 auto;min-width:0;}
3653
+ .gv-saved .pl-name{font-weight:600;font-size:13.5px;letter-spacing:-.01em;color:var(--ink);
3654
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
3655
+ .gv-saved .pl-meta{margin-top:4px;font-size:10.5px;font-weight:700;letter-spacing:.05em;
3656
+ text-transform:uppercase;color:var(--ink-3);}
3657
+ .gv-saved .pl-open{flex:0 0 auto;padding:7px 15px;font-size:12.5px;}
3658
+ .gv-saved .pl-legacy,.gv-saved .pl-origin{flex:0 0 auto;background:var(--field);border:1px solid var(--line);color:var(--ink-3);
3659
+ border-radius:999px;padding:3px 10px;font-size:11px;font-weight:600;}
3660
+ .gv-saved .pl-origin{font-family:var(--mono);letter-spacing:-.01em;}
3661
+ .gv-saved .pl-del{flex:0 0 auto;width:34px;height:34px;border-radius:10px;border:1.5px solid var(--line-2);
3662
+ background:#fff;display:grid;place-items:center;cursor:pointer;color:var(--ink-3);
3663
+ font-family:inherit;font-size:15px;line-height:1;transition:.12s;}
3664
+ .gv-saved .pl-del:hover{border-color:var(--red);color:var(--red-ink);background:var(--red-bg);}
3665
+ .gv-saved .pl-del:focus-visible{outline:2px solid var(--ink);outline-offset:2px;}
3666
+ .gv-saved .pl-empty{padding:30px 20px;text-align:center;color:var(--ink-3);font-size:13px;}
3667
+ .gv-archived{display:flex;flex-wrap:wrap;align-items:center;gap:7px;
3668
+ padding:12px 20px;border-top:1px solid var(--line);font-size:12px;color:var(--ink-2);}
3669
+ .gv-archived[hidden]{display:none;}
3670
+ .gv-archived .pl-chip{border:1px solid var(--line-2);background:var(--field);color:var(--ink-3);
3671
+ border-radius:999px;padding:2px 10px;font-family:inherit;font-size:11px;font-weight:600;cursor:pointer;transition:.12s;}
3672
+ .gv-archived .pl-chip:hover{border-color:var(--red);color:var(--red-ink);background:var(--red-bg);}
3673
+
3674
+ /* ---------- save dialog ---------- */
3675
+ .save-dialog{border:none;border-radius:18px;padding:0;box-shadow:var(--shadow);max-width:420px;width:92vw;}
3676
+ .save-dialog::backdrop{background:rgba(25,25,27,.34);}
3677
+ .save-dialog .sd-body{padding:20px 22px 18px;display:flex;flex-direction:column;gap:14px;}
3678
+ .save-dialog .sd-title{margin:0;font-size:16px;font-weight:700;letter-spacing:-.015em;}
3679
+ .save-dialog .sd-field{display:flex;flex-direction:column;gap:6px;}
3680
+ .save-dialog .sd-field label{font-weight:600;font-size:12.5px;color:var(--ink-2);}
3681
+ .save-dialog .sd-field input{width:100%;border:1.5px solid transparent;background:var(--field);
3682
+ border-radius:11px;padding:10px 12px;font:13px var(--sans);color:var(--ink);}
3683
+ .save-dialog .sd-field input:focus{outline:none;background:#fff;border-color:var(--ink);}
3684
+ .save-dialog .sd-note{margin:0;font-size:12px;line-height:1.5;color:var(--ink-2);
3685
+ background:var(--field);border-radius:11px;padding:9px 12px;}
3686
+ .save-dialog .sd-msg{margin:0;font-size:12px;line-height:1.5;color:var(--ink-3);white-space:pre-line;}
3687
+ .save-dialog .sd-msg:empty{display:none;}
3688
+ .save-dialog .sd-msg.err{color:var(--red-ink);}
3689
+ .save-dialog .sd-actions{display:flex;justify-content:flex-end;gap:9px;margin-top:2px;}
3690
+ .save-dialog .sd-actions .btn-ghost,.save-dialog .sd-actions .btn-go{padding:9px 18px;font-size:13px;}
3691
+
3692
+ @media (prefers-reduced-motion:reduce){
3693
+ .ap:hover:not(:disabled){transform:none;}
3694
+ /* The rotations stay — they are the icons' STATE, not motion; only the tween goes. */
3695
+ .gv-ins-toggle svg,.pal-grp .chev{transition:none;}
3696
+ }
3697
+
3698
+ /* ========================================================================== */
3699
+ /* Run monitor v2 — hosts + decor over the shared renderer (P6). */
3700
+ /* Appended at the END of the file — a deliberate deviation from the spec's */
3701
+ /* "after the v1 run-flow block" (A32): the v1 run-flow rules are 3–4-class */
3702
+ /* selectors that TIE P5's `.gv-world` block on specificity, so only source */
3703
+ /* order can decide; here every rule wins its tie, and the block survives */
3704
+ /* P8's deletion of the v1 block (now done). P5's `.xfoot/.xtoggle/.xrow/ */
3705
+ /* .xresult/.ngate/.nrun/.fan/.wfired` STRUCTURE rules are the base and are */
3706
+ /* NOT re-declared — only the states and ornaments P5 lacks live here. The */
3707
+ /* three neutralisers below are inert since P8 deleted the v1 rules they */
3708
+ /* cancelled; they are kept so the v2 box model is stated in ONE place. */
3709
+ /* ========================================================================== */
3710
+ /* Host: the v1 flex column becomes an absolute box the .gv-stage (inset:0)
3711
+ fills, so the stage rect === the wrap's padding box. A static host may be
3712
+ widened inline (native horizontal scroll, D5); monitor wraps never scroll. */
3713
+ .run-flow.gv-host{position:absolute;top:0;left:0;bottom:0;width:auto;min-width:100%;padding:0;margin:0;display:block;gap:0;}
3714
+ .run-flow-wrap.gv-wrap{user-select:none;-webkit-user-select:none;}
3715
+ .run-flow-wrap.gv-wrap-monitor{overflow:hidden;height:var(--run-host-h,360px);}
3716
+ .rc-detailed .run-flow-wrap.gv-wrap-static{height:300px;overflow-x:auto;overflow-y:hidden;} /* additive (Running D17) */
3717
+ .run-flow.gv-host .gv-world .node{cursor:default;}
3718
+ /* v1 descendant rules that out-rank P5's .gv-world block (3-4 classes) — one neutraliser each */
3719
+ .run-flow.gv-host .gv-world .xfoot>.fan{flex:none;flex-wrap:nowrap;margin:0;border-top:0;}
3720
+ .run-flow.gv-host .gv-world .xfoot .fl{margin-left:auto;font:700 10px/1.4 var(--mono);color:var(--ink-3);}
3721
+ .run-flow.gv-host .gv-world .nrun{margin-top:0;}
3722
+ /* two states the v1 block has no vocabulary for */
3723
+ .run-flow.gv-host .gv-world .node.is-error{border-color:color-mix(in srgb, var(--red) 50%, var(--line-2));box-shadow:0 0 0 3px color-mix(in srgb, var(--red) 13%, transparent),var(--shadow-soft);}
3724
+ .run-flow.gv-host .gv-world .node.is-skipped{opacity:.35;border-style:dashed;}
3725
+ /* settled pips (v1's .nstat, as pseudo-elements: the view creates no pip DOM) */
3726
+ .run-flow.gv-host .gv-world .node.is-done::after,.run-flow.gv-host .gv-world .node.is-paused::after,.run-flow.gv-host .gv-world .node.is-stopped::after,.run-flow.gv-host .gv-world .node.is-error::after{content:"";position:absolute;top:-8px;right:-8px;width:21px;height:21px;border-radius:50%;box-shadow:var(--shadow-soft);z-index:3;color:#fff;font:800 12px/21px var(--sans);text-align:center;}
3727
+ .run-flow.gv-host .gv-world .node.is-done::after{content:"✓";background:var(--green);}
3728
+ .run-flow.gv-host .gv-world .node.is-paused::after{content:"❚❚";font-size:8px;background:var(--amber);}
3729
+ .run-flow.gv-host .gv-world .node.is-stopped::after,.run-flow.gv-host .gv-world .node.is-error::after{content:"×";background:var(--red);}
3730
+ .run-flow.gv-host .gv-world .node:has(> .ngate)::after{content:none;} /* the gate pip owns the corner */
3731
+ .run-flow.gv-host .gv-world .ngate{cursor:pointer;animation:dotpulse 1.3s ease-in-out infinite;}
3732
+ /* footer leds: paused/stopped colours + the pulse P5 leaves to the run block.
3733
+ `xqPulse` has sqPulse's body; sqPulse itself may attach ONLY to the v1 fan
3734
+ square (test/ui-run-flow-css.test.mjs pins that), so the v2 leds get their own. */
3735
+ @keyframes xqPulse{0%,100%{opacity:1;}50%{opacity:.38;}}
3736
+ .run-flow.gv-host .gv-world .xfoot .xq.is-paused,.run-flow.gv-host .gv-world .xfoot .xrow.is-paused .led{background:var(--amber);}
3737
+ .run-flow.gv-host .gv-world .xfoot .xq.is-stopped,.run-flow.gv-host .gv-world .xfoot .xrow.is-stopped .led{background:var(--red);}
3738
+ .run-flow.gv-host .gv-world .xfoot .xq.is-active,.run-flow.gv-host .gv-world .xfoot .xrow.is-active .led{animation:xqPulse 1.5s ease-in-out infinite;}
3739
+ .run-flow.gv-host .gv-world .xrow,.run-flow.gv-host .gv-world .xresult a{cursor:pointer;}
3740
+ .run-flow.gv-host .gv-world .xrow:hover,.run-flow.gv-host .gv-world .xtoggle:hover{background:var(--field);}
3741
+ .run-flow.gv-host .gv-world .xresult a{color:var(--blue-ink);text-decoration:none;}
3742
+ .run-flow.gv-host .gv-world .xresult a:hover{text-decoration:underline;}
3743
+ /* ants on v2 wires (the v1 rule targets svg.wires; a dashoffset animation needs a dash).
3744
+ The animation lives ON THE PATH — never on :root. The old design animated a
3745
+ registered inherited <length> on :root as a shared clock, which forced a
3746
+ whole-document style recalc every frame on every view (GB-scale renderer
3747
+ memory churn; the 2026-08-31 run-detail freeze). Phase parity across wires —
3748
+ the one thing the :root clock bought — now comes from setWireLive (view.mjs)
3749
+ stamping a negative animation-delay from the shared document timeline. One
3750
+ iteration travels exactly one 6+6 dash period (-12px), so the loop boundary
3751
+ AND any restart when wire-live flips on are seamless; the retired snapping
3752
+ keyframe travelled -18px — 1.5 periods — and visibly jumped every iteration.
3753
+ -12px per .6s keeps 20px/s. */
3754
+ @keyframes wireDash{to{stroke-dashoffset:-12px;}}
3755
+ .run-flow.gv-host .gv-wires path.wire-live{stroke-dasharray:6 6;stroke-width:2.4;animation:wireDash .6s linear infinite;}
3756
+ .rd-graph.settled .run-flow.gv-host .gv-wires path.wire-live{animation:none;stroke-dashoffset:0;}
3757
+ /* the composer's ≤N budget pill is REPLACED by the N× delivery count in run mode (§8) */
3758
+ .run-flow.gv-host .wbadge{font-size:0;line-height:0;padding:0;border:0;background:none;}
3759
+ .run-flow.gv-host .wbadge:not(:has(> .wfired)){display:none;}
3760
+ .run-flow.gv-host .wbadge .wfired{display:inline-block;margin:0;padding:1px 7px;border-radius:999px;background:var(--amber-bg);color:var(--amber-ink);border:1px solid var(--amber);font:700 10px/1.4 var(--mono);}
3761
+ /* History: node click drives the Logs tab */
3762
+ .hd-graph.linked .gv-world .node[data-node-id]{cursor:pointer;}
3763
+ /* quiescence banner (.rd-banners / .hd-banners / History Overview note) */
3764
+ .run-warn{margin:0;padding:11px 15px;border-radius:12px;background:var(--amber-bg);color:var(--amber-ink);font-size:12.5px;font-weight:600;border:1px solid color-mix(in srgb, var(--amber) 45%, transparent);align-self:stretch;}
3765
+ .run-warn[hidden]{display:none;}
3766
+ /* engaged-wheel hint chip (D8) */
3767
+ .rg-hint{position:absolute;right:12px;bottom:10px;padding:4px 9px;border-radius:999px;background:color-mix(in srgb, var(--ink) 72%, transparent);color:#fff;font-size:10.5px;font-weight:600;pointer-events:none;opacity:0;transition:opacity .15s;z-index:5;}
3768
+ .run-flow-wrap.gv-wrap-monitor:hover > .rg-hint{opacity:1;}
3769
+ .run-flow-wrap.rg-engaged > .rg-hint{opacity:0;}
3770
+ @media (prefers-reduced-motion:reduce){
3771
+ .run-flow.gv-host .gv-wires path.wire-live,.run-flow.gv-host .gv-world .ngate,
3772
+ .run-flow.gv-host .gv-world .xfoot .xq.is-active,.run-flow.gv-host .gv-world .xfoot .xrow.is-active .led{animation:none;}
3773
+ /* the first selector above already stops the path's own dash animation; pin the phase */
3774
+ .run-flow.gv-host .gv-wires path.wire-live{stroke-dashoffset:0;}
3775
+ }
3776
+
3777
+ /* Legacy (v1) run strip — frozen runs only; no wires, no executions footer. */
3778
+ .run-strip{display:flex;flex-wrap:wrap;gap:8px;padding:14px 16px;}
3779
+ .run-strip .rchip{--c:var(--line-2);font-size:11.5px;font-weight:600;color:var(--ink-2);
3780
+ background:var(--field);border:1px solid var(--line);border-left:3px solid var(--c);
3781
+ border-radius:8px;padding:5px 10px;white-space:nowrap;}
3782
+ .run-strip .rchip.is-pending{opacity:.5;}
3783
+ .run-strip .rchip.is-done{color:var(--green-ink);}
3784
+ .run-strip .rchip.is-active{color:var(--c);}
3785
+ .run-strip .rchip.is-paused{color:var(--amber-ink);}
3786
+ .run-strip .rchip.is-error,.run-strip .rchip.is-stopped{color:var(--red-ink);}