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