dsh-workflow-map 0.0.0-stage → 0.3.2

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.
@@ -0,0 +1,1164 @@
1
+ /**
2
+ * theme.js — the ONE place that owns colours, sizes, timings and CSS for the panel.
3
+ *
4
+ * This file is a plain chunk, not a module: tools/build-client.mjs inlines it into
5
+ * client.js (the browser half must stay a single file — the ModuleLoader gives the
6
+ * factory a `require` that only knows the harness builtins, so a second local file
7
+ * could never be resolved at runtime).
8
+ *
9
+ * Colours are read from the harness theme tokens and every one of them carries a
10
+ * concrete fallback: an unknown token must degrade to a readable panel, never to
11
+ * transparent text on a transparent card. The token names below are the ones
12
+ * Cordis Inspect (client/Theme) reports as live in this build:
13
+ * --dsw-alias-bg-base / bg-layer-1 / bg-layer-2 / bg-overlay
14
+ * --dsw-alias-border-l1 / l2
15
+ * --dsw-alias-brand-primary
16
+ * --dsw-alias-label-primary / secondary
17
+ * --dsw-alias-state-success-primary / warn / error / idle
18
+ * (--dsw-alias-label-tertiary is NOT in that list; it is still written with a
19
+ * fallback because the contract names it, and a future build may add it.)
20
+ */
21
+
22
+ var WF_STYLE_ID = "dsh-workflow-map-css";
23
+
24
+ /** localStorage key for {width, height, collapsed, selectedStep}. */
25
+ var WF_PREFS_KEY = "dsh-workflow-map:prefs";
26
+
27
+ /** Poll cadence: 800 ms while the page is visible, 3 s while it is hidden. */
28
+ var WF_POLL_VISIBLE = 800;
29
+ var WF_POLL_HIDDEN = 3000;
30
+
31
+ /**
32
+ * Geometry . The main content area owns the size now: WIDTH/HEIGHT are no
33
+ * longer dragged or persisted, only the bounds and the historical default survive so
34
+ * a stored record from ..12 still sanitises into a valid value.
35
+ * MIN_WIDTH a sane lower bound for the centred column's content
36
+ * MAX_HEIGHT the tallest the settings card may ever become (bounded independently)
37
+ */
38
+ var WF_DEFAULT_WIDTH = 720;
39
+ var WF_DEFAULT_HEIGHT = 0;
40
+ var WF_MIN_WIDTH = 360;
41
+ var WF_MAX_WIDTH = 1200;
42
+ var WF_MIN_HEIGHT = 220;
43
+ var WF_MAX_HEIGHT = 860;
44
+ /** The panel now lives in the layout's centre column, so it carries no z-index at all
45
+ * (stacking inside the column is ordinary document order). Kept as a named constant
46
+ * so a future overlay seat can reintroduce one deliberately. */
47
+ var WF_Z_INDEX = 0;
48
+ var WF_DEFAULT_COLLAPSED = false;
49
+ /** presentation prefs. Values are an allow-list: a hand-edited localStorage
50
+ * record can only pick one of these, never invent a new visual mode. */
51
+ var WF_PALETTES = ["rainbow", "brand", "soft"];
52
+ var WF_MOTIONS = ["full", "always", "static"];
53
+ var WF_DETAIL_MODES = ["summary", "title"];
54
+ /** 0 means "all" (no window), matching the 4th choice in the settings card. */
55
+ var WF_NODE_LIMITS = [30, 60, 120, 0];
56
+
57
+ /**
58
+ * Step-card geometry , also asserted by tools/selftest-panel.mjs and
59
+ * measured in the browser by tools/measure-panel.ps1:
60
+ * STEP_GAP air between a card's bottom border and its progress rail (8 px)
61
+ * STEP_RAIL the rail track's thickness (6 px)
62
+ * STEP_LINK the connector line between two cards (14 px)
63
+ * STEP_MANY from this many cards up the column scrolls and its header sticks
64
+ */
65
+ var WF_STEP_GAP = 8;
66
+ var WF_STEP_RAIL = 6;
67
+ var WF_STEP_LINK = 14;
68
+ var WF_STEP_MANY = 8;
69
+
70
+ /**
71
+ * Flow-column limits , asserted by tools/selftest-panel.mjs:
72
+ * FLOW_CAP nodes drawn by default — the newest ones, the list opens at the bottom
73
+ * GROUP_MIN consecutive same-tool calls that fold into one row (mirrors the host's
74
+ * GROUP_THRESHOLD in src/fold.mjs; the browser only applies it when the
75
+ * host sent no groups, e.g. a state captured before withGroups existed)
76
+ */
77
+ var WF_FLOW_CAP = 60;
78
+ var WF_GROUP_MIN = 3;
79
+
80
+ /**
81
+ * Branch lanes , asserted by tools/selftest-panel.mjs:
82
+ * BRANCH_SHOWN lanes drawn per step (mirrors the host's BRANCHES_SHOWN); the rest are
83
+ * reported as "+n more" - never silently dropped
84
+ * BRANCH_LINK the coloured connector from the main track into a lane (6 px)
85
+ */
86
+ var WF_BRANCH_SHOWN = 4;
87
+ var WF_BRANCH_LINK = 6;
88
+
89
+ /**
90
+ * Chain geometry (the chain layout) - asserted by tools/selftest-effects.mjs, measured in a
91
+ * browser by tools/measure-effects.ps1.
92
+ *
93
+ * WHY THE NUMBERS LIVE HERE: the step's whole point is that the flow stops looking cramped, and
94
+ * "cramped" is four distances, not a feeling. Keeping them in the theme is what makes the browser
95
+ * measurement (a real getBoundingClientRect difference) and the stylesheet agree on ONE source.
96
+ * CHAIN_GAP node-to-node air on the spine (28 px). The chain reads as links, not a stack.
97
+ * CHAIN_LINK the bezier channel between two nodes (14 px of drawing room inside the gap).
98
+ * CHAIN_GAP must stay >= CHAIN_LINK so a connector never overflows its gap.
99
+ * CHAIN_PAD the breathing room between the spine and a card's left edge (12 px).
100
+ * CHAIN_BRANCH how far a branch dashes AWAY from the node before its round icon (34 px).
101
+ * CHAIN_ICON the round category icon's diameter (22 px — the step allows 20–24).
102
+ * CHAIN_RING the flowing halo around a running icon, as an inset overshoot (2 px).
103
+ */
104
+ var WF_CHAIN_GAP = 28;
105
+ var WF_CHAIN_LINK = 14;
106
+ var WF_CHAIN_PAD = 12;
107
+ var WF_CHAIN_BRANCH = 34;
108
+ var WF_CHAIN_ICON = 22;
109
+ var WF_CHAIN_RING = 2;
110
+ /**
111
+ * The living layer — asserted by tools/selftest-effects.mjs and measured in a
112
+ * browser by tools/measure-effects.ps1:
113
+ * FX_RAINBOW the six-stop rainbow the flowing curve and both wrapped frames share
114
+ * FX_BRAND the brand-coloured alternative (`data-curve="brand"`, may switch it)
115
+ * CURVE_MODE which one the layer starts in
116
+ * FX_STROKE the crisp stroke width of the curve
117
+ * FX_GLOW the blurred halo width (one extra path, 2.5 px blur — a glow, not a shadow)
118
+ * FX_DASH dash on/off; the keyframes slide `stroke-dashoffset`, so the colour flows
119
+ * FX_RING the conic-ish gradient of the wrapped frame (300 % wide, slid by keyframes)
120
+ * The blinking rhythm is 1.2 s and lives in two places on purpose: WF_PULSE_PERIOD (pulse.js,
121
+ * read by the self-test) and the `1.2s` literals below (this file builds the CSS before
122
+ * pulse.js has run, so it cannot read that constant). tools/selftest-effects.mjs asserts the
123
+ * two agree.
124
+ */
125
+ var WF_FX_RAINBOW = ["#ff2d78", "#ff9f1a", "#ffe14d", "#37d67a", "#18b6ff", "#8f7bea"];
126
+ var WF_FX_BRAND = ["#5b8cff", "#8f7bea", "#5b8cff"];
127
+ var WF_CURVE_MODE = "rainbow";
128
+ var WF_FX_STROKE = 2;
129
+ var WF_FX_GLOW = 6;
130
+ var WF_FX_DASH = "9 7";
131
+ var WF_FX_SPEED = "1.1s";
132
+ var WF_FX_RING = "linear-gradient(90deg," + WF_FX_RAINBOW.join(",") + "," + WF_FX_RAINBOW[0] + ")";
133
+ var WF_LIVE_PERIOD = "1.2s";
134
+
135
+ /**
136
+ * single-page chain geometry. ONE vertical run of step units; each unit hangs a few
137
+ * round satellites off its LEFT (read / query) and RIGHT (write / edit / tool) edges, joined by
138
+ * STRAIGHT lines (straight connectors were requested, not the old bezier channel).
139
+ *
140
+ * PAGE_PAD air between the scroller edge and a satellite circle
141
+ * SATELLITE round icon diameter (30 px — a top-level click target, bigger than the 22 px
142
+ * in-node category chip, because this one carries a count AND a label)
143
+ * SAT_LINK the horizontal run from a unit's edge to its circle
144
+ * UNIT_GAP air between two consecutive main-flow units (the "as much spacing as possible" requirement)
145
+ * UNIT_LINK the straight connector drawn inside that gap
146
+ * SIDE_SLOTS circles a side may carry: 2 left + 2 right per unit (user decision)
147
+ * SIDE_FILES calls one circle stands for before it stops listing them
148
+ */
149
+ var WF_PAGE_PAD = 16;
150
+ var WF_SATELLITE = 30;
151
+ var WF_SAT_LINK = 28;
152
+ var WF_UNIT_GAP = 56;
153
+ var WF_UNIT_LINK = 2;
154
+ var WF_SIDE_SLOTS = 2;
155
+ var WF_SIDE_FILES = 60;
156
+
157
+ /**
158
+ * what the right-hand page can show. `step` = one step's record, `type` = every call of
159
+ * one tool file type, `inject` = the hot-inject composer. An allow-list, because the value is
160
+ * persisted and a hand-edited record must not be able to invent a fourth page.
161
+ */
162
+ var WF_RIGHT_KINDS = ["step", "type", "inject"];
163
+
164
+ /** One CSS rule set, injected as <style id="dsh-workflow-map-css"> and removed on unmount. */
165
+ var WF_CSS = [
166
+ /* root : the panel IS the main content area. No fixed box, no corner anchor,
167
+ no click-through layer — the layout gives it the centre column and it fills it. */
168
+ ".wf-root{position:relative;width:100%;height:100%;min-height:0;display:flex;flex-direction:column;" +
169
+ // the three branch hues live on the ROOT, so the dashed branches and their round
170
+ // icons share one palette with the rest of the panel (and one place to change it).
171
+ "--wf-chain-read:#5b8cff;--wf-chain-edit:#e0a13a;--wf-chain-tool:#8f7bea;" +
172
+ "font-family:ui-sans-serif,system-ui,-apple-system,'Segoe UI','Microsoft YaHei',sans-serif;font-size:12px;line-height:1.5}",
173
+
174
+ /* card: fills the main area; the header and the settings card keep their own height,
175
+ the two-column body takes the rest. No drag grips: the column has no free edges.
176
+ Flush edges on purpose : a 12 px rounded box floating inside the centre column
177
+ would read as the old overlay again. */
178
+ ".wf-card{position:relative;flex:1 1 auto;min-height:0;display:flex;flex-direction:column;min-width:0;" +
179
+ "border-radius:0;overflow:hidden;" +
180
+ "background:var(--dsw-alias-bg-base,#161920);color:var(--dsw-alias-label-primary,#e9ecf3)}",
181
+ /* header */
182
+ ".wf-head{display:flex;align-items:flex-start;gap:8px;padding:9px 10px 8px 14px;" +
183
+ "border-bottom:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.12));" +
184
+ "background:var(--dsw-alias-bg-layer-1,#232733)}",
185
+ ".wf-head-text{flex:1;min-width:0}",
186
+ ".wf-task{font-size:12.5px;font-weight:600;line-height:1.35;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
187
+ ".wf-meta{display:flex;align-items:center;gap:6px;margin-top:3px;font-size:11px;color:var(--dsw-alias-label-secondary,#9aa3b2);flex-wrap:wrap}",
188
+ ".wf-pct{color:var(--dsw-alias-brand-primary,#5b8cff);font-variant-numeric:tabular-nums;font-weight:600}",
189
+ ".wf-sep{opacity:.5}",
190
+ ".wf-status-bad{color:var(--dsw-alias-state-warn-primary,#e0a13a)}",
191
+ ".wf-btns{display:flex;gap:3px;flex:0 0 auto}",
192
+ ".wf-btn{width:23px;height:23px;padding:0;border-radius:6px;border:1px solid transparent;background:transparent;" +
193
+ "color:var(--dsw-alias-label-secondary,#9aa3b2);font:inherit;font-size:13px;line-height:1;cursor:pointer;" +
194
+ "display:inline-flex;align-items:center;justify-content:center}",
195
+ ".wf-btn:hover{background:var(--dsw-alias-bg-layer-2,#2c3140);color:var(--dsw-alias-label-primary,#e9ecf3)}",
196
+ ".wf-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
197
+ ".wf-btn[data-active='true']{color:var(--dsw-alias-brand-primary,#5b8cff);border-color:var(--dsw-alias-brand-primary,#5b8cff)}",
198
+ // Hot injection is the one header action that injects text into the RUNNING turn, so it is the
199
+ // one that must never be hunted for: a named, filled chip that keeps its name in Chinese.
200
+ // User feedback: "too small, still have to open it first" - so it grows to 96x30 (the other two in the row stay
201
+ // 23x23 and it sets this row's height), gets a solid brand background, and carries an "N awaiting pickup" badge. The badge is driven only by
202
+ // the count of status==='pending' rows in model.injects; with no pending rows the button carries no badge at all.
203
+ ".wf-btn-hot{position:relative;width:auto;min-width:96px;height:30px;padding:0 14px;gap:5px;" +
204
+ "font-size:12.5px;font-weight:600;letter-spacing:.01em;" +
205
+ "color:var(--dsw-alias-bg-base,#161920);background:var(--dsw-alias-brand-primary,#5b8cff);" +
206
+ "border-color:transparent;box-shadow:0 0 0 1px rgba(91,140,255,.45),0 2px 10px rgba(91,140,255,.30)}",
207
+ ".wf-btn-hot:hover{filter:brightness(1.08);color:var(--dsw-alias-bg-base,#161920)}",
208
+ ".wf-btn-hot[data-active='true']{box-shadow:0 0 0 2px var(--dsw-alias-bg-base,#161920)," +
209
+ "0 0 0 3px var(--dsw-alias-brand-primary,#5b8cff)}",
210
+ // The badge is a real <span>, not a pseudo-element: the count is text a screen reader can read,
211
+ // and the button keeps `data-pending-count` so the layout self-test can measure it.
212
+ ".wf-inject-badge{position:absolute;top:-6px;right:-6px;min-width:16px;height:16px;padding:0 4px;" +
213
+ "border-radius:9px;background:var(--dsw-alias-state-error-primary,#e85a5a);color:#fff;" +
214
+ "font-size:9.5px;font-weight:700;line-height:16px;text-align:center;font-variant-numeric:tabular-nums;" +
215
+ "box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-1,#232733);pointer-events:none}",
216
+
217
+ /* settings strip (skeleton: endpoint / counts / reset) */
218
+ ".wf-settings{display:flex;flex-wrap:wrap;gap:4px 12px;align-items:center;padding:7px 12px;font-size:11px;" +
219
+ "color:var(--dsw-alias-label-secondary,#9aa3b2);border-bottom:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.1))}",
220
+ ".wf-kv{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}",
221
+ ".wf-link{padding:2px 8px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.2));" +
222
+ "background:transparent;color:inherit;font:inherit;cursor:pointer}",
223
+ ".wf-link:hover{border-color:var(--dsw-alias-brand-primary,#5b8cff);color:var(--dsw-alias-label-primary,#e9ecf3)}",
224
+
225
+ /* body: the two columns the whole step is about */
226
+ ".wf-body{display:grid;grid-template-columns:minmax(240px,44%) 1fr;gap:10px;padding:10px;overflow:hidden;min-height:0;flex:1 1 auto}",
227
+ ".wf-body[data-kind='planning']{grid-template-columns:1fr}",
228
+ ".wf-col{display:flex;flex-direction:column;gap:6px;min-width:0;min-height:0;overflow:hidden}",
229
+ ".wf-col-h{display:flex;align-items:baseline;gap:8px;min-width:0;font-size:11px;font-weight:600;letter-spacing:.02em;" +
230
+ "color:var(--dsw-alias-label-secondary,#9aa3b2)}",
231
+ ".wf-col-sub{font-weight:400;color:var(--dsw-alias-label-tertiary,#7d8798);white-space:nowrap;overflow:hidden;" +
232
+ "text-overflow:ellipsis;min-width:0}",
233
+ ".wf-count{margin-left:auto;font-weight:400;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-tertiary,#7d8798)}",
234
+ ".wf-scroll{display:flex;flex-direction:column;gap:5px;min-height:0;overflow:auto;padding-right:2px;scrollbar-width:thin}",
235
+ ".wf-col>.wf-scroll{flex:1 1 auto}",
236
+
237
+ /* step column (left): cards, the rail under each card, the connector between them */
238
+ ".wf-steps-scroll{flex:1 1 auto}",
239
+ ".wf-col-h-sticky{position:sticky;top:0;z-index:3;padding:2px 0 6px;" +
240
+ "background:var(--dsw-alias-bg-overlay,#1c1f27)}",
241
+ // the step column gets the SAME chain air the flow column got: the cards are now
242
+ // WF_CHAIN_GAP apart (they were 8 px + a 14 px connector), so the two columns read as one
243
+ // document. `gap` replaces the per-connector margin, which is why the link below no longer
244
+ // carries a `margin-top` of its own.
245
+ ".wf-steps{display:flex;flex-direction:column;gap:" + WF_CHAIN_GAP + "px;list-style:none;margin:0;" +
246
+ "padding:0;min-height:0}",
247
+ // the disclosure triangle is a real <button> in the 20 px band the card
248
+ // reserves with padding-left. One-column grid ⇒ the triangle and the card share cell 1/1,
249
+ // so the triangle spans the card's whole height; the card stays a DIRECT child of the
250
+ // <li> (the running-frame ring and FlowEffects both query `.wf-step-item > .wf-step-card`).
251
+ ".wf-step-item{display:grid;grid-template-columns:1fr;list-style:none}",
252
+ // `grid-area:1/1` is what makes the triangle and the card share the cell: without it the
253
+ // auto-placement cursor would drop the card into row 2, below the triangle.
254
+ ".wf-step-card{grid-area:1/1;display:flex;gap:8px;align-items:flex-start;width:100%;padding:7px 8px;border-radius:8px;text-align:left;" +
255
+ "font:inherit;color:inherit;cursor:pointer;border:1px solid transparent;" +
256
+ "background:var(--dsw-alias-bg-layer-2,#282d3a);border-left:3px solid transparent}",
257
+ ".wf-step-card:hover{border-color:var(--dsw-alias-border-l2,rgba(255,255,255,.22));" +
258
+ "background:var(--dsw-alias-bg-layer-1,#232733)}",
259
+ ".wf-step-card:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
260
+ ".wf-step-card[data-selected='true']{border-color:var(--dsw-alias-brand-primary,#5b8cff);" +
261
+ "background:var(--dsw-alias-bg-layer-1,#232733)}",
262
+ ".wf-step-card[data-active='true']{border-left-color:var(--dsw-alias-brand-primary,#5b8cff)}",
263
+ ".wf-step-ring{flex:0 0 auto;width:18px;height:18px;margin-top:1px;border-radius:50%;" +
264
+ "display:inline-flex;align-items:center;justify-content:center;font-size:11px;line-height:1;" +
265
+ "border:1px solid currentColor;color:var(--dsw-alias-state-idle-primary,#7d8798)}",
266
+ ".wf-step-item[data-status='done'] .wf-step-ring{color:var(--dsw-alias-state-success-primary,#45c07a)}",
267
+ ".wf-step-item[data-status='running'] .wf-step-ring{color:var(--dsw-alias-brand-primary,#5b8cff);" +
268
+ "animation:wf-ring 1.6s ease-in-out infinite}",
269
+ ".wf-step-item[data-status='failed'] .wf-step-ring{color:var(--dsw-alias-state-error-primary,#e85a5a)}",
270
+ ".wf-step-item[data-status='pending'] .wf-step-ring{color:var(--dsw-alias-state-idle-primary,#7d8798)}",
271
+ ".wf-step-item[data-status='skipped'] .wf-step-ring{color:var(--dsw-alias-label-tertiary,#7d8798);border-style:dashed}",
272
+ ".wf-step-main{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}",
273
+ ".wf-step-title{font-size:12px;line-height:1.35;color:var(--dsw-alias-label-primary,#e9ecf3);" +
274
+ "display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}",
275
+ ".wf-step-sum{font-size:10.5px;line-height:1.4;color:var(--dsw-alias-label-tertiary,#7d8798);" +
276
+ "display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}",
277
+ ".wf-step-meta{display:flex;align-items:center;flex-wrap:wrap;gap:6px;font-size:10.5px;" +
278
+ "color:var(--dsw-alias-label-tertiary,#7d8798);font-variant-numeric:tabular-nums}",
279
+ ".wf-step-seq{flex:0 0 auto;font-size:10.5px;line-height:1.6;letter-spacing:.02em;" +
280
+ "color:var(--dsw-alias-label-tertiary,#7d8798);font-variant-numeric:tabular-nums}",
281
+ ".wf-step-flag{padding:0 5px;border-radius:999px;font-size:10px;line-height:15px;" +
282
+ "color:var(--dsw-alias-state-warn-primary,#e0a13a);border:1px solid currentColor}",
283
+ // the card of a step that is waiting on a human gets a warn-toned left edge, so the
284
+ // flag is visible even when the badge row has scrolled out of the card's visible text.
285
+ ".wf-step-item[data-confirm='true']>.wf-step-card{box-shadow:inset 2px 0 0 var(--dsw-alias-state-warn-primary,#e0a13a)}",
286
+ ".wf-unit[data-confirm='true'] .wf-unit-ring{color:var(--dsw-alias-state-warn-primary,#e0a13a)}",
287
+ ".wf-step-card[data-selected='true'] .wf-step-title{font-weight:600}",
288
+ /* status badges */
289
+ ".wf-badge{padding:0 5px;border-radius:999px;font-size:10px;line-height:15px;border:1px solid currentColor}",
290
+ ".wf-step-item[data-status='done'] .wf-badge{color:var(--dsw-alias-state-success-primary,#45c07a)}",
291
+ ".wf-step-item[data-status='running'] .wf-badge{color:var(--dsw-alias-brand-primary,#5b8cff)}",
292
+ ".wf-step-item[data-status='pending'] .wf-badge{color:var(--dsw-alias-state-idle-primary,#7d8798)}",
293
+ ".wf-step-item[data-status='failed'] .wf-badge{color:var(--dsw-alias-state-error-primary,#e85a5a)}",
294
+ ".wf-step-item[data-status='skipped'] .wf-badge{color:var(--dsw-alias-label-tertiary,#7d8798)}",
295
+ ".wf-step-item[data-status='pending'] .wf-step-title{color:var(--dsw-alias-label-secondary,#9aa3b2)}",
296
+ ".wf-step-item[data-status='skipped'] .wf-step-title{color:var(--dsw-alias-label-tertiary,#7d8798);" +
297
+ "text-decoration:line-through}",
298
+
299
+ /* the rail: WF_STEP_GAP of air below the card frame, WF_STEP_RAIL tall.
300
+ the dock is a two-line block — line 1 is the 6 px track + the percent, line 2
301
+ the secondary age fact. `flex-direction:column` keeps them stacked without the
302
+ track losing a pixel of width, and the whole block stays right-aligned. */
303
+ ".wf-prog-dock{display:flex;flex-direction:column;gap:2px;margin-top:" + WF_STEP_GAP + "px}",
304
+ ".wf-prog-row{display:flex;align-items:center;gap:6px;height:" + WF_STEP_RAIL + "px}",
305
+ /* the second line. Tertiary colour, one font step below the percent, tabular
306
+ figures so a ticking age does not jitter its own width, nowrap so it never wraps
307
+ into a third line and eats the card below. */
308
+ ".wf-prog-sub{display:flex;align-items:center;justify-content:flex-end;gap:4px;min-width:0;" +
309
+ "font-size:9px;line-height:1.2;color:var(--dsw-alias-label-tertiary,#7d8798);" +
310
+ "font-variant-numeric:tabular-nums;white-space:nowrap}",
311
+ ".wf-prog-ago{flex:0 0 auto}",
312
+ // (2026-10-26, measured by tools/measure-panel.ps1): in the single-page layout the dock
313
+ // is a child of the `gap:8px` centre column, so its own margin added a SECOND 8 px and the
314
+ // documented "8 px of air under the card" measured 16 px. Scoped away for that placement only.
315
+ ".wf-unit-center>.wf-prog-dock{margin-top:0}",
316
+ /* Narrow container (`detail = title`): line 2 is dropped, not squeezed. */
317
+ ".wf-step-item[data-detail='title'] .wf-prog-dock{height:" + WF_STEP_RAIL + "px;gap:0}",
318
+ ".wf-step-item[data-detail='title'] .wf-prog-sub{display:none}",
319
+ /* THE ONE ANIMATED RAIL and THE ONE ANIMATED FILL.
320
+ The user's rule: exactly ONE step's rail may animate, and it must be a real PROGRESS bar
321
+ (a measured fill + a number), never a plain scrolling streamer. Two consequences
322
+ are encoded here:
323
+ 1. only `.wf-unit[data-running='true']` — the single row the host marks as running — gets
324
+ the flowing gradient; every other running-looking row stays a flat fill;
325
+ 2. a running row with no number yet gets a determinate-looking 30 % fill with the SAME
326
+ gradient and NO slide animation, so it reads "working, unknown how far" instead of
327
+ "a separate streamer is spinning". `data-indeterminate` is still emitted for tests. */
328
+ ".wf-prog-track{position:relative;flex:1 1 auto;height:" + WF_STEP_RAIL + "px;border-radius:3px;overflow:hidden;" +
329
+ "background:var(--dsw-alias-bg-base,rgba(0,0,0,.3));border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.1))}",
330
+ ".wf-prog-fill{position:absolute;left:0;top:0;bottom:0;width:0;border-radius:3px;" +
331
+ "background:var(--dsw-alias-label-secondary,#9aa3b2);transition:width .35s ease}",
332
+ ".wf-prog-dock[data-status='done'] .wf-prog-fill{background:var(--dsw-alias-state-success-primary,#45c07a)}",
333
+ ".wf-prog-dock[data-status='failed'] .wf-prog-fill{background:var(--dsw-alias-state-error-primary,#e85a5a)}",
334
+ ".wf-prog-fill[data-live='true']{background:linear-gradient(90deg,var(--dsw-alias-brand-primary,#5b8cff),#8f7bea,var(--dsw-alias-brand-primary,#5b8cff));" +
335
+ "background-size:200% 100%;animation:wf-prog-flow 1.5s linear infinite}",
336
+ ".wf-prog-dock[data-status='pending'] .wf-prog-fill,.wf-prog-dock[data-status='skipped'] .wf-prog-fill{" +
337
+ "width:0!important;background:transparent;animation:none}",
338
+ ".wf-prog-fill[data-indeterminate='true']{width:30%!important;animation:wf-prog-flow 1.5s linear infinite}",
339
+ ".wf-prog-dock[data-status='skipped'] .wf-prog-track{border-style:dashed}",
340
+ ".wf-prog-label{flex:0 0 auto;font-size:10px;line-height:1;color:var(--dsw-alias-label-tertiary,#7d8798);" +
341
+ "font-variant-numeric:tabular-nums;white-space:nowrap}",
342
+ ".wf-prog-label[data-kind='estimate']{font-style:italic}",
343
+ ".wf-prog-label[data-kind='self']{color:var(--dsw-alias-label-secondary,#9aa3b2)}",
344
+
345
+ /* connector between two cards: WF_STEP_LINK tall, solid once the step above is done */
346
+ /* connector between two cards: WF_STEP_LINK tall, solid once the step above is done.
347
+ the line moves 3 px right with the card's new 20 px triangle band, so it still
348
+ hangs off the ring's centre (the band is 20 px, the ring starts 20 + 8 px padding).
349
+ the column now carries the chain's `gap`, so the connector must NOT add a margin —
350
+ that would make the space between two step cards (gap + margin + link) larger than the hole
351
+ its own line is drawn in. WF_STEP_LINK stays the drawn height. */
352
+ ".wf-step-link{display:block;position:relative;height:" + WF_STEP_LINK + "px}",
353
+ ".wf-step-link::before{content:'';position:absolute;left:29px;top:0;bottom:0;width:2px;border-radius:1px;" +
354
+ "background:var(--dsw-alias-border-l2,rgba(255,255,255,.22))}",
355
+ ".wf-step-link[data-done='true']::before{background:var(--dsw-alias-state-success-primary,#45c07a)}",
356
+ ".wf-step-link[data-done='false']::before{background:repeating-linear-gradient(180deg," +
357
+ "var(--dsw-alias-border-l2,rgba(255,255,255,.22)) 0 3px,transparent 3px 6px)}",
358
+ "@keyframes wf-prog-flow{from{background-position:0 0}to{background-position:-200% 0}}",
359
+ "@keyframes wf-prog-slide{0%{transform:translateX(-110%)}100%{transform:translateX(360%)}}",
360
+ "@keyframes wf-ring{0%,100%{opacity:1}50%{opacity:.45}}",
361
+ "@media (prefers-reduced-motion: reduce){.wf-prog-fill,.wf-step-ring{animation:none}}",
362
+
363
+ /* flow column (right): one spine, node cards hang off it, groups fold runs */
364
+ ".wf-flow{flex:1 1 auto;min-height:0}",
365
+ ".wf-flow-h{display:flex;align-items:baseline;gap:8px;flex:0 0 auto;padding:0 0 6px;" +
366
+ "border-bottom:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.12))}",
367
+ ".wf-flow-h .wf-col-sub{flex:1 1 auto;min-width:0}",
368
+ ".wf-flow-h .wf-count{flex:0 0 auto;margin-left:auto;white-space:nowrap}",
369
+ ".wf-flow-scroll{flex:1 1 auto;padding-top:4px}",
370
+ /* Chain layout: the list stops being a 5 px stack and becomes a CHAIN - WF_CHAIN_GAP of
371
+ air between neighbours (a real distance, so a link can be drawn in it), the spine inset by
372
+ WF_CHAIN_PAD, and every card one link further right. `gap` alone would be enough for the
373
+ spacing; the padding and the gap come from the SAME constants the browser measurement reads.
374
+ The spine is still a pseudo-element, but it is now only the fallback for the no-JS case: the
375
+ measured bezier channel (FlowEffects.js) paints over it and the `data-chain` attribute on the
376
+ list says which one is live. */
377
+ ".wf-flow-list{position:relative;display:flex;flex-direction:column;gap:" + WF_CHAIN_GAP + "px;" +
378
+ "list-style:none;margin:0;padding:0 0 0 " + (WF_CHAIN_PAD + 2) + "px}",
379
+ ".wf-flow-list[data-chain='curve']::before{opacity:.45}",
380
+ ".wf-flow-list::before{content:'';position:absolute;left:4px;top:6px;bottom:6px;width:2px;border-radius:1px;" +
381
+ "background:var(--dsw-alias-border-l2,rgba(255,255,255,.22))}",
382
+ ".wf-flow-list[data-empty='true']::before{display:none}",
383
+ ".wf-flow-list[data-truncated='true']::before{background:repeating-linear-gradient(180deg," +
384
+ "var(--dsw-alias-border-l2,rgba(255,255,255,.22)) 0 3px,transparent 3px 6px)}",
385
+ ".wf-flow-item{position:relative;min-width:0}",
386
+ ".wf-flow-item::before{content:'';position:absolute;left:-13px;top:12px;width:6px;height:6px;border-radius:50%;" +
387
+ "background:var(--dsw-alias-border-l2,rgba(255,255,255,.22));box-shadow:0 0 0 2px var(--dsw-alias-bg-overlay,#1c1f27)}",
388
+ ".wf-flow-item[data-status='running']::before{background:var(--dsw-alias-brand-primary,#5b8cff);" +
389
+ "animation:wf-pulse 1.6s ease-in-out infinite}",
390
+ ".wf-flow-item[data-status='error']::before{background:var(--dsw-alias-state-error-primary,#e85a5a)}",
391
+
392
+ /* the branch strip under a RUNNING node: 2–4 flowing dashes, each ending in a round
393
+ category icon (read = blue / edit = orange / tool = violet, the three hues below are the
394
+ same ones wfBranchHue palette covers). Only the running node renders this block,
395
+ so a done or a pending node produces no branch DOM at all. */
396
+ ".wf-branching{position:relative;display:flex;flex-direction:column;gap:5px;margin:" +
397
+ WF_CHAIN_LINK / 2 + "px 0 0 " + WF_CHAIN_PAD + "px}",
398
+ ".wf-branch-row{position:relative;display:flex;align-items:center;gap:7px;min-width:0;padding-left:" +
399
+ WF_CHAIN_BRANCH + "px;min-height:" + WF_CHAIN_ICON + "px}",
400
+ // the flowing dash itself: one absolutely positioned SVG per row, drawn by FlowEffects.js as a
401
+ // cubic bezier from the spine out to the icon. Conic-gradient would colour it flat, so the
402
+ // stroke carries a gradient id and the dash slides with `stroke-dashoffset` ('s ruse).
403
+ ".wf-branch-stroke{position:absolute;left:0;top:0;width:" + WF_CHAIN_BRANCH + "px;height:100%;" +
404
+ "pointer-events:none;overflow:visible}",
405
+ ".wf-branch-core{fill:none;stroke-width:1.8;stroke-linecap:round;stroke-dasharray:4 4;" +
406
+ "animation:wf-fx-dash 1.1s linear infinite}",
407
+ ".wf-branch-row[data-kind='read'] .wf-branch-core{stroke:var(--wf-chain-read,#5b8cff)}",
408
+ ".wf-branch-row[data-kind='edit'] .wf-branch-core{stroke:var(--wf-chain-edit,#e0a13a)}",
409
+ ".wf-branch-row[data-kind='tool'] .wf-branch-core{stroke:var(--wf-chain-tool,#8f7bea)}",
410
+ // the round icon: a real <button> (keyboard-operable, aria-expanded) that IS the circle, so the
411
+ // 22 px diameter below is the measured diameter and not an inner badge.
412
+ ".wf-branch-icon{flex:0 0 auto;width:" + WF_CHAIN_ICON + "px;height:" + WF_CHAIN_ICON + "px;padding:0;" +
413
+ "border-radius:50%;display:inline-flex;align-items:center;justify-content:center;" +
414
+ "font:inherit;font-size:11px;line-height:1;cursor:pointer;position:relative;" +
415
+ "background:var(--dsw-alias-bg-layer-2,#282d3a);color:var(--dsw-alias-label-secondary,#9aa3b2);" +
416
+ "border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.28))}",
417
+ ".wf-branch-icon:hover{color:var(--dsw-alias-label-primary,#e9ecf3)}",
418
+ ".wf-branch-icon:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:2px}",
419
+ ".wf-branch-row[data-kind='read'] .wf-branch-icon{color:var(--wf-chain-read,#5b8cff);border-color:currentColor}",
420
+ ".wf-branch-row[data-kind='edit'] .wf-branch-icon{color:var(--wf-chain-edit,#e0a13a);border-color:currentColor}",
421
+ ".wf-branch-row[data-kind='tool'] .wf-branch-icon{color:var(--wf-chain-tool,#8f7bea);border-color:currentColor}",
422
+ // "the running circle is wrapped in flowing colour" - the same mask-composite ring wraps the two cards in,
423
+ // sized to the icon: 2 px of paint outside the circle, slid by background-position.
424
+ ".wf-branch-row[data-live='true'] .wf-branch-icon::after{content:'';position:absolute;inset:-" +
425
+ WF_CHAIN_RING + "px;border-radius:50%;padding:" + WF_CHAIN_RING + "px;background:" + WF_FX_RING + ";" +
426
+ "background-size:300% 100%;animation:wf-fx-wrap 2.6s linear infinite;pointer-events:none;" +
427
+ "-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;" +
428
+ "mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude}",
429
+ ".wf-branch-label{flex:1 1 auto;min-width:0;font-size:10.5px;line-height:1.35;" +
430
+ "color:var(--dsw-alias-label-tertiary,#7d8798);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
431
+ ".wf-branch-open .wf-branch-label{color:var(--dsw-alias-label-secondary,#9aa3b2)}",
432
+ // the opened category detail: the SAME .wf-node-detail block renders, pulled back to
433
+ // the icon's column so it reads as belonging to the circle that opened it.
434
+ ".wf-branch-body{display:flex;flex-direction:column;gap:4px;margin-left:" + WF_CHAIN_PAD + "px}",
435
+ "@media (prefers-reduced-motion: reduce){.wf-branch-core{animation:none}}",
436
+
437
+ /* the "earlier nodes folded" strip above the list */
438
+ ".wf-flow-more{display:flex;align-items:center;gap:8px;padding:5px 8px;margin:6px 0 0;border-radius:8px;" +
439
+ "font-size:10.5px;line-height:1.4;color:var(--dsw-alias-label-tertiary,#7d8798);" +
440
+ "border:1px dashed var(--dsw-alias-border-l2,rgba(255,255,255,.2))}",
441
+ ".wf-flow-more .wf-link{margin-left:auto;flex:0 0 auto}",
442
+ /* node card: icon · title · meta · status dot */
443
+ ".wf-node{display:flex;gap:7px;align-items:flex-start;width:100%;padding:6px 8px;border-radius:8px;" +
444
+ "text-align:left;font:inherit;color:inherit;cursor:pointer;" +
445
+ "background:var(--dsw-alias-bg-layer-2,#282d3a);border:1px solid transparent;" +
446
+ "border-left:3px solid var(--dsw-alias-border-l2,rgba(255,255,255,.2))}",
447
+ ".wf-node:hover{border-color:var(--dsw-alias-border-l2,rgba(255,255,255,.24));background:var(--dsw-alias-bg-layer-1,#232733)}",
448
+ ".wf-node:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
449
+ ".wf-node[data-selected='true']{border-color:var(--dsw-alias-brand-primary,#5b8cff);" +
450
+ "background:var(--dsw-alias-bg-layer-1,#232733)}",
451
+ ".wf-node[data-selected='true'] .wf-node-title{font-weight:600}",
452
+ ".wf-node[data-kind='tool']{border-left-color:var(--dsw-alias-brand-primary,#5b8cff)}",
453
+ ".wf-node[data-kind='report']{border-left-color:var(--dsw-alias-state-success-primary,#45c07a)}",
454
+ ".wf-node[data-kind='question'],.wf-node[data-kind='approval']{border-left-color:var(--dsw-alias-state-warn-primary,#e0a13a)}",
455
+ ".wf-node[data-kind='subagent']{border-left-color:var(--dsw-alias-state-idle-primary,#8f7bea)}",
456
+ ".wf-node[data-status='error']{border-left-color:var(--dsw-alias-state-error-primary,#e85a5a)}",
457
+ ".wf-node-icon{flex:0 0 16px;height:16px;margin-top:1px;border-radius:5px;display:inline-flex;align-items:center;" +
458
+ "justify-content:center;font-size:10px;line-height:1;" +
459
+ "background:var(--dsw-alias-bg-base,rgba(0,0,0,.22));color:var(--dsw-alias-label-secondary,#9aa3b2)}",
460
+ ".wf-node[data-category='file'] .wf-node-icon{color:var(--dsw-alias-brand-primary,#5b8cff)}",
461
+ ".wf-node[data-category='command'] .wf-node-icon{color:var(--dsw-alias-state-warn-primary,#e0a13a)}",
462
+ ".wf-node[data-category='skill'] .wf-node-icon,.wf-node[data-category='report'] .wf-node-icon{" +
463
+ "color:var(--dsw-alias-state-success-primary,#45c07a)}",
464
+ ".wf-node[data-category='subagent'] .wf-node-icon{color:var(--dsw-alias-state-idle-primary,#8f7bea)}",
465
+ ".wf-node[data-category='question'] .wf-node-icon,.wf-node[data-category='approval'] .wf-node-icon{" +
466
+ "color:var(--dsw-alias-state-warn-primary,#e0a13a)}",
467
+ ".wf-kind{flex:0 0 auto;font-size:10px;line-height:16px;padding:0 5px;border-radius:5px;" +
468
+ "background:var(--dsw-alias-bg-base,rgba(0,0,0,.22));color:var(--dsw-alias-label-secondary,#9aa3b2)}",
469
+ ".wf-node-main{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}",
470
+ ".wf-node-title{font-size:11.5px;line-height:1.35;color:var(--dsw-alias-label-primary,#e9ecf3);" +
471
+ "display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}",
472
+ ".wf-node[data-compact='true'] .wf-node-title,.wf-step-item[data-detail='title'] .wf-step-sum{-webkit-line-clamp:1}",
473
+ ".wf-node-verb{font-weight:600;color:var(--dsw-alias-label-secondary,#9aa3b2)}",
474
+ ".wf-node-target{color:var(--dsw-alias-label-primary,#e9ecf3)}",
475
+ ".wf-node-meta{display:flex;flex-wrap:wrap;gap:6px;font-size:10.5px;color:var(--dsw-alias-label-tertiary,#7d8798);" +
476
+ "font-variant-numeric:tabular-nums}",
477
+ ".wf-node[data-error='true'] .wf-node-meta{color:var(--dsw-alias-state-error-primary,#e85a5a)}",
478
+ ".wf-flow-status{flex:0 0 auto;width:7px;height:7px;margin-top:5px;border-radius:50%;" +
479
+ "background:var(--dsw-alias-state-idle-primary,#7d8798)}",
480
+ ".wf-flow-status[data-status='done']{background:var(--dsw-alias-state-success-primary,#45c07a)}",
481
+ ".wf-flow-status[data-status='running']{background:var(--dsw-alias-brand-primary,#5b8cff);" +
482
+ "animation:wf-pulse 1.6s ease-in-out infinite}",
483
+ ".wf-flow-status[data-status='error']{background:var(--dsw-alias-state-error-primary,#e85a5a)}",
484
+
485
+ /* a folded run: one dashed row, expanded into its calls on click */
486
+ ".wf-group{display:flex;flex-direction:column;gap:5px;min-width:0}",
487
+ ".wf-group-head{grid-area:1/1;display:flex;gap:7px;align-items:center;width:100%;padding:6px 8px 6px 20px;border-radius:8px;" +
488
+ "text-align:left;font:inherit;color:inherit;cursor:pointer;" +
489
+ "background:var(--dsw-alias-bg-base,rgba(0,0,0,.18));" +
490
+ "border:1px dashed var(--dsw-alias-border-l2,rgba(255,255,255,.2))}",
491
+ ".wf-group-head:hover{border-color:var(--dsw-alias-brand-primary,#5b8cff)}",
492
+ ".wf-group-head:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
493
+ ".wf-group[data-expanded='true'] .wf-group-head{border-style:solid;" +
494
+ "border-color:var(--dsw-alias-border-l2,rgba(255,255,255,.28))}",
495
+ ".wf-group-main{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}",
496
+ ".wf-group-label{font-size:11.5px;line-height:1.35;color:var(--dsw-alias-label-primary,#e9ecf3);" +
497
+ "white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
498
+ ".wf-group-meta{font-size:10.5px;color:var(--dsw-alias-label-tertiary,#7d8798);font-variant-numeric:tabular-nums}",
499
+ // the caret is a real <button> now (wfTri in StepColumn.js), so the row is a
500
+ // one-column grid: triangle and row share cell 1/1 and the triangle spans the row's
501
+ // height in the 20 px band the row reserves with padding-left.
502
+ ".wf-group-row{display:grid;grid-template-columns:1fr}",
503
+ ".wf-group-body{display:flex;flex-direction:column;gap:5px;list-style:none;margin:0 0 0 6px;padding:0 0 0 8px;" +
504
+ "border-left:1px dashed var(--dsw-alias-border-l2,rgba(255,255,255,.2))}",
505
+ ".wf-group-body .wf-flow-item::before{display:none}",
506
+ "@media (prefers-reduced-motion: reduce){.wf-flow-status,.wf-flow-item::before{animation:none}}",
507
+
508
+ /* branch lanes : parallel subagent / workflow / background job */
509
+ ".wf-branches{position:relative;display:flex;flex-direction:column;gap:6px;margin-top:10px;padding:0 0 2px 14px}",
510
+ ".wf-branches-h{display:flex;align-items:baseline;gap:8px;font-size:10.5px;font-weight:600;letter-spacing:.02em;" +
511
+ "color:var(--dsw-alias-label-tertiary,#7d8798)}",
512
+ ".wf-branches-h .wf-count{margin-left:auto}",
513
+ ".wf-branch{position:relative;min-width:0}",
514
+ ".wf-branch-link{position:absolute;left:-12px;top:14px;width:12px;height:" + WF_BRANCH_LINK + "px;border-radius:3px;" +
515
+ "background:var(--wf-branch-line,#5b8cff)}",
516
+ ".wf-branch-card{border-radius:8px;border:1px solid var(--wf-branch-dim,rgba(91,140,255,.5));" +
517
+ "background:var(--dsw-alias-bg-layer-2,#282d3a);overflow:hidden}",
518
+ ".wf-branch-head{display:flex;align-items:center;gap:6px;width:100%;padding:5px 8px;text-align:left;" +
519
+ "font:inherit;color:inherit;cursor:pointer;background:transparent;border:0}",
520
+ ".wf-branch-head:focus-visible{outline:2px solid var(--wf-branch-line,#5b8cff);outline-offset:1px}",
521
+ ".wf-branch-badge{flex:0 0 auto;padding:0 5px;border-radius:999px;font-size:10px;line-height:15px;" +
522
+ "color:var(--wf-branch-text,#8f7bea);border:1px solid currentColor}",
523
+ ".wf-branch-label{flex:1 1 auto;min-width:0;font-size:11.5px;line-height:1.35;" +
524
+ "color:var(--dsw-alias-label-primary,#e9ecf3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
525
+ ".wf-branch-meta{flex:0 0 auto;font-size:10px;color:var(--dsw-alias-label-tertiary,#7d8798);" +
526
+ "font-variant-numeric:tabular-nums}",
527
+ ".wf-branch-dot{flex:0 0 auto;width:7px;height:7px;border-radius:50%;" +
528
+ "background:var(--dsw-alias-state-idle-primary,#7d8798)}",
529
+ ".wf-branch-dot[data-status='running']{background:var(--wf-branch-line,#5b8cff);" +
530
+ "animation:wf-pulse 1.6s ease-in-out infinite}",
531
+ ".wf-branch-dot[data-status='done']{background:var(--dsw-alias-state-success-primary,#45c07a)}",
532
+ ".wf-branch-dot[data-status='error']{background:var(--dsw-alias-state-error-primary,#e85a5a)}",
533
+ ".wf-branch-caret{flex:0 0 auto;font-size:9px;line-height:1;color:var(--dsw-alias-label-tertiary,#7d8798);" +
534
+ "transition:transform .15s ease}",
535
+ ".wf-branch[data-expanded='true'] .wf-branch-caret{transform:rotate(90deg)}",
536
+ ".wf-branch-body{display:flex;flex-direction:column;gap:3px;list-style:none;margin:0;padding:0 6px 6px 8px}",
537
+ ".wf-branch-row{min-width:0}",
538
+ ".wf-branch-node{display:flex;align-items:center;gap:6px;width:100%;padding:3px 6px;border-radius:6px;" +
539
+ "text-align:left;font:inherit;color:inherit;cursor:pointer;" +
540
+ "background:var(--dsw-alias-bg-base,rgba(0,0,0,.18));border:1px solid transparent}",
541
+ ".wf-branch-node:hover{border-color:var(--wf-branch-dim,rgba(91,140,255,.5))}",
542
+ ".wf-branch-node:focus-visible{outline:2px solid var(--wf-branch-line,#5b8cff);outline-offset:1px}",
543
+ ".wf-branch-node[data-selected='true']{border-color:var(--wf-branch-line,#5b8cff)}",
544
+ ".wf-branch-glyph{flex:0 0 12px;font-size:10px;line-height:1;color:var(--wf-branch-text,#8f7bea);text-align:center}",
545
+ ".wf-branch-row-main{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1}",
546
+ ".wf-branch-row-title{font-size:11px;line-height:1.3;color:var(--dsw-alias-label-primary,#e9ecf3);" +
547
+ "white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
548
+ ".wf-branch-row-detail{font-size:10px;line-height:1.35;color:var(--dsw-alias-label-tertiary,#7d8798);" +
549
+ "white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
550
+ ".wf-branch-clock{flex:0 0 auto;font-size:10px;color:var(--dsw-alias-label-tertiary,#7d8798);" +
551
+ "font-variant-numeric:tabular-nums}",
552
+ ".wf-branch-phase{padding:1px 6px;font-size:10px;font-weight:600;letter-spacing:.02em;" +
553
+ "color:var(--wf-branch-text,#8f7bea)}",
554
+ ".wf-branch-trunc{padding:1px 6px;font-size:10px;font-style:italic;color:var(--dsw-alias-label-tertiary,#7d8798)}",
555
+ ".wf-branch-more{font-size:10px;color:var(--dsw-alias-label-tertiary,#7d8798);padding-left:2px}",
556
+ "@media (prefers-reduced-motion: reduce){.wf-branch-dot{animation:none}}",
557
+
558
+ ".wf-empty{padding:10px;border-radius:8px;text-align:center;font-size:11px;" +
559
+ "color:var(--dsw-alias-label-tertiary,#7d8798);border:1px dashed var(--dsw-alias-border-l2,rgba(255,255,255,.2))}",
560
+
561
+ /* ===== node detail + step detail ===== */
562
+
563
+ /* The 「i」 sits inside the 26 px band the card reserves on its right, so it never
564
+ covers the title or the seq counter. Sibling of the card: valid HTML, Tab-reachable. */
565
+ // the card keeps a 20 px band on its LEFT for the disclosure triangle and the
566
+ // 26 px band on its right for the 「i」; the <li> is a grid (see .wf-step-item above), so
567
+ // both siblings land in cell 1/1 without absolute positioning games on the card side.
568
+ ".wf-step-item{position:relative}",
569
+ ".wf-step-card{padding-left:20px;padding-right:26px}",
570
+ // The ONE disclosure triangle: a full-height band, not an 8 px glyph. `grid-area:1/1` puts
571
+ // it in the card's cell and `align-self:stretch` makes it exactly as tall as the card, so
572
+ // the hit target is 20 px × the card height (measured in the browser by measure-tri.ps1).
573
+ // 10 px glyph on a 20 px band: big enough to read as a control, small enough not to compete
574
+ // with the status ring next to it. The colour lifts on hover/focus and on the open state.
575
+ ".wf-tri{grid-area:1/1;align-self:stretch;justify-self:start;width:20px;min-height:100%;" +
576
+ "padding:8px 0 0;margin:0;border:0;border-radius:6px 0 0 6px;background:transparent;" +
577
+ "color:var(--dsw-alias-label-secondary,#9aa3b2);" +
578
+ "font:inherit;font-size:11px;line-height:1;cursor:pointer;z-index:5;" +
579
+ "display:flex;align-items:flex-start;justify-content:center}",
580
+ ".wf-tri:hover{color:var(--dsw-alias-label-primary,#e9ecf3);background:var(--dsw-alias-bg-layer-1,#232733)}",
581
+ ".wf-tri:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:-2px;border-radius:6px}",
582
+ ".wf-tri[data-expanded='true']{color:var(--dsw-alias-brand-primary,#5b8cff)}",
583
+ ".wf-step-info{position:absolute;right:6px;top:6px;width:15px;height:15px;padding:0;border-radius:50%;" +
584
+ "border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.22));background:var(--dsw-alias-bg-base,rgba(0,0,0,.25));" +
585
+ "color:var(--dsw-alias-label-tertiary,#7d8798);font:inherit;font-size:9px;line-height:1;cursor:pointer;z-index:4;" +
586
+ "display:inline-flex;align-items:center;justify-content:center}",
587
+ ".wf-step-info:hover{color:var(--dsw-alias-label-primary,#e9ecf3);border-color:var(--dsw-alias-brand-primary,#5b8cff)}",
588
+ ".wf-step-info:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
589
+ ".wf-step-info[data-open='true']{color:var(--dsw-alias-brand-primary,#5b8cff);border-color:var(--dsw-alias-brand-primary,#5b8cff)}",
590
+ ".wf-col-h-sticky{flex-wrap:wrap}",
591
+ ".wf-col-h-sticky .wf-step-head{flex:1 1 100%;margin-top:1px;font-weight:400;font-size:10px;" +
592
+ "white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
593
+
594
+ ".wf-node-detail{margin-top:5px}",
595
+ ".wf-step-detail{margin-top:6px}",
596
+ ".wf-node-detail,.wf-step-detail{position:relative;display:flex;flex-direction:column;gap:5px;min-width:0;" +
597
+ "padding:7px 8px;border-radius:8px;font-size:10.5px;line-height:1.45;" +
598
+ "background:var(--dsw-alias-bg-layer-1,#232733);border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.12))}",
599
+ ".wf-step-detail[data-status='done']{border-left:3px solid var(--dsw-alias-state-success-primary,#45c07a)}",
600
+ ".wf-step-detail[data-status='failed']{border-left:3px solid var(--dsw-alias-state-error-primary,#e85a5a)}",
601
+ ".wf-step-detail[data-status='running']{border-left:3px solid var(--dsw-alias-brand-primary,#5b8cff)}",
602
+ ".wf-group-body .wf-node-detail{margin-left:0}",
603
+
604
+ ".wf-detail-head,.wf-step-detail-head{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}",
605
+ ".wf-detail-icon{flex:0 0 auto;width:15px;height:15px;border-radius:5px;display:inline-flex;align-items:center;" +
606
+ "justify-content:center;font-size:9px;line-height:1;background:var(--dsw-alias-bg-base,rgba(0,0,0,.25));" +
607
+ "color:var(--dsw-alias-label-secondary,#9aa3b2)}",
608
+ ".wf-node-detail[data-category='file'] .wf-detail-icon{color:var(--dsw-alias-brand-primary,#5b8cff)}",
609
+ ".wf-node-detail[data-category='command'] .wf-detail-icon{color:var(--dsw-alias-state-warn-primary,#e0a13a)}",
610
+ ".wf-node-detail[data-category='skill'] .wf-detail-icon{color:var(--dsw-alias-state-success-primary,#45c07a)}",
611
+ ".wf-node-detail[data-category='subagent'] .wf-detail-icon,.wf-node-detail[data-category='cad'] .wf-detail-icon{" +
612
+ "color:var(--dsw-alias-state-idle-primary,#8f7bea)}",
613
+ ".wf-detail-name{font-size:11.5px;font-weight:600;color:var(--dsw-alias-label-primary,#e9ecf3);word-break:break-all;min-width:0}",
614
+ ".wf-step-detail-title{flex:1 1 auto;min-width:0;font-size:11.5px;font-weight:600;" +
615
+ "color:var(--dsw-alias-label-primary,#e9ecf3)}",
616
+ ".wf-detail-chip,.wf-step-detail-state{flex:0 0 auto;padding:0 5px;border-radius:999px;font-size:9.5px;" +
617
+ "line-height:15px;border:1px solid currentColor;color:var(--dsw-alias-label-secondary,#9aa3b2)}",
618
+ ".wf-detail-chip[data-category='cad'],.wf-detail-chip[data-category='subagent']{color:var(--dsw-alias-state-idle-primary,#8f7bea)}",
619
+ ".wf-detail-chip[data-category='command']{color:var(--dsw-alias-state-warn-primary,#e0a13a)}",
620
+ ".wf-detail-chip[data-category='file']{color:var(--dsw-alias-brand-primary,#5b8cff)}",
621
+ ".wf-detail-state[data-state='error'],.wf-step-detail-state[data-status='failed']{color:var(--dsw-alias-state-error-primary,#e85a5a)}",
622
+ ".wf-detail-state[data-state='done'],.wf-step-detail-state[data-status='done']{color:var(--dsw-alias-state-success-primary,#45c07a)}",
623
+ ".wf-detail-state[data-state='pending'],.wf-step-detail-state[data-status='pending']{color:var(--dsw-alias-label-tertiary,#7d8798)}",
624
+ ".wf-step-detail-state[data-status='running']{color:var(--dsw-alias-brand-primary,#5b8cff)}",
625
+
626
+ ".wf-detail-block{display:flex;flex-direction:column;gap:3px;min-width:0}",
627
+ ".wf-detail-block-head{display:flex;align-items:center;gap:6px;min-height:16px;min-width:0}",
628
+ ".wf-detail-label{font-size:10px;font-weight:600;letter-spacing:.02em;" +
629
+ "color:var(--dsw-alias-label-tertiary,#7d8798)}",
630
+ ".wf-detail-copy{margin-left:auto;flex:0 0 auto;padding:0 6px;border-radius:6px;" +
631
+ "border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.2));background:transparent;color:inherit;" +
632
+ "font:inherit;font-size:10px;line-height:15px;cursor:pointer}",
633
+ ".wf-detail-copy:hover{border-color:var(--dsw-alias-brand-primary,#5b8cff)}",
634
+ // The acknowledgement lives in its own span: a file row keeps its basename + directory,
635
+ // and the standalone copy button hides its label while the status shows.
636
+ ".wf-copy-status{flex:0 0 auto;margin-left:auto;color:inherit;white-space:nowrap}",
637
+ ".wf-copy-status:empty{display:none}",
638
+ ".wf-detail-copy[data-copy-state='copied'] .wf-copy-label,.wf-detail-copy[data-copy-state='failed'] .wf-copy-label{display:none}",
639
+ ".wf-detail-copy:focus-visible,.wf-detail-file:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
640
+ ".wf-detail-copy[data-copy-state='copied'],.wf-detail-file[data-copy-state='copied']{" +
641
+ "color:var(--dsw-alias-state-success-primary,#45c07a);border-color:currentColor}",
642
+ ".wf-detail-copy[data-copy-state='failed'],.wf-detail-file[data-copy-state='failed']{" +
643
+ "color:var(--dsw-alias-state-error-primary,#e85a5a);border-color:currentColor}",
644
+ ".wf-detail-preview{margin:0;font-family:ui-monospace,SFMono-Regular,Consolas,'Microsoft YaHei',monospace;" +
645
+ "font-size:10px;line-height:1.4;white-space:pre-wrap;word-break:break-all;" +
646
+ "color:var(--dsw-alias-label-secondary,#9aa3b2);max-height:30px;overflow:hidden}",
647
+ ".wf-detail-more{margin-top:1px}",
648
+ // NO ::before glyph here. The triangle belongs to the step card / folded run
649
+ // (a real <button> with aria-expanded); a <details> summary only styles its own marker.
650
+ ".wf-detail-summary{cursor:pointer;font-size:10px;line-height:1.5;list-style:none;" +
651
+ "color:var(--dsw-alias-brand-primary,#5b8cff)}",
652
+ ".wf-detail-summary::-webkit-details-marker{display:none}",
653
+ ".wf-detail-summary:hover{text-decoration:underline}",
654
+ ".wf-detail-args{margin:3px 0 0;padding:4px 6px;border-radius:5px;max-height:180px;overflow:auto;" +
655
+ "background:var(--dsw-alias-bg-base,rgba(0,0,0,.25));font-family:ui-monospace,SFMono-Regular,Consolas,'Microsoft YaHei',monospace;" +
656
+ "font-size:10px;line-height:1.4;white-space:pre-wrap;word-break:break-all}",
657
+ ".wf-detail-note{font-size:9.5px;font-style:italic;color:var(--dsw-alias-label-tertiary,#7d8798)}",
658
+ ".wf-detail-empty,.wf-detail-result{font-size:10px;color:var(--dsw-alias-label-tertiary,#7d8798)}",
659
+ ".wf-detail-result{white-space:pre-wrap;word-break:break-word}",
660
+ ".wf-detail-groups,.wf-detail-group{display:flex;flex-direction:column;gap:3px;min-width:0}",
661
+ ".wf-detail-sublabel{display:flex;align-items:center;gap:4px;font-size:9.5px;" +
662
+ "color:var(--dsw-alias-label-tertiary,#7d8798)}",
663
+ ".wf-detail-glyph{font-size:10px;line-height:1}",
664
+ ".wf-detail-rows{display:flex;flex-direction:column;gap:1px;min-width:0}",
665
+ ".wf-detail-file{display:flex;align-items:baseline;gap:6px;width:100%;min-width:0;padding:1px 4px;" +
666
+ "border-radius:5px;border:1px solid transparent;background:transparent;color:inherit;font:inherit;" +
667
+ "font-size:10px;line-height:1.5;text-align:left;cursor:pointer}",
668
+ ".wf-detail-file:hover{background:var(--dsw-alias-bg-layer-2,#2c3140)}",
669
+ ".wf-detail-base{flex:0 0 auto;color:var(--dsw-alias-label-primary,#e9ecf3);" +
670
+ "font-family:ui-monospace,SFMono-Regular,Consolas,'Microsoft YaHei',monospace}",
671
+ ".wf-detail-dir{flex:1 1 auto;min-width:0;font-size:9.5px;color:var(--dsw-alias-label-tertiary,#7d8798);" +
672
+ "white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
673
+ ".wf-detail-error{margin:0;max-height:200px;overflow:auto;white-space:pre-wrap;word-break:break-word;" +
674
+ "font-family:ui-monospace,SFMono-Regular,Consolas,'Microsoft YaHei',monospace;font-size:10px;" +
675
+ "line-height:1.45;color:var(--dsw-alias-state-error-primary,#e85a5a)}",
676
+ ".wf-detail-errors{display:flex;flex-direction:column;gap:2px;min-width:0}",
677
+ ".wf-detail-meta{display:flex;flex-wrap:wrap;gap:3px 10px;font-size:9.5px;" +
678
+ "color:var(--dsw-alias-label-tertiary,#7d8798);font-variant-numeric:tabular-nums}",
679
+ ".wf-step-detail-summary-text{font-size:10.5px;line-height:1.5;" +
680
+ "color:var(--dsw-alias-label-secondary,#9aa3b2);white-space:pre-wrap;word-break:break-word}",
681
+ ".wf-step-detail-stats{display:flex;flex-wrap:wrap;gap:3px 10px;font-size:10px;" +
682
+ "color:var(--dsw-alias-label-secondary,#9aa3b2);font-variant-numeric:tabular-nums}",
683
+ ".wf-detail-close{margin-left:auto;flex:0 0 auto;width:17px;height:17px;padding:0;border-radius:5px;" +
684
+ "border:1px solid transparent;background:transparent;color:var(--dsw-alias-label-tertiary,#7d8798);" +
685
+ "font:inherit;font-size:12px;line-height:1;cursor:pointer}",
686
+ ".wf-detail-close:hover{color:var(--dsw-alias-label-primary,#e9ecf3);background:var(--dsw-alias-bg-layer-2,#2c3140)}",
687
+ ".wf-detail-close:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
688
+
689
+ /* collapsed capsule : the main area keeps a one-line pill at its top;
690
+ clicking it expands the two-column panel back. pointer-events:auto is now the
691
+ default — the root is a real column, not a click-through overlay. */
692
+ ".wf-capsule-bar{display:flex;align-items:center;gap:8px;padding:10px 12px}",
693
+ /* collapsed capsule */
694
+ ".wf-capsule{display:inline-flex;align-items:center;gap:7px;pointer-events:auto;padding:5px 11px;border-radius:999px;" +
695
+ "font:inherit;font-size:12px;cursor:pointer;color:var(--dsw-alias-label-primary,#e9ecf3);" +
696
+ "background:var(--dsw-alias-bg-overlay,#1c1f27);border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,.24));" +
697
+ "box-shadow:0 8px 24px rgba(0,0,0,.35);font-variant-numeric:tabular-nums}",
698
+ ".wf-capsule:hover{border-color:var(--dsw-alias-brand-primary,#5b8cff)}",
699
+ ".wf-capsule:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:2px}",
700
+ ".wf-dot{width:7px;height:7px;border-radius:50%;flex:0 0 auto;background:var(--dsw-alias-brand-primary,#5b8cff);" +
701
+ "animation:wf-pulse 1.6s ease-in-out infinite}",
702
+ "@keyframes wf-pulse{0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(91,140,255,.55)}50%{opacity:.55;box-shadow:0 0 0 5px rgba(91,140,255,0)}}",
703
+ "@media (prefers-reduced-motion: reduce){.wf-dot{animation:none}}",
704
+
705
+ /* ===== the living layer — flowing curve, wrapped frames, breathing dot ===== */
706
+
707
+ /* The body is the curve layer's containing block: an absolutely positioned SVG inside the
708
+ two-column grid needs position:relative here, and 3 px of extra gutter so neither wrapped
709
+ frame's ring is shaved by a scroller's overflow:auto edge. */
710
+ ".wf-body{position:relative}",
711
+ ".wf-flow-scroll,.wf-steps-scroll{padding-right:4px}",
712
+ // The wrapped frame's ring is 3 px outside the card, so the first card's ring would be shaved
713
+ // by the step scroller's own overflow:auto edge. 4 px of list padding buys it back (the browser
714
+ // screenshot showed the left edge of the ring clipped before this).
715
+ ".wf-steps{padding-left:4px;padding-top:4px}",
716
+
717
+ /* the layer itself: click-through, painted above both columns, hidden until measured */
718
+ // Hidden means opacity:0, never display:none — a display:none box measures 0×0, and the
719
+ // first measurement needs the layer's real rect to turn the card coordinates into user
720
+ // space. An invisible SVG paints nothing, so there is no cost either way.
721
+ ".wf-fx{position:absolute;inset:0;width:100%;height:100%;opacity:0;overflow:visible;pointer-events:none;z-index:4}",
722
+ ".wf-fx[data-visible='true']{opacity:1}",
723
+ ".wf-fx-core{fill:none;stroke:url(#wf-fx-rainbow);stroke-width:" + WF_FX_STROKE + ";stroke-linecap:round;" +
724
+ "stroke-linejoin:round;stroke-dasharray:" + WF_FX_DASH + ";animation:wf-fx-dash " + WF_FX_SPEED + " linear infinite}",
725
+ ".wf-fx-glow{fill:none;stroke:url(#wf-fx-rainbow);stroke-width:" + WF_FX_GLOW + ";stroke-linecap:round;" +
726
+ "stroke-linejoin:round;stroke-dasharray:" + WF_FX_DASH + ";opacity:.45;filter:blur(2.5px);" +
727
+ "animation:wf-fx-dash " + WF_FX_SPEED + " linear infinite}",
728
+ ".wf-fx[data-curve='brand'] .wf-fx-core,.wf-fx[data-curve='brand'] .wf-fx-glow{stroke:url(#wf-fx-brand)}",
729
+ ".wf-fx-cap{fill:url(#wf-fx-rainbow);filter:drop-shadow(0 0 3px rgba(143,123,234,.85))}",
730
+ ".wf-fx[data-curve='brand'] .wf-fx-cap{fill:url(#wf-fx-brand)}",
731
+ ".wf-fx-stop-1{stop-color:" + WF_FX_RAINBOW[0] + "}",
732
+ ".wf-fx-stop-2{stop-color:" + WF_FX_RAINBOW[1] + "}",
733
+ ".wf-fx-stop-3{stop-color:" + WF_FX_RAINBOW[2] + "}",
734
+ ".wf-fx-stop-4{stop-color:" + WF_FX_RAINBOW[3] + "}",
735
+ ".wf-fx-stop-5{stop-color:" + WF_FX_RAINBOW[4] + "}",
736
+ ".wf-fx-stop-6{stop-color:" + WF_FX_RAINBOW[5] + "}",
737
+ ".wf-fx-stop-b1{stop-color:var(--dsw-alias-brand-primary,#5b8cff)}",
738
+ ".wf-fx-stop-b2{stop-color:#8f7bea}",
739
+ ".wf-fx-stop-b3{stop-color:var(--dsw-alias-brand-primary,#5b8cff)}",
740
+ // One pattern length is 9+7 = 16 px, so a -32 offset is a seamless loop.
741
+ "@keyframes wf-fx-dash{from{stroke-dashoffset:0}to{stroke-dashoffset:-32}}",
742
+
743
+ /* the chain's curve channel. One SVG per flow list, absolutely positioned INSIDE the
744
+ list, drawn between the cards' right and left edges so the two endpoints sit on the edges
745
+ themselves (the 2 px acceptance tolerance is about rounding, not correction). It is
746
+ pointer-events:none — the step's own discipline and the only way the cards stay clickable
747
+ through it — and it is `opacity:0` rather than `display:none` before it is measured, because
748
+ a display:none box measures 0×0 and could never be positioned. */
749
+ ".wf-chain{position:absolute;inset:0;width:100%;height:100%;opacity:0;overflow:visible;" +
750
+ "pointer-events:none;z-index:1}",
751
+ ".wf-chain[data-visible='true']{opacity:1}",
752
+ ".wf-chain-core{fill:none;stroke:var(--dsw-alias-border-l2,rgba(255,255,255,.22));stroke-width:1.6;" +
753
+ "stroke-linecap:round;stroke-linejoin:round}",
754
+ ".wf-chain-core[data-done='true']{stroke:var(--dsw-alias-state-success-primary,#45c07a);opacity:.75}",
755
+ /* Wrapped frame: the running step card and the running node card. The pseudo-element is
756
+ 3 px larger than the card and paints only its outer 2 px (mask), which leaves the 1 px
757
+ transparent seam between ring and card edge the step asks for. No @property: the
758
+ gradient is 300 % wide and slid by background-position, which every Chrome does. */
759
+ ".wf-step-item[data-status='running']>.wf-step-card,.wf-node[data-status='running']{position:relative;" +
760
+ "box-shadow:0 0 0 1px rgba(143,123,234,.3),0 0 14px rgba(91,140,255,.26)}",
761
+ ".wf-step-item[data-status='running']>.wf-step-card::after,.wf-node[data-status='running']::after{content:'';" +
762
+ "position:absolute;inset:-3px;border-radius:11px;padding:2px;background:" + WF_FX_RING + ";" +
763
+ "background-size:300% 100%;animation:wf-fx-wrap 2.6s linear infinite;pointer-events:none;z-index:2;" +
764
+ "-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;" +
765
+ "mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude}",
766
+ "@keyframes wf-fx-wrap{from{background-position:0 0}to{background-position:300% 0}}",
767
+
768
+ /* "working ...": a 6 px breathing dot with a spreading halo plus three dots lighting in turn.
769
+ The period is 1.2 s (WF_LIVE_PERIOD / WF_PULSE_PERIOD) — same rhythm as the DSH
770
+ thinking indicator, deliberately faster than the 1.6 s status dots around it. */
771
+ ".wf-live{display:inline-flex;align-items:center;gap:5px;margin-top:1px;font-size:10.5px;line-height:1.3;" +
772
+ "color:var(--dsw-alias-brand-primary,#5b8cff)}",
773
+ ".wf-live-dot{flex:0 0 auto;width:6px;height:6px;border-radius:50%;background:currentColor;" +
774
+ "animation:wf-live-dot " + WF_LIVE_PERIOD + " ease-in-out infinite}",
775
+ ".wf-live-word{letter-spacing:.02em}",
776
+ ".wf-live-dots{display:inline-flex;align-items:center;gap:2px}",
777
+ ".wf-live-dots>i{width:2px;height:2px;border-radius:50%;background:currentColor;opacity:.25;" +
778
+ "animation:wf-live-dots " + WF_LIVE_PERIOD + " ease-in-out infinite}",
779
+ ".wf-live-dots>i:nth-child(2){animation-delay:.16s}",
780
+ ".wf-live-dots>i:nth-child(3){animation-delay:.32s}",
781
+ "@keyframes wf-live-dot{0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(91,140,255,.55)}50%{opacity:.35;box-shadow:0 0 0 5px rgba(91,140,255,0)}}",
782
+ "@keyframes wf-live-dots{0%,100%{opacity:.2}60%{opacity:1}}",
783
+
784
+ /* Idle freezes every animation under the panel (paused keeps the current frame, so the
785
+ still image does not jump); reduced motion removes them outright and keeps the static
786
+ rainbow ring/curve. Both are attributed on the root, so the capsule is covered too. */
787
+ // The pseudo-elements are listed explicitly: `animation`/`animation-play-state` are NOT
788
+ // inherited, so a rule on `*` never reaches the ring on .wf-step-card::after or the lane dot on
789
+ // .wf-flow-item::before. (The browser measurement caught exactly this.)
790
+ ".wf-root[data-idle='1'] *,.wf-root[data-idle='1'] *::before,.wf-root[data-idle='1'] *::after{animation-play-state:paused!important}",
791
+ ".wf-root[data-motion='static'] *,.wf-root[data-motion='static'] *::before,.wf-root[data-motion='static'] *::after{animation:none!important}",
792
+ "@media (prefers-reduced-motion: reduce){.wf-root *,.wf-root *::before,.wf-root *::after{animation:none!important}}",
793
+ /* ===== settings card + palette switch + snapshot export ===== */
794
+
795
+ // One settings card inside the panel (the gear is the only entry). The grid keeps every
796
+ // label in a fixed column so the four segmented controls read as one block.
797
+ ".wf-settings{display:flex;flex-direction:column;gap:7px;padding:8px 12px 9px;font-size:11px;flex:0 0 auto;" +
798
+ "max-height:min(55vh,320px);overflow:auto;scrollbar-width:thin;" +
799
+ "color:var(--dsw-alias-label-secondary,#9aa3b2);border-bottom:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.1));" +
800
+ "background:var(--dsw-alias-bg-layer-1,#232733)}",
801
+ ".wf-set-grid{display:grid;grid-template-columns:auto minmax(0,1fr);gap:5px 9px;align-items:center}",
802
+ ".wf-set-label{white-space:nowrap;font-size:10px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-tertiary,#7d8798)}",
803
+ ".wf-seg{display:flex;flex-wrap:wrap;gap:3px;min-width:0}",
804
+ ".wf-seg-btn{padding:2px 7px;border-radius:6px;border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.14));" +
805
+ "background:transparent;color:var(--dsw-alias-label-secondary,#9aa3b2);font:inherit;font-size:10px;line-height:1.5;cursor:pointer}",
806
+ ".wf-seg-btn:hover{border-color:var(--dsw-alias-border-l2,rgba(255,255,255,.24));color:var(--dsw-alias-label-primary,#e9ecf3)}",
807
+ ".wf-seg-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
808
+ ".wf-seg-btn[aria-pressed='true']{color:var(--dsw-alias-brand-primary,#5b8cff);border-color:currentColor;" +
809
+ "background:color-mix(in srgb,var(--dsw-alias-brand-primary,#5b8cff) 13%,transparent)}",
810
+ ".wf-set-actions{display:flex;align-items:center;flex-wrap:wrap;gap:5px 8px;padding-top:1px}",
811
+ ".wf-set-actions .wf-link{font-size:10px}",
812
+ ".wf-export{display:flex;align-items:center;flex-wrap:wrap;gap:4px 6px;padding-top:5px;border-top:1px dashed var(--dsw-alias-border-l1,rgba(255,255,255,.12))}",
813
+ ".wf-export-label{font-size:10px;font-weight:600;color:var(--dsw-alias-label-tertiary,#7d8798)}",
814
+ ".wf-export .wf-link,.wf-export .wf-seg-btn{font-size:10px}",
815
+ ".wf-export .wf-copy-status{margin-left:0;color:var(--dsw-alias-state-success-primary,#45c07a)}",
816
+ ".wf-export .wf-link[data-copy-state='failed'],.wf-export .wf-seg-btn[data-copy-state='failed']{color:var(--dsw-alias-state-error-primary,#e85a5a)}",
817
+ ".wf-settings .wf-kv{font-size:10px}",
818
+
819
+ // Palette "brand": every living accent follows the harness brand token instead of the
820
+ // six-stop rainbow. Palette "soft": low-saturation, dimmed colours for dark rooms.
821
+ ".wf-root[data-palette='brand'] .wf-fx-core,.wf-root[data-palette='brand'] .wf-fx-glow{stroke:url(#wf-fx-brand)}",
822
+ ".wf-root[data-palette='brand'] .wf-fx-cap{fill:url(#wf-fx-brand)}",
823
+ ".wf-root[data-palette='brand'] .wf-step-item[data-status='running']>.wf-step-card::after," +
824
+ ".wf-root[data-palette='brand'] .wf-node[data-status='running']::after," +
825
+ ".wf-root[data-palette='brand'] .wf-unit[data-status='running']>.wf-unit-center>.wf-unit-card::after{background:linear-gradient(90deg," +
826
+ "var(--dsw-alias-brand-primary,#5b8cff),#8f7bea,var(--dsw-alias-brand-primary,#5b8cff));background-size:300% 100%}",
827
+ ".wf-root[data-palette='brand'] .wf-prog-dock[data-status='running'] .wf-prog-fill{background:linear-gradient(90deg," +
828
+ "var(--dsw-alias-brand-primary,#5b8cff),#8f7bea,var(--dsw-alias-brand-primary,#5b8cff));background-size:200% 100%}",
829
+
830
+ ".wf-root[data-palette='soft']{--wf-soft-1:#7f93b8;--wf-soft-2:#9a8fc4;--wf-soft-3:#6f829f}",
831
+ ".wf-root[data-palette='soft'] .wf-fx-stop-1{stop-color:var(--wf-soft-1)}",
832
+ ".wf-root[data-palette='soft'] .wf-fx-stop-2{stop-color:var(--wf-soft-2)}",
833
+ ".wf-root[data-palette='soft'] .wf-fx-stop-3{stop-color:var(--wf-soft-3)}",
834
+ ".wf-root[data-palette='soft'] .wf-fx-stop-4{stop-color:var(--wf-soft-1)}",
835
+ ".wf-root[data-palette='soft'] .wf-fx-stop-5{stop-color:var(--wf-soft-2)}",
836
+ ".wf-root[data-palette='soft'] .wf-fx-stop-6{stop-color:var(--wf-soft-3)}",
837
+ ".wf-root[data-palette='soft'] .wf-fx-core,.wf-root[data-palette='soft'] .wf-fx-glow{stroke:url(#wf-fx-rainbow)}",
838
+ ".wf-root[data-palette='soft'] .wf-fx-cap{fill:var(--wf-soft-2)}",
839
+ ".wf-root[data-palette='soft'] .wf-pct,.wf-root[data-palette='soft'] .wf-live{color:var(--wf-soft-1)}",
840
+ ".wf-root[data-palette='soft'] .wf-dot{background:var(--wf-soft-2)}",
841
+ ".wf-root[data-palette='soft'] .wf-step-item[data-status='running'] .wf-step-ring{color:var(--wf-soft-1)}",
842
+ ".wf-root[data-palette='soft'] .wf-flow-item[data-status='running']::before," +
843
+ ".wf-root[data-palette='soft'] .wf-flow-status[data-status='running']{background:var(--wf-soft-1)}",
844
+ ".wf-root[data-palette='soft'] .wf-step-item[data-status='running']>.wf-step-card::after," +
845
+ ".wf-root[data-palette='soft'] .wf-node[data-status='running']::after{background:linear-gradient(90deg," +
846
+ "var(--wf-soft-1),var(--wf-soft-2),var(--wf-soft-1));background-size:300% 100%}",
847
+ ".wf-root[data-palette='soft'] .wf-prog-dock[data-status='running'] .wf-prog-fill{background:linear-gradient(90deg," +
848
+ "var(--wf-soft-1),var(--wf-soft-2),var(--wf-soft-1));background-size:200% 100%}",
849
+ // The live single-page structure: the unit card frame, its status ring and the satellite glow are
850
+ // the elements that actually render now, so soft must cover them too — otherwise they fall back to
851
+ // the rainbow WF_FX_RING / the brand blue. The StepColumn + FlowColumn selectors above stay as they
852
+ // are: selftest-panel, selftest-settings and the preview-* fixtures still build those nodes.
853
+ ".wf-root[data-palette='soft'] .wf-unit[data-status='running']>.wf-unit-center>.wf-unit-card::after{background:linear-gradient(90deg," +
854
+ "var(--wf-soft-1),var(--wf-soft-2),var(--wf-soft-1));background-size:300% 100%}",
855
+ ".wf-root[data-palette='soft'] .wf-unit[data-status='running'] .wf-unit-ring{color:var(--wf-soft-1)}",
856
+ ".wf-root[data-palette='soft'] .wf-sat[data-live='true']::after," +
857
+ ".wf-root[data-palette='soft'] .wf-sat-row[data-live='true'] .wf-sat-line{background:linear-gradient(90deg," +
858
+ "var(--wf-soft-1),var(--wf-soft-2),var(--wf-soft-1));background-size:300% 100%}",
859
+ ].join("\n");
860
+
861
+ /* ===== the split body and the inject column ===== */
862
+
863
+ // Two-column mode replaces the body's grid with a flex row: the flow keeps its own layout and
864
+ // simply GROWS (flex:1 1 auto), and the inject column is a FIXED 320 px beside it. A grid with
865
+ // `minmax(240px,44%) 1fr` would have to be redefined to add a third track, which is exactly the
866
+ // kind of second source of truth this plugin avoids — so the split is one extra class on the
867
+ // body, and the two children own their own sizes.
868
+ //
869
+ // The important property: `min-width:0` on the flow side. Without it a flex item refuses to
870
+ // shrink below its content and the whole body would overflow instead of the column scrolling.
871
+ var WF_CSS_INJECT = [
872
+ // the split is GONE: hot injection is the right page's third kind, mounted inside .wf-right-scroll
873
+ // (a flex COLUMN), so the composer must fill that column instead of keeping its old fixed basis.
874
+ ".wf-card{container-type:inline-size}",
875
+ ".wf-right .wf-inject{flex:1 1 auto;min-height:0;border:0;border-radius:0;padding:0;background:transparent}",
876
+ ".wf-inject-head{display:flex;align-items:center;gap:8px;min-width:0}",
877
+ ".wf-inject-title{flex:1;min-width:0;font-size:11px;font-weight:600;letter-spacing:.02em;" +
878
+ "color:var(--dsw-alias-label-secondary,#9aa3b2)}",
879
+ ".wf-inject-close{font-size:10px}",
880
+ ".wf-inject-hint{margin:0;font-size:10px;line-height:1.45;color:var(--dsw-alias-label-tertiary,#7d8798)}",
881
+ ".wf-inject-area{flex:1 1 auto;min-height:88px;resize:vertical;padding:6px 7px;border-radius:7px;" +
882
+ "border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.14));background:var(--dsw-alias-bg-base,#161920);" +
883
+ "color:var(--dsw-alias-label-primary,#e9ecf3);font:inherit;font-size:11px;line-height:1.5}",
884
+ ".wf-inject-area:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
885
+ ".wf-inject-foot{display:flex;align-items:center;gap:8px;min-width:0}",
886
+ ".wf-inject-count{flex:1;min-width:0;font-size:10px;font-variant-numeric:tabular-nums;" +
887
+ "color:var(--dsw-alias-label-tertiary,#7d8798)}",
888
+ ".wf-inject-send{font-size:10.5px}",
889
+ // The enabled button is the only one that reads as an action; a disabled one must still look
890
+ // like a button, just not like an invitation.
891
+ ".wf-inject-send[data-ready='true']{color:var(--dsw-alias-brand-primary,#5b8cff);" +
892
+ "border-color:var(--dsw-alias-brand-primary,#5b8cff)}",
893
+ ".wf-inject-send:disabled{opacity:.45;cursor:default}",
894
+ // the history rows. Three columns (status / text / time) that stay readable in the
895
+ // right page's 460 px: the status chip is fixed, the text takes the rest and wraps, the time
896
+ // is tabular so a column of rows does not jitter.
897
+ ".wf-inject-history{display:flex;flex-direction:column;gap:4px;margin-top:10px;padding-top:8px;" +
898
+ "border-top:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.14));min-height:0}",
899
+ ".wf-inject-history-label{font-size:10px;font-weight:600;letter-spacing:.02em;" +
900
+ "color:var(--dsw-alias-label-secondary,#9aa3b2)}",
901
+ ".wf-inject-history-empty{font-size:10px;color:var(--dsw-alias-label-tertiary,#7d8798)}",
902
+ ".wf-inject-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px;" +
903
+ "overflow:auto;min-height:0;scrollbar-width:thin}",
904
+ ".wf-inject-row{display:grid;grid-template-columns:auto 1fr auto auto;align-items:baseline;gap:6px;" +
905
+ "font-size:10px;line-height:1.45;padding:4px 6px;border-radius:6px;" +
906
+ "background:var(--dsw-alias-bg-base,#161920)}",
907
+ ".wf-inject-row-status{white-space:nowrap;font-weight:600}",
908
+ ".wf-inject-row-status[data-status='picked']{color:var(--dsw-alias-state-success-primary,#45c07a)}",
909
+ ".wf-inject-row-status[data-status='pending']{color:var(--dsw-alias-state-warn-primary,#e8b84a)}",
910
+ ".wf-inject-row-text{min-width:0;overflow-wrap:anywhere;color:var(--dsw-alias-label-primary,#e9ecf3)}",
911
+ ".wf-inject-row-time{white-space:nowrap;font-variant-numeric:tabular-nums;" +
912
+ "color:var(--dsw-alias-label-tertiary,#7d8798)}",
913
+ // the row's own "Revoke" button and its failure line. The button is a `wf-link`
914
+ // like every other affordance in this panel, so it stays quiet next to the status word; the
915
+ // failure line spans the whole row (grid-column 1/-1) and prints the host's reason verbatim.
916
+ ".wf-inject-row-revoke{white-space:nowrap;font-size:10px;color:var(--dsw-alias-state-warn-primary,#e8b84a)}",
917
+ ".wf-inject-row-revoke:disabled{opacity:.45;cursor:default}",
918
+ ".wf-inject-row-error{grid-column:1/-1;font-size:10px;line-height:1.4;overflow-wrap:anywhere;" +
919
+ "color:var(--dsw-alias-state-error-primary,#e85a5a)}",
920
+ ".wf-inject-note[data-kind='sent']{color:var(--dsw-alias-state-success-primary,#45c07a)}",
921
+ ".wf-inject-note[data-kind='failed']{color:var(--dsw-alias-state-error-primary,#e85a5a)}",
922
+ // the PERSISTENT dock: the right page's footer for every kind that
923
+ // is NOT the composer itself, so a prompt can be typed without opening the page first. One row
924
+ // (label + single-line input + send + the NEWEST injection's status); a failed send prints the
925
+ // host's own reason under that row, inside the same bar and verbatim — never optimistically.
926
+ ".wf-inject-dock{flex:0 0 auto;display:flex;flex-direction:column;gap:4px;padding:7px 10px;min-width:0;" +
927
+ "border-top:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.12));background:var(--dsw-alias-bg-layer-2,#2c3140)}",
928
+ ".wf-inject-dock-row{display:flex;align-items:center;gap:7px;min-width:0}",
929
+ ".wf-inject-dock-label{flex:0 0 auto;padding:0;border:0;background:transparent;cursor:pointer;" +
930
+ "font:inherit;font-size:10.5px;font-weight:600;color:var(--dsw-alias-label-secondary,#9aa3b2)}",
931
+ ".wf-inject-dock-label:hover{color:var(--dsw-alias-label-primary,#e9ecf3)}",
932
+ ".wf-inject-dock-label:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
933
+ ".wf-inject-dock-input{flex:1 1 auto;min-width:0;padding:5px 7px;border-radius:7px;" +
934
+ "border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.14));background:var(--dsw-alias-bg-base,#161920);" +
935
+ "color:var(--dsw-alias-label-primary,#e9ecf3);font:inherit;font-size:11px;line-height:1.4}",
936
+ ".wf-inject-dock-input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
937
+ ".wf-inject-dock-send{flex:0 0 auto;font-size:10.5px}",
938
+ ".wf-inject-dock-send[data-ready='true']{color:var(--dsw-alias-brand-primary,#5b8cff);" +
939
+ "border-color:var(--dsw-alias-brand-primary,#5b8cff)}",
940
+ ".wf-inject-dock-send:disabled{opacity:.45;cursor:default}",
941
+ ".wf-inject-dock-state{flex:0 0 auto;white-space:nowrap;font-size:10px;font-weight:600;" +
942
+ "color:var(--dsw-alias-label-tertiary,#7d8798)}",
943
+ ".wf-inject-dock-state[data-status='picked']{color:var(--dsw-alias-state-success-primary,#45c07a)}",
944
+ ".wf-inject-dock-state[data-status='pending']{color:var(--dsw-alias-state-warn-primary,#e8b84a)}",
945
+ ".wf-inject-dock-note{font-size:10px;line-height:1.4;overflow-wrap:anywhere;" +
946
+ "color:var(--dsw-alias-state-error-primary,#e85a5a)}",
947
+ ".wf-inject-dock-note[data-kind='sent']{color:var(--dsw-alias-state-success-primary,#45c07a)}",
948
+ // Narrow: the panel is its own container, so the dock stays the stacked layout's footer and is
949
+ // allowed to wrap rather than squeeze the input down to nothing. Declared HERE (not in
950
+ // WF_CSS_PAGE's container block) because this stylesheet is concatenated last: the same
951
+ // specificity means the later declaration wins.
952
+ "@container (max-width:900px){.wf-inject-dock{padding:6px 8px}" +
953
+ ".wf-inject-dock-row{flex-wrap:wrap}" +
954
+ ".wf-inject-dock-input{flex:1 1 120px}}",
955
+ ].join("\n");
956
+
957
+ /* ===== the single page: serial main flow + satellites + the right page ===== */
958
+
959
+ // The body is now ONE scrolling column plus an optional right page. The right page is a FLEX
960
+ // sibling, not an overlay: it takes width from the flow (which narrows, never re-flows its
961
+ // content), and the whole thing stays inside the card.
962
+ //
963
+ // The flow side carries `min-width:0` for the same reason 's split does: a flex item
964
+ // refuses to shrink below its content, and without it the long step titles would push the right
965
+ // page off the right edge instead of letting the chain use the scrollbar.
966
+ var WF_CSS_PAGE = [
967
+ ".wf-body{display:flex;flex-direction:row;align-items:stretch;gap:0;padding:0;overflow:hidden;min-height:0;flex:1 1 auto}",
968
+ ".wf-body[data-kind='planning']{padding:10px}",
969
+
970
+ /* ---- the main flow ---- */
971
+ ".wf-page{flex:1 1 auto;min-width:0;min-height:0;display:flex;flex-direction:column;position:relative}",
972
+ ".wf-page-head{display:flex;align-items:baseline;gap:8px;padding:8px 12px 6px;font-size:11px;font-weight:600;" +
973
+ "letter-spacing:.02em;color:var(--dsw-alias-label-secondary,#9aa3b2)}",
974
+ ".wf-page-scroll{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;" +
975
+ "padding:8px " + WF_PAGE_PAD + "px 40px}",
976
+ // The chain is centred as a document: the column of units is at most 560 px wide and the
977
+ // satellites hang in the gutters the padding reserves. `margin:0 auto` is what makes the page
978
+ // read as "centred and clean" instead of a card glued to the left edge.
979
+ ".wf-units{list-style:none;margin:0 auto;padding:0;max-width:560px;display:flex;flex-direction:column;gap:0}",
980
+
981
+ /* ---- one unit: [left satellites][centre][right satellites] ---- */
982
+ ".wf-unit{position:relative;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,3fr) minmax(0,1fr);" +
983
+ "align-items:center;gap:0;column-gap:0;padding:0 " + (WF_SAT_LINK + WF_PAGE_PAD) + "px}",
984
+ ".wf-unit-center{grid-column:2;min-width:0;display:flex;flex-direction:column;gap:8px;position:relative;z-index:2}",
985
+ ".wf-sat-side{grid-row:1;display:flex;flex-direction:column;gap:14px;min-width:0;position:relative;z-index:1}",
986
+ ".wf-sat-side[data-side='left']{grid-column:1;align-items:flex-end}",
987
+ ".wf-sat-side[data-side='right']{grid-column:3;align-items:flex-start}",
988
+ // The horizontal interface: a 1.6 px straight run from the unit's edge to the circle. Left side
989
+ // reads `[circle][line]`, right side `[line][circle]` — the line always touches the circle.
990
+ ".wf-sat-row{display:flex;align-items:center;gap:0;min-width:0}",
991
+ ".wf-sat-line{flex:0 0 " + WF_SAT_LINK + "px;height:1.6px;background:var(--dsw-alias-border-l2,rgba(255,255,255,.22));" +
992
+ "border-radius:1px}",
993
+ // 26 px is small: the label inside is a 4-character extension at most, so the circle needs
994
+ // `overflow:hidden`-grade discipline (no wrap, no overflow) and the count badge must sit fully
995
+ // inside the gutter the unit reserves, not on top of the interface line.
996
+ ".wf-sat{position:relative;box-sizing:border-box;flex:0 0 auto;width:" + WF_SATELLITE + "px;height:" + WF_SATELLITE + "px;" +
997
+ "border-radius:50%;border:1.6px solid var(--dsw-alias-border-l2,rgba(255,255,255,.26));background:var(--dsw-alias-bg-layer-2,#282d3a);" +
998
+ "color:var(--dsw-alias-label-secondary,#9aa3b2);font:inherit;font-size:9px;line-height:1;cursor:pointer;" +
999
+ "display:inline-flex;align-items:center;justify-content:center;padding:0;overflow:visible}",
1000
+ ".wf-sat:hover{border-color:var(--dsw-alias-brand-primary,#5b8cff);color:var(--dsw-alias-label-primary,#e9ecf3)}",
1001
+ ".wf-sat:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:2px}",
1002
+ // Tone = the SIDE's meaning, so the two gutters never mix up: read (left, blue), edit (right,
1003
+ // amber), tool (right, violet). The in-node chain chips use the same three hues.
1004
+ ".wf-sat[data-tone='read']{color:var(--wf-chain-read,#5b8cff);border-color:currentColor}",
1005
+ ".wf-sat[data-tone='edit']{color:var(--wf-chain-edit,#e0a13a);border-color:currentColor}",
1006
+ ".wf-sat[data-tone='tool']{color:var(--wf-chain-tool,#8f7bea);border-color:currentColor}",
1007
+ ".wf-sat[data-open='true']{box-shadow:0 0 0 3px rgba(91,140,255,.28)}",
1008
+ ".wf-sat-glyph{display:block;max-width:100%;padding:0 2px;font-size:9px;font-weight:600;letter-spacing:-.02em;" +
1009
+ "white-space:nowrap;overflow:hidden;text-overflow:clip;text-align:center;pointer-events:none}",
1010
+ // The count rides the circle's top-LEFT rim (towards the card), which is the one quadrant the
1011
+ // interface line never crosses — so the badge never sits on the line it belongs to.
1012
+ ".wf-sat-count{position:absolute;top:-6px;left:-6px;min-width:14px;height:14px;padding:0 3px;box-sizing:border-box;" +
1013
+ "border-radius:999px;background:var(--dsw-alias-bg-layer-1,#232733);border:1px solid currentColor;color:inherit;" +
1014
+ "font-size:8.5px;line-height:12px;text-align:center;font-variant-numeric:tabular-nums;pointer-events:none}",
1015
+ // Being called RIGHT NOW: the flowing ring the panel already uses for a wrapped frame.
1016
+ ".wf-sat[data-live='true']::after{content:'';position:absolute;inset:-3px;border-radius:50%;padding:2px;" +
1017
+ "background:" + WF_FX_RING + ";background-size:300% 100%;animation:wf-fx-wrap 2.6s linear infinite;" +
1018
+ "pointer-events:none;-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);" +
1019
+ "-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);" +
1020
+ "mask-composite:exclude}",
1021
+ // A live interface's straight line flows too — same rainbow, same 2 px, no second vocabulary.
1022
+ ".wf-sat-row[data-live='true'] .wf-sat-line{background:" + WF_FX_RING + ";background-size:300% 100%;" +
1023
+ "animation:wf-fx-wrap 2.6s linear infinite}",
1024
+
1025
+ /* ---- the unit card ---- */
1026
+ ".wf-unit-card{display:grid;grid-template-columns:auto auto minmax(0,1fr);align-items:flex-start;gap:8px;" +
1027
+ "width:100%;padding:10px 12px;border-radius:10px;text-align:left;border:1px solid transparent;" +
1028
+ "background:var(--dsw-alias-bg-layer-2,#282d3a);color:inherit;font:inherit;cursor:pointer;" +
1029
+ "border-left:3px solid transparent;box-sizing:border-box}",
1030
+ ".wf-unit-card:hover{border-color:var(--dsw-alias-border-l2,rgba(255,255,255,.22));" +
1031
+ "background:var(--dsw-alias-bg-layer-1,#232733)}",
1032
+ ".wf-unit-card:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
1033
+ ".wf-unit-card[data-selected='true']{border-color:var(--dsw-alias-brand-primary,#5b8cff)}",
1034
+ ".wf-unit-card[data-active='true']{border-left-color:var(--dsw-alias-brand-primary,#5b8cff)}",
1035
+ ".wf-unit-seq{font-size:10px;line-height:1.9;letter-spacing:.02em;font-variant-numeric:tabular-nums;" +
1036
+ "color:var(--dsw-alias-label-tertiary,#7d8798)}",
1037
+ ".wf-unit-ring{width:18px;height:18px;margin-top:1px;border-radius:50%;display:inline-flex;align-items:center;" +
1038
+ "justify-content:center;font-size:11px;line-height:1;border:1px solid currentColor;" +
1039
+ "color:var(--dsw-alias-state-idle-primary,#7d8798)}",
1040
+ ".wf-unit[data-status='done'] .wf-unit-ring{color:var(--dsw-alias-state-success-primary,#45c07a)}",
1041
+ ".wf-unit[data-status='running'] .wf-unit-ring{color:var(--dsw-alias-brand-primary,#5b8cff);" +
1042
+ "animation:wf-ring 1.6s ease-in-out infinite}",
1043
+ ".wf-unit[data-status='failed'] .wf-unit-ring{color:var(--dsw-alias-state-error-primary,#e85a5a)}",
1044
+ ".wf-unit[data-status='skipped'] .wf-unit-ring{color:var(--dsw-alias-label-tertiary,#7d8798);border-style:dashed}",
1045
+ ".wf-unit-main{display:flex;flex-direction:column;gap:3px;min-width:0}",
1046
+ ".wf-unit-title{font-size:12.5px;line-height:1.4;color:var(--dsw-alias-label-primary,#e9ecf3);" +
1047
+ "display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}",
1048
+ ".wf-unit-card[data-selected='true'] .wf-unit-title{font-weight:600}",
1049
+ ".wf-unit-meta{display:flex;align-items:center;flex-wrap:wrap;gap:6px;font-size:10.5px;" +
1050
+ "color:var(--dsw-alias-label-tertiary,#7d8798);font-variant-numeric:tabular-nums}",
1051
+ ".wf-unit[data-status='done'] .wf-badge{color:var(--dsw-alias-state-success-primary,#45c07a)}",
1052
+ ".wf-unit[data-status='running'] .wf-badge{color:var(--dsw-alias-brand-primary,#5b8cff)}",
1053
+ ".wf-unit[data-status='failed'] .wf-badge{color:var(--dsw-alias-state-error-primary,#e85a5a)}",
1054
+ ".wf-unit[data-status='pending'] .wf-badge{color:var(--dsw-alias-state-idle-primary,#7d8798)}",
1055
+ ".wf-unit[data-status='pending'] .wf-unit-title{color:var(--dsw-alias-label-secondary,#9aa3b2)}",
1056
+ ".wf-unit[data-status='skipped'] .wf-unit-title{color:var(--dsw-alias-label-tertiary,#7d8798);text-decoration:line-through}",
1057
+ ".wf-unit-more{font-size:9.5px;line-height:1.4;color:var(--dsw-alias-label-tertiary,#7d8798)}",
1058
+
1059
+ // The straight connector down the middle, in the gap the units leave: 2 px wide, WF_UNIT_GAP
1060
+ // tall, drawn ONLY between two units (never under the last one). Done steps get a solid line,
1061
+ // everything else a dashed one, so the chain reads "how far the one-way run has got".
1062
+ ".wf-unit-link{grid-column:2;grid-row:2;justify-self:center;width:" + WF_UNIT_LINK + "px;" +
1063
+ "height:" + WF_UNIT_GAP + "px;margin:0;background:var(--dsw-alias-border-l2,rgba(255,255,255,.2));" +
1064
+ "border-radius:1px}",
1065
+ ".wf-unit-link[data-done='false']{background:repeating-linear-gradient(180deg," +
1066
+ "var(--dsw-alias-border-l2,rgba(255,255,255,.2)) 0 5px,transparent 5px 10px)}",
1067
+ ".wf-unit-link[data-done='true']{background:var(--dsw-alias-state-success-primary,#45c07a);opacity:.65}",
1068
+ // The connectors between steps are now ALL one dashed style - no rainbow gradient, no scrolling animation: the whole main flow uses
1069
+ // one line language (done = solid green, everything else = grey dashed), leaving the expression of "what is happening" entirely to the
1070
+ // wrapping ring on the card and the single progress rail. `data-live` stays on the DOM (for tests) but no longer affects appearance.
1071
+ ".wf-unit-link[data-live='true']{background:repeating-linear-gradient(180deg," +
1072
+ "var(--dsw-alias-border-l2,rgba(255,255,255,.2)) 0 5px,transparent 5px 10px);animation:none}",
1073
+
1074
+ /* ---- the right page ---- */
1075
+ ".wf-right{flex:0 0 460px;min-width:0;min-height:0;display:flex;flex-direction:column;box-sizing:border-box;" +
1076
+ "border-left:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.12));" +
1077
+ "background:var(--dsw-alias-bg-layer-1,#232733)}",
1078
+ ".wf-right-head{display:flex;align-items:center;gap:8px;padding:8px 10px 8px 12px;" +
1079
+ "border-bottom:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.1));flex:0 0 auto}",
1080
+ ".wf-right-title{flex:1;min-width:0;font-size:11.5px;font-weight:600;white-space:nowrap;overflow:hidden;" +
1081
+ "text-overflow:ellipsis}",
1082
+ ".wf-page-close{flex:0 0 auto;width:22px;height:22px;padding:0;border-radius:6px;border:1px solid transparent;" +
1083
+ "background:transparent;color:var(--dsw-alias-label-secondary,#9aa3b2);font:inherit;font-size:13px;line-height:1;" +
1084
+ "cursor:pointer;display:inline-flex;align-items:center;justify-content:center}",
1085
+ ".wf-page-close:hover{background:var(--dsw-alias-bg-layer-2,#2c3140);color:var(--dsw-alias-label-primary,#e9ecf3)}",
1086
+ ".wf-page-close:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
1087
+ ".wf-right-scroll{flex:1 1 auto;min-height:0;overflow:auto;padding:10px 12px 16px;scrollbar-width:thin;" +
1088
+ "display:flex;flex-direction:column;gap:9px}",
1089
+ ".wf-page-sub{font-size:10.5px;line-height:1.45;color:var(--dsw-alias-label-tertiary,#7d8798)}",
1090
+
1091
+ /* the step page's type summary */
1092
+ ".wf-type-list{display:flex;flex-direction:column;gap:5px;min-width:0}",
1093
+ ".wf-type-rows{display:flex;flex-direction:column;gap:4px;min-width:0}",
1094
+ ".wf-type-row{display:flex;align-items:center;gap:7px;width:100%;padding:5px 7px;border-radius:7px;" +
1095
+ "border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.1));background:var(--dsw-alias-bg-base,#161920);" +
1096
+ "color:inherit;font:inherit;font-size:11px;cursor:pointer;text-align:left;box-sizing:border-box}",
1097
+ ".wf-type-row:hover{border-color:var(--dsw-alias-border-l2,rgba(255,255,255,.22))}",
1098
+ ".wf-type-row:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
1099
+ ".wf-type-row[data-open='true']{border-color:var(--dsw-alias-brand-primary,#5b8cff)}",
1100
+ ".wf-type-dot{flex:0 0 auto;width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-label-tertiary,#7d8798)}",
1101
+ ".wf-type-dot[data-tone='read']{background:var(--wf-chain-read,#5b8cff)}",
1102
+ ".wf-type-dot[data-tone='edit']{background:var(--wf-chain-edit,#e0a13a)}",
1103
+ ".wf-type-dot[data-tone='tool']{background:var(--wf-chain-tool,#8f7bea)}",
1104
+ ".wf-type-row[data-live='true']{box-shadow:0 0 0 1px rgba(143,123,234,.4)}",
1105
+ ".wf-type-label{font-weight:600;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary,#e9ecf3)}",
1106
+ ".wf-type-kind{flex:1;min-width:0;font-size:10px;color:var(--dsw-alias-label-tertiary,#7d8798)}",
1107
+ ".wf-type-count{font-size:10px;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary,#9aa3b2)}",
1108
+
1109
+ /* the type page's call list */
1110
+ ".wf-calls{display:flex;flex-direction:column;gap:6px;min-width:0}",
1111
+ ".wf-call-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px;min-width:0}",
1112
+ ".wf-call{min-width:0;display:flex;flex-direction:column;gap:5px}",
1113
+ ".wf-call-head{display:flex;align-items:center;gap:6px;width:100%;padding:5px 7px;border-radius:7px;" +
1114
+ "border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.1));background:var(--dsw-alias-bg-base,#161920);" +
1115
+ "color:inherit;font:inherit;font-size:11px;cursor:pointer;text-align:left;box-sizing:border-box}",
1116
+ ".wf-call-head:hover{border-color:var(--dsw-alias-border-l2,rgba(255,255,255,.22))}",
1117
+ ".wf-call-head:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8cff);outline-offset:1px}",
1118
+ ".wf-call-head[data-open='true']{border-color:var(--dsw-alias-brand-primary,#5b8cff)}",
1119
+ ".wf-call-main{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
1120
+ ".wf-call-meta{flex:0 0 auto;font-size:9.5px;font-variant-numeric:tabular-nums;" +
1121
+ "color:var(--dsw-alias-label-tertiary,#7d8798)}",
1122
+ ".wf-call[data-status='running'] .wf-call-main{color:var(--dsw-alias-brand-primary,#5b8cff)}",
1123
+
1124
+ // The running step card KEEPS its flowing colour wrap (that is the most important "current" cue);
1125
+ // only the rainbow on the connectors BETWEEN steps is gone - main-flow connectors are now one dashed style.
1126
+ ".wf-unit[data-status='running']>.wf-unit-center>.wf-unit-card{position:relative;" +
1127
+ "box-shadow:0 0 0 1px rgba(143,123,234,.3),0 0 14px rgba(91,140,255,.26)}",
1128
+ ".wf-unit[data-status='running']>.wf-unit-center>.wf-unit-card::after{content:'';position:absolute;inset:-3px;" +
1129
+ "border-radius:13px;padding:2px;background:" + WF_FX_RING + ";background-size:300% 100%;" +
1130
+ "animation:wf-fx-wrap 2.6s linear infinite;pointer-events:none;z-index:2;" +
1131
+ "-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;" +
1132
+ "mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude}",
1133
+ // The connector's flowing keyframes were removed together with the rainbow connectors (only the uniform dashed style remains).
1134
+ // Narrow: the right page stacks UNDER the chain and the satellites fold into tighter gutters.
1135
+ // The satellite line shrinks with them (a fixed 26 px run would push the circle off the edge).
1136
+ "@container (max-width:900px){.wf-body[data-kind='ready']{flex-direction:column}" +
1137
+ ".wf-right{flex:0 0 auto;width:100%;max-height:46%;border-left:0;" +
1138
+ "border-top:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,.12))}" +
1139
+ ".wf-units{max-width:100%}}",
1140
+ "@container (max-width:560px){.wf-unit{padding:0 4px}" +
1141
+ ".wf-sat-line{flex:0 0 10px}.wf-sat{width:22px;height:22px;font-size:8.5px}}",
1142
+ ].join("\n");
1143
+
1144
+ /* ===== the conversation-view seat hides the host's composer ===== */
1145
+
1146
+ // The panel is ONE child of the host's conversation column; the composer is a SIBLING the host
1147
+ // renders next to the selected view (measured in this host:
1148
+ // `div[data-conversation-content] > div[data-conversation-scroll] > [selected view, div[data-composer-seat]]`).
1149
+ // Nothing inside the panel's own tree can unmount it, so the TAB seat marks itself
1150
+ // (`data-dsh-workflow-map="conversation-view"`, see wfViewSeatRoot in panel.js) and this rule hides
1151
+ // the sibling seat while that marker is mounted — the todo dock and the input bar live inside it.
1152
+ //
1153
+ // SCOPED BY `:has` ON PURPOSE: the marker exists only while the host has THIS view selected (it
1154
+ // unmounts the others), so the composer returns by itself on chat / context / fleet - no state to keep in
1155
+ // sync, and no composer-chain takeover that would also swallow the host's plan-review composer.
1156
+ var WF_CSS_SEAT = [
1157
+ "[data-conversation-content]:has([data-dsh-workflow-map='conversation-view']) [data-composer-seat]{display:none!important}",
1158
+ ].join("\n");
1159
+
1160
+ WF_CSS = WF_CSS + "\n" + WF_CSS_PAGE;
1161
+
1162
+ WF_CSS = WF_CSS + "\n" + WF_CSS_INJECT;
1163
+
1164
+ WF_CSS = WF_CSS + "\n" + WF_CSS_SEAT;