@dsh-fly/dsh-plugin-fly-smart 0.0.0-stage → 0.2.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.
package/lib/client.js ADDED
@@ -0,0 +1,1569 @@
1
+ /* @dsh-fly/dsh-plugin-fly-smart — browser half, built by scripts/build-plugin-client.mjs.
2
+ * src-hash: bafe93920b51dce641f4f21bd094f1d70f3191ca4d20691b236cd5f7051172a4
3
+ * Factory-form CJS: the loader calls the factory with its platform `require`
4
+ * and memoizes the returned exports. Keep the banner so the loader id and the
5
+ * package that produced the bundle stay greppable in a live page. */
6
+ window.__ModuleLoader__.load({
7
+ id: "@dsh-fly/dsh-plugin-fly-smart",
8
+ factory: (require) => {
9
+ var module = { exports: {} };
10
+ var exports = module.exports;
11
+ "use strict";
12
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
13
+ const jsxRuntime = require("react/jsx-runtime");
14
+ const react = require("react");
15
+ const WORKBENCH_CSS = `
16
+ .fly-panel {
17
+ --fly-rail: 8px;
18
+ /* The desk, 12px, matching the gap the drawer already leaves above the panel -- and
19
+ it is a structural part of the edge, not spacing taste. With min-height:100% the
20
+ panel was welded to the drawer's bottom edge: measured 2026-10-04, its bottom edge
21
+ sat at y=900 of a 900px viewport with 0px of room below it, so the slab's 3px rim
22
+ was painted on the window's last rows with nothing under it. The top edge had 12px
23
+ of darker surface above it and read as depth; the bottom one read as the window's
24
+ own border (the user's report, verbatim). A lit rim is a relationship -- a bright
25
+ line over something darker -- so the bottom half of the ladder needs a surface to
26
+ land on. The subtraction is what keeps the page from gaining a 12px scroll:
27
+ a (100% - 12px) box plus a 12px margin is exactly 100%. */
28
+ --fly-desk: 12px;
29
+ box-sizing: border-box;
30
+ min-height: calc(100% - var(--fly-desk));
31
+ margin-bottom: var(--fly-desk);
32
+ padding: 22px 26px 34px;
33
+ color: var(--dsw-alias-label-primary, #e8eef7);
34
+ background:
35
+ radial-gradient(1200px 420px at 12% -10%, rgba(255, 176, 32, 0.06), transparent 70%),
36
+ var(--dsw-alias-bg-base, #070b12);
37
+ font-variant-numeric: tabular-nums;
38
+ }
39
+
40
+ /* ── Elevation recipe ───────────────────────────────────────────────────────
41
+ A surface only reads as a raised object when it carries *five* shadows at
42
+ once: a 1px lit top edge, a 1px shaded bottom edge, a hairline ring, and
43
+ three cast shadows that grow wider and softer with distance. That is the
44
+ recipe measured off the autox prototype (automodel.html, .card/.kpi),
45
+ and it is why its cards look lifted while ours looked like flat rectangles
46
+ with a drop shadow.
47
+
48
+ The ring is a box-shadow spread, never a border: a border grows the box by
49
+ 2px and shoves the layout around — the same rule the selection pill follows.
50
+ Colours are tokens (--dsw-specific-fly-*), so the light half gets its own strengths.
51
+
52
+ The upstream hooks are anchored to the END of the class attribute
53
+ ([class$='_card'], [class*='_card ']) because a plain substring walked
54
+ straight into the inner spans: upstream names the pieces of a thing
55
+ <hash>_newSession, <hash>_newSessionLabel, <hash>_newSessionContent, so
56
+ [class*='newSession'] painted a ring and a pane on the *label* too — measured
57
+ live as five matches on one button, which is the little rectangle the user
58
+ circled around the word 新会话. The same trap sits in _card/_cardBody.
59
+ A tool call's own *layer* is never in this list. Upstream draws both shapes as
60
+ flat lines -- ToolRow.row and the Bash sample's _card frame paint no border and no
61
+ background -- so the recipe belongs to what really is a card: the _ioCard (or the
62
+ Bash sample's _terminal) inside. First the 运行命令 layer, then every other tool:
63
+ the user asked for upstream's flat line both times.
64
+
65
+ Scope is the surfaces that both float *and* paint a background: upstream's
66
+ dialog role, the stat panes inside the shortcut modal, the composer card, the
67
+ rail's new-session button, upstream's _card-suffixed CSS-module cards (the
68
+ attachment/deliverable cards in a message) and our own cards. Bare wrappers
69
+ that merely contain a menu are deliberately left out — a wrapper has no
70
+ background, so a shadow on one paints a rectangle around nothing. */
71
+ html[data-fly-palette] .fly-card {
72
+ /* The pane gradient (prototype --glass-bg): a lit top, a flat middle and a
73
+ shaded bottom, painted over whatever background-color the surface already
74
+ carries. This is what separates "a pane" from "a rectangle". The 1px lit
75
+ line is baked into the gradient rather than drawn with a ::before, because
76
+ two of these surfaces belong to upstream and their pseudo-elements are not
77
+ ours to take. */
78
+ background-image: var(--dsw-specific-fly-pane, none);
79
+ box-shadow:
80
+ 0 1px 0 var(--dsw-specific-fly-surface-hi, rgba(255, 255, 255, 0.2)) inset,
81
+ 0 -1px 0 var(--dsw-specific-fly-surface-lo, rgba(0, 0, 0, 0.3)) inset,
82
+ 0 0 0 1px var(--dsw-specific-fly-ring, rgba(232, 238, 247, 0.13)),
83
+ var(--dsw-specific-fly-shadow-near, 0 1px 2px rgba(0, 0, 0, 0.22)),
84
+ var(--dsw-specific-fly-shadow-mid, 0 6px 16px rgba(0, 0, 0, 0.24)),
85
+ var(--dsw-specific-fly-shadow-far, 0 18px 44px rgba(0, 0, 0, 0.32));
86
+ }
87
+ /* The card's tech edge: a hairline that fades out at both ends along the top,
88
+ plus one brand-hued bloom in the corner (prototype .card::before and
89
+ .kpi::after). overflow: hidden keeps the bloom inside the card instead of
90
+ haloing its neighbours. */
91
+ html[data-fly-palette] .fly-card {
92
+ position: relative;
93
+ overflow: hidden;
94
+ }
95
+ html[data-fly-palette] .fly-card::before {
96
+ content: '';
97
+ position: absolute;
98
+ left: 5%;
99
+ right: 5%;
100
+ top: 0;
101
+ height: 1px;
102
+ background-image: linear-gradient(90deg, transparent, var(--dsw-specific-fly-sheen, rgba(255, 255, 255, 0.32)), transparent);
103
+ pointer-events: none;
104
+ }
105
+ html[data-fly-palette] .fly-card::after {
106
+ content: '';
107
+ position: absolute;
108
+ top: -60%;
109
+ right: -8%;
110
+ width: 200px;
111
+ height: 200px;
112
+ border-radius: 50%;
113
+ background-image: radial-gradient(circle, color-mix(in srgb, var(--dsw-alias-brand-primary, #00ff9c) 42%, transparent), transparent 62%);
114
+ filter: blur(28px);
115
+ opacity: 0.62;
116
+ pointer-events: none;
117
+ }
118
+ html[data-fly-palette='solid'] body:not([data-ds-dark-theme]) .fly-panel {
119
+ background:
120
+ radial-gradient(1200px 420px at 12% -10%, rgba(163, 74, 6, 0.10), transparent 70%),
121
+ linear-gradient(180deg, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0) 38%),
122
+ var(--dsw-alias-bg-base, #f0f4fb);
123
+ /* The panel is a lifted surface like a card, so it carries the same edge: a lit
124
+ top line (the rim that makes the pane's corner read as a corner), the matching
125
+ bottom shade -- a lit edge with nothing shading the far side is a line, not an
126
+ edge -- and the contact/ambient pair from the elevation tokens. */
127
+ box-shadow:
128
+ 0 1px 0 var(--dsw-specific-fly-surface-hi, #ffffff) inset,
129
+ 0 -1px 0 var(--dsw-specific-fly-surface-lo, rgba(13, 20, 32, 0.12)) inset,
130
+ var(--dsw-specific-fly-shadow-near, 0 1px 2px rgba(15, 23, 42, 0.09)),
131
+ var(--dsw-specific-fly-shadow-mid, 0 14px 34px rgba(17, 19, 22, 0.12)),
132
+ var(--dsw-specific-fly-shadow-far, 0 34px 80px rgba(17, 19, 22, 0.14));
133
+ }
134
+ html[data-fly-palette='glass'] body:not([data-ds-dark-theme]) .fly-panel,
135
+ html[data-fly-palette='volume'] body:not([data-ds-dark-theme]) .fly-panel,
136
+ html[data-fly-palette='neon'] body:not([data-ds-dark-theme]) .fly-panel {
137
+ background:
138
+ radial-gradient(1200px 420px at 12% -10%, rgba(163, 74, 6, 0.11), transparent 70%),
139
+ linear-gradient(170deg, rgba(255, 255, 255, 0.94), rgba(244, 249, 255, 0.58) 42%, rgba(234, 241, 252, 0.64)),
140
+ color-mix(in srgb, var(--dsw-alias-bg-base, #f0f4fb) 62%, transparent);
141
+ /* Same edge as the solid half, through the same tokens. It used to be a single
142
+ blue-tinted negative-spread shadow (rgba(16, 44, 92, 0.45)) -- the last cool
143
+ blue left in the light glass CSS, and not a contact/ambient pair at all. */
144
+ box-shadow:
145
+ 0 1px 0 var(--dsw-specific-fly-surface-hi, #ffffff) inset,
146
+ 0 -1px 0 var(--dsw-specific-fly-surface-lo, rgba(13, 20, 32, 0.12)) inset,
147
+ var(--dsw-specific-fly-shadow-near, 0 2px 4px rgba(17, 19, 22, 0.2)),
148
+ var(--dsw-specific-fly-shadow-mid, 0 18px 44px rgba(17, 19, 22, 0.16)),
149
+ var(--dsw-specific-fly-shadow-far, 0 34px 80px rgba(17, 19, 22, 0.14));
150
+ }
151
+ html[data-fly-palette='glass'] body:not([data-ds-dark-theme]) .fly-panel .fly-card,
152
+ html[data-fly-palette='volume'] body:not([data-ds-dark-theme]) .fly-panel .fly-card,
153
+ html[data-fly-palette='neon'] body:not([data-ds-dark-theme]) .fly-panel .fly-card {
154
+ box-shadow:
155
+ 0 1px 0 var(--dsw-specific-fly-surface-hi, #ffffff) inset,
156
+ 0 -1px 0 var(--dsw-specific-fly-surface-lo, rgba(13, 20, 32, 0.12)) inset,
157
+ var(--dsw-specific-fly-shadow-near, 0 2px 4px rgba(17, 19, 22, 0.2)),
158
+ var(--dsw-specific-fly-shadow-mid, 0 18px 44px rgba(17, 19, 22, 0.16));
159
+ }
160
+ /* The dark chip's neon halo is invisible on white; light needs its own halo or
161
+ the accent reads as plain text in a pill. Same two states, same icons and
162
+ border thicknesses as the dark half — only the halo strength changes. */
163
+ html[data-fly-palette] body:not([data-ds-dark-theme]) .fly-chip[data-tone='ok'] {
164
+ border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary, #0b7649) 45%, transparent);
165
+ background: color-mix(in srgb, var(--dsw-alias-state-success-primary, #0b7649) 7%, transparent);
166
+ box-shadow:
167
+ 0 0 0 1px color-mix(in srgb, var(--dsw-alias-state-success-primary, #0b7649) 10%, transparent),
168
+ 0 0 16px -6px color-mix(in srgb, var(--dsw-alias-state-success-primary, #0b7649) 45%, transparent);
169
+ }
170
+ html[data-fly-palette] body:not([data-ds-dark-theme]) .fly-chip[data-tone='warn'] {
171
+ border-color: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #c22343) 45%, transparent);
172
+ box-shadow: 0 0 16px -6px color-mix(in srgb, var(--dsw-alias-state-warn-primary, #c22343) 40%, transparent);
173
+ }
174
+ html[data-fly-palette] body:not([data-ds-dark-theme]) .fly-chip[data-tone='error'] {
175
+ border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary, #bf1733) 55%, transparent);
176
+ box-shadow: 0 0 16px -6px color-mix(in srgb, var(--dsw-alias-state-error-primary, #bf1733) 45%, transparent);
177
+ }
178
+ /* Item 4 of the reference-image pass: corners. The radius the cards float on was
179
+ 12px everywhere; the reference look wants a bigger, softer corner on the surfaces
180
+ that are *lifted* and nothing else -- an inline control with a flat 14px corner
181
+ reads as a button someone forgot to finish. So the lifted set moves to 14px in the
182
+ light half (dark keeps 12px: this pass is the light half's, and dark's pixels are
183
+ asserted unchanged), the Fly panel goes to 16px and the cards inside it to 14px.
184
+ The rail and the drawer column are deliberately *not* rounded -- see the floating
185
+ rail entry in DESIGN.md's TODO list: rounding them means detaching them from the
186
+ frame, which is a layout change, not a radius change. The tool-call cards
187
+ (_ioCard / the expanded body cards) are also left at 12px: an earlier round pinned
188
+ their geometry and the live assertion says so out loud.
189
+ NOTE: no backticks in this block -- the whole stylesheet is a template literal. */
190
+ html[data-fly-palette] body:not([data-ds-dark-theme]) .fly-card {
191
+ border-radius: 14px;
192
+ }
193
+ html[data-fly-palette='glass'] body:not([data-ds-dark-theme]) .fly-panel,
194
+ html[data-fly-palette='volume'] body:not([data-ds-dark-theme]) .fly-panel,
195
+ html[data-fly-palette='neon'] body:not([data-ds-dark-theme]) .fly-panel {
196
+ border-radius: 16px;
197
+ }
198
+ html[data-fly-palette='glass'] body:not([data-ds-dark-theme]) .fly-panel .fly-card,
199
+ html[data-fly-palette='volume'] body:not([data-ds-dark-theme]) .fly-panel .fly-card,
200
+ html[data-fly-palette='neon'] body:not([data-ds-dark-theme]) .fly-panel .fly-card {
201
+ border-radius: 14px;
202
+ }
203
+ /* Trial: warm brand gold on the light panel edge and on the cards inside it.
204
+ Measured on the real drawer (glass, light): the panel edge is the border upstream
205
+ paints (1px solid rgba(13, 20, 32, 0.1) before this rule) and so is the card's --
206
+ its box-shadow is the lit line, the bottom shade and two casts, no ring layer --
207
+ so both take the colour on that border. The card sits 27px/136px inside the panel,
208
+ i.e. two separate lines, never one edge drawn twice. The first cut of this trial put
209
+ the colour on a box-shadow ring indirection; sampling the real drawer panel showed
210
+ that indirection had NO consumer there -- the panel's edge is the 1px border
211
+ upstream paints (measured live: 1px solid rgba(13, 20, 32, 0.1)) and its shadow
212
+ carries only the lit line, the bottom shade and the projection. So the colour goes
213
+ on the border that is actually painted. A border is one line by construction: the
214
+ single-edge guard's "exactly one clean edge" cannot regress here, and the two gold
215
+ cards (input card, new session) stay out of scope for the same reason as before --
216
+ they already carry their own amber ring, and a second line on them is the bevel
217
+ double-line class of bug. The neutral --dsw-specific-fly-ring token is untouched:
218
+ it is not what this rule paints. Dark never loads this rule (light half only). */
219
+ html[data-fly-palette] body:not([data-ds-dark-theme]) .fly-panel:not([data-composer-card]):not([class*='newSession']),
220
+ html[data-fly-palette] body:not([data-ds-dark-theme]) .fly-panel:not([data-composer-card]):not([class*='newSession']) .fly-card,
221
+ html[data-fly-palette] body:not([data-ds-dark-theme]) .fly-card:not([data-composer-card]):not([class*='newSession']) {
222
+ border-color: var(--dsw-specific-fly-edge-brand, rgba(163, 74, 6, 0.22));
223
+ }
224
+ html[data-fly-palette='glass'] body:not([data-ds-dark-theme]) .fly-panel,
225
+ html[data-fly-palette='volume'] body:not([data-ds-dark-theme]) .fly-panel,
226
+ html[data-fly-palette='neon'] body:not([data-ds-dark-theme]) .fly-panel {
227
+ backdrop-filter: blur(30px) saturate(140%);
228
+ }
229
+ html[data-fly-palette='glass'] body:not([data-ds-dark-theme]) .fly-panel .fly-card,
230
+ html[data-fly-palette='volume'] body:not([data-ds-dark-theme]) .fly-panel .fly-card,
231
+ html[data-fly-palette='neon'] body:not([data-ds-dark-theme]) .fly-panel .fly-card {
232
+ backdrop-filter: blur(22px) saturate(135%);
233
+ }
234
+ /* ── Fly 面板的滚动可达性(fly.console 等内容够不着)─────────────────────────
235
+ 症状:Fly 控制台(fly.console 页签)里 token 列表下半截够不着、页面滚不动。
236
+ 实测(1040x900,浅/深 × 实心/玻璃四组合一致):token 列表所在 .fly-panel 的
237
+ clientHeight 860~862 而 scrollHeight 1225~1491(实心 1225/1281,玻璃 1477/1491),
238
+ 但面板自己没有声明过任何 overflow(computed = visible,即初始值),而它的父层
239
+ P3OORG_tabBody(上游页签容器)是 overflow-y: hidden —— 溢出被父层裁掉,整条祖先链上
240
+ 没有任何滚动容器:所以这不是"滚动条不可见",是"根本不能滚"(scrollTop 无处可设)。
241
+ 候选逐条排除:③ 滚动条被我们弄没 —— 全表没有任何 scrollbar-width / ::-webkit-scrollbar /
242
+ scrollbar-color 规则,排除;① 我们的配方里 overflow:hidden —— 全表唯一一处 overflow:hidden
243
+ 在 .kpi::after 上,与面板无关,排除;④ position:fixed/absolute 无滚动上下文 —— 面板是
244
+ 普通流内定高块,排除;⑤ 上游页签容器自身不滚 —— 属实(tabBody overflow-y:hidden),
245
+ 但上游不动;② 高度算成内容高 —— 面板高度是定值(860),问题在不滚。
246
+ 修法:让面板自己成为滚动容器(皮肤的面板配方,一次性覆盖各 Fly 面板)。
247
+ 硬约束不变:不新增线、不加粗、不改金色身份;圆角/边线/对齐守卫不受影响。 */
248
+ html[data-fly-palette] .fly-panel {
249
+ overflow-y: auto;
250
+ min-height: 0;
251
+ }
252
+
253
+ /* The glass palette: a filter is not a colour, so no alias token can carry the
254
+ blur or the hairline edge — these live here, scoped by the attribute the
255
+ client sets on <html> from the active palette, so every .fly-panel in the
256
+ composition (shell, diagnostics, netops, plugins) gets the same treatment.
257
+ The blur only shows where something sits behind the panel; in the drawer
258
+ column that is the app background, which is why the edge and the wash carry
259
+ most of the effect. */
260
+ html[data-fly-palette='glass'] .fly-panel,
261
+ html[data-fly-palette='volume'] .fly-panel,
262
+ html[data-fly-palette='neon'] .fly-panel {
263
+ background:
264
+ radial-gradient(1200px 420px at 12% -10%, rgba(255, 176, 32, 0.10), transparent 70%),
265
+ linear-gradient(165deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.015) 38%, transparent 70%),
266
+ color-mix(in srgb, var(--dsw-alias-bg-base, #070b12) 74%, transparent);
267
+ backdrop-filter: blur(20px) saturate(150%);
268
+ border: 1px solid var(--dsw-alias-border-l1, #1c2736);
269
+ border-radius: 14px;
270
+ box-shadow:
271
+ inset 0 1px 0 rgba(255, 255, 255, 0.08),
272
+ 0 18px 50px -24px rgba(0, 0, 0, 0.55);
273
+ }
274
+ /* The slab ladder: the panel stops being a wall and becomes a lit slab. Dark half
275
+ of the glass palette only, on purpose, and the scope is the whole decision:
276
+
277
+ - The light half already carries this idea in its own dialect ("every lifted
278
+ surface wears the same edge", a lit 1px top and a shaded bottom, DESIGN.md
279
+ section 3) and it is asserted surface by surface. A second, differently shaped
280
+ edge there would be two overlapping recipes, not a better one.
281
+ - The solid palette is the battle-room look and its panel is a full-bleed page:
282
+ flat by design. The reverse assertion in verify-shell pins that it stays flat.
283
+ - What is left is the thinnest edge in the system -- the dark glass panel had a
284
+ single 0.08 white inset -- and the reference sheet is a dark glass UI.
285
+
286
+ The five layers are the measured anatomy, inside out: a 1px top hairline, the
287
+ inner falloff under it, a slow brightening towards the bottom, a 3px bottom rim,
288
+ and the 1px dark cut line painted *over* the rim's outer pixel. Order matters:
289
+ the first listed shadow paints on top, so listing the cut before the rim is what
290
+ puts the dark line at the very edge with the bright rim just inside it. In the
291
+ reference that bottom rim is the brightest structural line on the page (luma 164
292
+ against a body of 89 and a gap of 19) -- it is the lift, and it is why the slab
293
+ reads as lit from below rather than merely outlined.
294
+
295
+ Strengthened in two steps on 2026-10-04, both at the user's call. First the rim:
296
+ the first cut was 2px at 0.42 white and too quiet to notice in a running window --
297
+ a real one-pixel line at 1x, in one corner of one page. It is now 3px of visible
298
+ light (a 4px band with the cut covering its outer pixel) at 0.62 white, which lands
299
+ the rim near the reference's own luma instead of a third of it. Then the *rise*,
300
+ after the user's second read: "your highlight is only on the top edge". They were
301
+ right, and the number behind it is the body's own gradient -- the shipped 165 degree
302
+ wash lights the top of the panel and is spent by the time it reaches the bottom, so
303
+ the live panel measures 46 near its top edge and 8.4 down in its bottom padding.
304
+ Against that, 0.10 white was +9 luma over the last 16px: a line standing on a dark
305
+ board, which is why the bottom edge read as drawn on rather than lit. The reference
306
+ sheet brightens its body towards the bottom (71 -> 89, +18), so the rise is now 0.22
307
+ over a 20px band, and both halves of the claim are asserted live rather than in the
308
+ fixture: the amplitude (>= 15 luma between the body 32px in and the body just above
309
+ the rim) and that it has flattened out before the content starts (the panel's own
310
+ 34px bottom padding is what the band has to stay inside). The floors in verify-shell rose with it, and the
311
+ rim's *width* is asserted too: a brighter edge that quietly narrows back is the
312
+ same regression as a brighter edge that fades.
313
+
314
+ The geometry lives here and nowhere else: inset shadows paint at the padding box
315
+ edge, so the slab keeps its edge while a long page scrolls under it, and unlike
316
+ an overlay they cannot swallow a click. The cast layer is repeated verbatim from
317
+ the block above because box-shadow does not compose -- this rule replaces that
318
+ stack rather than adding to it, in this palette and scheme only. */
319
+ html[data-fly-palette='glass'] body[data-ds-dark-theme] .fly-panel,
320
+ html[data-fly-palette='volume'] body[data-ds-dark-theme] .fly-panel {
321
+ box-shadow:
322
+ inset 0 -1px 0 var(--dsw-specific-fly-slab-under, transparent),
323
+ inset 0 -4px 0 var(--dsw-specific-fly-slab-bottom, transparent),
324
+ inset 0 1px 0 var(--dsw-specific-fly-slab-top, transparent),
325
+ inset 0 -20px 30px -18px var(--dsw-specific-fly-slab-rise, transparent),
326
+ /* 8px band and 8px blur, not the 12px band this wanted to be: the padding
327
+ invariant in verify-shell measures the worst case (offset + |spread| +
328
+ blur/2 = 20px) against the panel's 22px top padding, and a wider band would
329
+ have washed the first line of text to buy a curve nobody asked for. */
330
+ inset 0 8px 8px -8px var(--dsw-specific-fly-slab-falloff, transparent),
331
+ 0 18px 50px -24px rgba(0, 0, 0, 0.55);
332
+ }
333
+ .fly-panel h1 {
334
+ margin: 0 0 2px;
335
+ font-size: 20px;
336
+ font-weight: 600;
337
+ letter-spacing: 0.01em;
338
+ }
339
+ .fly-sub {
340
+ margin: 0 0 20px;
341
+ color: var(--dsw-alias-label-secondary, #8b9bb4);
342
+ font-size: 12.5px;
343
+ }
344
+ .fly-chip-row {
345
+ display: flex;
346
+ flex-wrap: wrap;
347
+ gap: 8px;
348
+ margin-bottom: 20px;
349
+ }
350
+ .fly-chip {
351
+ display: inline-flex;
352
+ align-items: center;
353
+ gap: 6px;
354
+ padding: 4px 10px;
355
+ border: 1px solid var(--dsw-alias-border-l1, #1c2736);
356
+ border-radius: 999px;
357
+ background: var(--dsw-alias-bg-layer-1, #0d131c);
358
+ font-family: var(--fly-mono, ui-monospace, SFMono-Regular, monospace);
359
+ font-size: 11.5px;
360
+ color: var(--dsw-alias-label-secondary, #8b9bb4);
361
+ }
362
+ .fly-chip[data-tone='ok'] {
363
+ border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary, #00ff9c) 40%, transparent);
364
+ color: var(--dsw-alias-state-success-primary, #00ff9c);
365
+ box-shadow:
366
+ 0 0 0 1px color-mix(in srgb, var(--dsw-alias-state-success-primary, #00ff9c) 8%, transparent),
367
+ 0 0 18px -6px color-mix(in srgb, var(--dsw-alias-state-success-primary, #00ff9c) 35%, transparent);
368
+ }
369
+ /* 告警与失败:同一个红族的两级,靠**图标 + 边框厚度**分开 —— 色相只差一档,
370
+ 小字号 chip 上单看颜色分不出来。warn = ▲ + 1px(chip 的发丝线),
371
+ error = ✕ + 2px。
372
+ 厚度只能走整像素:Chrome 把 border-width 的 resolved value 取整,1.5px
373
+ 在 deviceScaleFactor 2 下也算成 1px(实测),拿分数当区分通道会静默失效。 */
374
+ .fly-chip[data-tone='warn'],
375
+ .fly-chip[data-tone='error'] {
376
+ font-weight: 600;
377
+ }
378
+ .fly-chip[data-tone='warn'] {
379
+ border-width: 1px;
380
+ border-color: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #ff6b85) 55%, transparent);
381
+ color: var(--dsw-alias-state-warn-primary, #ff6b85);
382
+ }
383
+ .fly-chip[data-tone='error'] {
384
+ border-width: 2px;
385
+ border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary, #ff3b5c) 70%, transparent);
386
+ color: var(--dsw-alias-state-error-primary, #ff3b5c);
387
+ }
388
+ .fly-chip[data-tone='warn']::before {
389
+ content: '▲';
390
+ font-size: 8.5px;
391
+ line-height: 1;
392
+ }
393
+ .fly-chip[data-tone='error']::before {
394
+ content: '✕';
395
+ font-size: 10px;
396
+ line-height: 1;
397
+ }
398
+ .fly-grid {
399
+ display: grid;
400
+ /* 260px let two cards fit a ~600px drawer, which left each of them ~290px —
401
+ too narrow for a two-column key/value list whose value can be rgba(...).
402
+ At 340px they stack in the narrow drawer and pair up again when wide. */
403
+ grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
404
+ gap: 12px;
405
+ }
406
+ .fly-card {
407
+ /* Grid children default to min-width:auto, so one long token (a profile path,
408
+ a bundle id) can force the track wider than the drawer and push the right
409
+ column out of the window — measured as values clipped at the viewport edge.
410
+ Letting the card shrink makes the text wrap instead. */
411
+ min-width: 0;
412
+ padding: 14px 16px;
413
+ border: 1px solid var(--dsw-alias-border-l1, #1c2736);
414
+ border-radius: 10px;
415
+ background: var(--dsw-alias-bg-layer-1, #0d131c);
416
+ }
417
+ /* A glass card carries its own blur, so it reads as a pane even when the panel
418
+ behind it is only a flat colour. */
419
+ html[data-fly-palette='glass'] .fly-panel .fly-card,
420
+ html[data-fly-palette='volume'] .fly-panel .fly-card,
421
+ html[data-fly-palette='neon'] .fly-panel .fly-card {
422
+ border-radius: 12px;
423
+ backdrop-filter: blur(14px) saturate(140%);
424
+ box-shadow:
425
+ 0 1px 0 var(--dsw-specific-fly-surface-hi, rgba(255, 255, 255, 0.2)) inset,
426
+ 0 -1px 0 var(--dsw-specific-fly-surface-lo, rgba(0, 0, 0, 0.3)) inset,
427
+ 0 0 0 1px var(--dsw-specific-fly-ring, rgba(232, 238, 247, 0.13)),
428
+ var(--dsw-specific-fly-shadow-near, 0 1px 2px rgba(0, 0, 0, 0.22)),
429
+ 0 8px 22px -12px rgba(0, 0, 0, 0.55);
430
+ }
431
+ .fly-card > h2 {
432
+ margin: 0 0 10px;
433
+ font-size: 12px;
434
+ font-weight: 600;
435
+ letter-spacing: 0.08em;
436
+ text-transform: uppercase;
437
+ color: var(--dsw-alias-label-secondary, #8b9bb4);
438
+ }
439
+ .fly-kv {
440
+ display: grid;
441
+ grid-template-columns: minmax(96px, max-content) 1fr;
442
+ gap: 4px 14px;
443
+ font-family: var(--fly-mono, ui-monospace, SFMono-Regular, monospace);
444
+ font-size: 12px;
445
+ }
446
+ .fly-kv dt {
447
+ min-width: 0;
448
+ overflow-wrap: anywhere;
449
+ color: var(--dsw-alias-label-secondary, #8b9bb4);
450
+ }
451
+ .fly-kv dd {
452
+ min-width: 0;
453
+ overflow-wrap: anywhere;
454
+ }
455
+ .fly-note {
456
+ margin-top: 18px;
457
+ color: var(--dsw-alias-label-secondary, #8b9bb4);
458
+ font-size: 12px;
459
+ line-height: 1.6;
460
+ }
461
+ .fly-mark {
462
+ display: block;
463
+ color: currentColor;
464
+ }
465
+ .fly-mark[data-active='true'] {
466
+ filter: drop-shadow(0 0 6px rgba(255, 176, 32, 0.5));
467
+ }
468
+
469
+ /* The Settings row (official settings.general.item slot): the owner draws no
470
+ frame of its own, so the title, the control and the hint are all this row. */
471
+ .fly-skin-row {
472
+ display: flex;
473
+ flex-direction: column;
474
+ align-items: flex-start;
475
+ gap: 8px;
476
+ }
477
+ .fly-skin-title {
478
+ color: var(--dsw-alias-label-primary, #e8eef7);
479
+ font-size: 13px;
480
+ font-weight: 600;
481
+ }
482
+ .fly-skin-hint {
483
+ color: var(--dsw-alias-label-secondary, #8b9bb4);
484
+ font-size: 12px;
485
+ line-height: 1.5;
486
+ }
487
+
488
+ /* ---------------------------------------------------------------------------
489
+ * Workbench primitives.
490
+ *
491
+ * Every Fly workbench renders: a header with a refresh control, a telemetry
492
+ * table, severity tags, and a findings list. Those live here, in the skin, so a
493
+ * workbench plugin only writes what is genuinely its own — which is why this
494
+ * stylesheet is the shared surface and a workbench's own tag stays small.
495
+ * ------------------------------------------------------------------------- */
496
+ .fly-actions {
497
+ display: flex;
498
+ align-items: baseline;
499
+ justify-content: space-between;
500
+ gap: 16px;
501
+ margin-bottom: 14px;
502
+ }
503
+ /* 表单字段(或目录未就绪时的提示)后面紧跟动作行时要留间距:.fly-field 只给自己 margin-top、
504
+ .fly-actions 只给自己 margin-bottom,中间原本是 0(实测 select 底边 == 按钮行顶边 438,
505
+ 整排按钮贴着选择框)。相邻选择器只作用于这两个组合,别的页面把动作行放在卡片顶部时不受影响。 */
506
+ .fly-field + .fly-actions,
507
+ .fly-note + .fly-actions {
508
+ margin-top: 10px;
509
+ }
510
+ .fly-btn {
511
+ flex: none;
512
+ padding: 5px 12px;
513
+ border: 1px solid var(--dsw-alias-border-l1, #1c2736);
514
+ border-radius: 8px;
515
+ background: var(--dsw-alias-bg-layer-2, #131b28);
516
+ color: var(--dsw-alias-label-primary, #e8eef7);
517
+ font: inherit;
518
+ font-size: 12px;
519
+ cursor: pointer;
520
+ }
521
+ .fly-btn:hover:not(:disabled) {
522
+ border-color: var(--dsw-alias-brand-primary, #00ff9c);
523
+ color: var(--dsw-alias-brand-primary, #00ff9c);
524
+ }
525
+ /* 选中态必须把 hover 的品牌边留住:鼠标移开就掉色的按钮,用户看不出"现在选的是哪个"
526
+ (smart 面板的任务选择与产物 tab 都用 data-selected;2026-10-01 用户实测报的)。 */
527
+ .fly-btn[data-selected='true']:not(:disabled) {
528
+ border-color: var(--dsw-alias-brand-primary, #00ff9c);
529
+ color: var(--dsw-alias-brand-primary, #00ff9c);
530
+ }
531
+ .fly-btn:disabled {
532
+ opacity: 0.55;
533
+ cursor: progress;
534
+ }
535
+ .fly-table {
536
+ width: 100%;
537
+ border-collapse: collapse;
538
+ font-family: var(--fly-mono, ui-monospace, SFMono-Regular, monospace);
539
+ font-size: 12px;
540
+ }
541
+ .fly-table th {
542
+ padding: 0 8px 6px 0;
543
+ text-align: left;
544
+ font-weight: 500;
545
+ color: var(--dsw-alias-label-secondary, #8b9bb4);
546
+ border-bottom: 1px solid var(--dsw-alias-border-l1, #1c2736);
547
+ }
548
+ .fly-table td {
549
+ padding: 4px 8px 4px 0;
550
+ border-bottom: 1px solid rgba(28, 39, 54, 0.55);
551
+ overflow-wrap: anywhere;
552
+ }
553
+ .fly-table tr:last-child td {
554
+ border-bottom: 0;
555
+ }
556
+ .fly-table td:first-child {
557
+ max-width: 220px;
558
+ }
559
+ .fly-num {
560
+ text-align: right;
561
+ font-variant-numeric: tabular-nums;
562
+ }
563
+ .fly-tag {
564
+ /* 与 .fly-chip 同款:作为 flex item 时 display 会被块化成 block(实测 computed = block),
565
+ inline-block 那套"盒子围住文字"的假设就没了 —— 行盒贴顶、盒子被拉高,字看起来偏高
566
+ (实测:盒 907–933 / 行盒 910–922,墨迹中心比盒中心高 3.8px)。inline-flex + center
567
+ 让盒子仍可被拉高,但字恒在盒内居中,和芯片同一套语义。 */
568
+ display: inline-flex;
569
+ align-items: center;
570
+ justify-content: center;
571
+ /* 间隔交给 gap:::before 的字形在 flex 布局里会丢掉 content 末尾的空格。 */
572
+ gap: 0.3em;
573
+ padding: 1px 7px;
574
+ border: 1px solid var(--dsw-alias-border-l2, #2a3a50);
575
+ border-radius: 999px;
576
+ font-size: 10.5px;
577
+ letter-spacing: 0.04em;
578
+ }
579
+ .fly-tag[data-severity='high'] {
580
+ border-width: 2px;
581
+ border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary, #ff3b5c) 60%, transparent);
582
+ color: var(--dsw-alias-state-error-primary, #ff3b5c);
583
+ font-weight: 600;
584
+ }
585
+ .fly-tag[data-severity='med'],
586
+ .fly-tag[data-severity='watch'],
587
+ .fly-tag[data-severity='warn'] {
588
+ border-width: 1px;
589
+ border-color: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #ff6b85) 55%, transparent);
590
+ color: var(--dsw-alias-state-warn-primary, #ff6b85);
591
+ }
592
+ /* Same second channel as the chips: the two red levels carry a glyph and a
593
+ different border thickness, so 告警 and 失败 are distinguishable without
594
+ trusting the eye to separate #ff6b85 from #ff3b5c at 10.5px. Integer widths
595
+ only — Chrome's resolved border-width rounds (see the chip rule). */
596
+ .fly-tag[data-severity='high']::before {
597
+ content: '✕';
598
+ }
599
+ .fly-tag[data-severity='med']::before,
600
+ .fly-tag[data-severity='watch']::before,
601
+ .fly-tag[data-severity='warn']::before {
602
+ content: '▲';
603
+ }
604
+ .fly-tag[data-severity='info'] {
605
+ color: var(--dsw-alias-state-idle-primary, #d4d4d4);
606
+ }
607
+ .fly-tag[data-severity='ok'] {
608
+ border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary, #00ff9c) 50%, transparent);
609
+ color: var(--dsw-alias-state-success-primary, #00ff9c);
610
+ }
611
+ /* ── 相位板四态(smart 工作台) ─────────────────────────────────────────────
612
+ The smart panel stamps the state machine's own word on both the gate chip and
613
+ the status tag (data-phase-status), so the skin paints four *named* states
614
+ instead of borrowing a severity word from another domain: 待做 = the official
615
+ idle grey, 进行中 = the pending cyan, 已过门 = the done green plus the glow,
616
+ 已归档 = the same done green **without** the glow. The chip and the tag share
617
+ each rule — they are one state shown in two voices. */
618
+ .fly-chip[data-phase-status='todo'],
619
+ .fly-tag[data-phase-status='todo'] {
620
+ color: var(--dsw-alias-state-idle-primary, #d4d4d4);
621
+ }
622
+ .fly-chip[data-phase-status='active'],
623
+ .fly-tag[data-phase-status='active'] {
624
+ border-color: color-mix(in srgb, var(--dsw-specific-fly-pending, #35d3e0) 55%, transparent);
625
+ color: var(--dsw-specific-fly-pending, #35d3e0);
626
+ }
627
+ .fly-chip[data-phase-status='passed'],
628
+ .fly-tag[data-phase-status='passed'] {
629
+ border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary, #00ff9c) 50%, transparent);
630
+ color: var(--dsw-alias-state-success-primary, #00ff9c);
631
+ text-shadow: 0 0 8px color-mix(in srgb, var(--dsw-alias-state-success-primary, #00ff9c) 45%, transparent);
632
+ }
633
+ /* 已归档(2026-10-01 改):绿字绿框,**不发光、不淡出**。旧的写法是 idle 灰 + opacity
634
+ 0.72,两条理由推翻了它:① 辉光的语义是"门刚过 / 还在这条流程里",而归档恰恰是"已经
635
+ 收起来、不需要你看"—— 给页面上最不需要看的一格发光是反的;② 灰 + 淡出让**终态比尚未
636
+ 开始的「待做」还淡**(方向反了,用户对着截图问"为什么不是发光绿色")。
637
+ 现在它与「已过门」同色,差别就是那层辉光(浅色半区本就关辉光,两态靠词与门芯片的
638
+ — / G5 分);也不再与「待做」的规则逐字相同(那条 0.72 原本就是拿来区分这两个灰的)。 */
639
+ .fly-chip[data-phase-status='archived'],
640
+ .fly-tag[data-phase-status='archived'] {
641
+ border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary, #00ff9c) 50%, transparent);
642
+ color: var(--dsw-alias-state-success-primary, #00ff9c);
643
+ }
644
+ /* The glow is a dark-half device, and this is where it gets switched off: on
645
+ white it does not read as light coming off the text, it reads as a smear
646
+ behind it (dense CJK glyphs merge their shadows into one pale block — the
647
+ first state-sheet render showed exactly that). The light half keeps the
648
+ darker green ink and drops the halo, the same "no neon on white" rule the
649
+ rest of the palette follows. Written after the rules it cancels, and gated
650
+ on the same body marker as every other light-half override. */
651
+ html[data-fly-palette] body:not([data-ds-dark-theme]) .fly-chip[data-phase-status='passed'],
652
+ html[data-fly-palette] body:not([data-ds-dark-theme]) .fly-tag[data-phase-status='passed'] {
653
+ text-shadow: none;
654
+ }
655
+ /* A wildcard bind is reachable from anywhere; a bound one is not. */
656
+ .fly-scope[data-scope='all'] {
657
+ color: var(--dsw-alias-state-warn-primary, #ff6b85);
658
+ }
659
+ .fly-find {
660
+ display: flex;
661
+ align-items: baseline;
662
+ gap: 10px;
663
+ padding: 8px 0;
664
+ border-bottom: 1px solid rgba(28, 39, 54, 0.55);
665
+ font-size: 12.5px;
666
+ }
667
+ .fly-find:last-child {
668
+ border-bottom: 0;
669
+ }
670
+ .fly-find code {
671
+ flex: none;
672
+ font-family: var(--fly-mono, ui-monospace, SFMono-Regular, monospace);
673
+ color: var(--dsw-alias-label-primary, #e8eef7);
674
+ }
675
+ .fly-empty {
676
+ margin: 0;
677
+ color: var(--dsw-alias-label-secondary, #8b9bb4);
678
+ font-size: 12px;
679
+ }
680
+ .fly-note-error {
681
+ margin: 0 0 12px;
682
+ padding: 10px 12px;
683
+ /* 失败 = 红族的 error 级:2px 边 + ✕,和 warn 的 1.5px 边 + ▲ 成对。 */
684
+ border: 2px solid color-mix(in srgb, var(--dsw-alias-state-error-primary, #ff3b5c) 55%, transparent);
685
+ border-radius: 8px;
686
+ color: var(--dsw-alias-state-error-primary, #ff3b5c);
687
+ font-size: 12px;
688
+ }
689
+ .fly-note-error::before {
690
+ content: '✕ ';
691
+ font-weight: 700;
692
+ }
693
+ /* The 双模型路由 card in the fly.smart drawer page: the workbench pages' first
694
+ editable control, so its frame is the skin's rather than the browser's. The hook
695
+ is a data attribute the card owns, not a class, so a future workbench field can
696
+ opt into the same frame without the panel shipping a stylesheet of its own. */
697
+ .fly-field {
698
+ display: flex;
699
+ flex-direction: column;
700
+ gap: 4px;
701
+ margin-top: 10px;
702
+ }
703
+ .fly-field > span {
704
+ color: var(--dsw-alias-label-secondary, #8b9bb4);
705
+ font-size: 11px;
706
+ }
707
+ .fly-field input[data-fly-smart-field],
708
+ .fly-field select[data-fly-smart-field] {
709
+ width: 100%;
710
+ box-sizing: border-box;
711
+ padding: 6px 8px;
712
+ border: 1px solid rgba(13, 20, 32, 0.18);
713
+ border-radius: 6px;
714
+ background: transparent;
715
+ color: var(--dsw-alias-label-primary, #e8eef7);
716
+ font-family: var(--fly-mono, ui-monospace, SFMono-Regular, monospace);
717
+ font-size: 12px;
718
+ }
719
+ .fly-field input[data-fly-smart-field]:focus-visible,
720
+ .fly-field select[data-fly-smart-field]:focus-visible {
721
+ outline: none;
722
+ border-color: #ffb020;
723
+ box-shadow: 0 0 0 2px rgba(255, 176, 32, 0.35);
724
+ }
725
+ html[data-ds-dark-theme] .fly-field input[data-fly-smart-field],
726
+ html[data-ds-dark-theme] .fly-field select[data-fly-smart-field] {
727
+ border-color: rgba(232, 238, 247, 0.22);
728
+ } .fly-panel {
729
+ box-shadow:
730
+ inset 0 -1px 0 var(--dsw-specific-fly-slab-under, transparent),
731
+ inset 0 -4px 0 var(--dsw-specific-fly-slab-bottom, transparent),
732
+ inset 0 1px 0 var(--dsw-specific-fly-slab-top, transparent),
733
+ inset 0 -20px 30px -18px var(--dsw-specific-fly-slab-rise, transparent),
734
+ inset 0 8px 8px -8px var(--dsw-specific-fly-slab-falloff, transparent),
735
+ 0 0 0 1px var(--dsw-specific-fly-ring, rgba(232, 238, 247, 0.13)),
736
+ var(--dsw-specific-fly-shadow-near, 0 1px 2px rgba(0, 0, 0, 0.22)),
737
+ var(--dsw-specific-fly-shadow-mid, 0 6px 16px rgba(0, 0, 0, 0.24)),
738
+ var(--dsw-specific-fly-shadow-far, 0 18px 44px rgba(0, 0, 0, 0.32));
739
+ } .fly-card {
740
+ box-shadow:
741
+ inset 0 -1px 0 var(--dsw-specific-fly-slab-under, transparent),
742
+ inset 0 -3px 0 var(--dsw-specific-fly-slab-bottom, transparent),
743
+ inset 0 1px 0 var(--dsw-specific-fly-slab-top, transparent),
744
+ inset 0 -12px 14px -11px var(--dsw-specific-fly-slab-rise, transparent),
745
+ inset 0 5px 6px -5px var(--dsw-specific-fly-slab-falloff, transparent),
746
+ 0 0 0 1px var(--dsw-specific-fly-ring, rgba(232, 238, 247, 0.13)),
747
+ var(--dsw-specific-fly-shadow-near, 0 1px 2px rgba(0, 0, 0, 0.22)),
748
+ var(--dsw-specific-fly-shadow-mid, 0 6px 16px rgba(0, 0, 0, 0.24)),
749
+ var(--dsw-specific-fly-shadow-far, 0 18px 44px rgba(0, 0, 0, 0.32));
750
+ }
751
+
752
+ /* 玻璃材质本体:目标图(01–08)的 .glass —— 163° 冷蓝渐变 + 0.46 暗基座 +
753
+ 118° 抛光带 + 两个顶角高光 —— 现在整套住在 pane token 里(强度也在
754
+ token:spec-corner / spec-sheen 嵌在 pane 的层里),所以家族卡片 / 金卡 /
755
+ 工具琥珀栈 / 面板读的是同一块玻璃。第一轮只给 .fly-panel 上了收尾,用户
756
+ 看的卡片还是暗板 —— 那就是「玻璃2 没变化」的根因。面板自己只多一样东西:
757
+ 琥珀色场,画在玻璃层之下,从 0.46 的半透基座里透出来(目标图面板底下的
758
+ 光池语义;层序照 .glass 的解剖:色场在下、材质在上、收尾在最上)。 */
759
+ html[data-fly-palette='volume'] body[data-ds-dark-theme] .fly-panel {
760
+ background-image:
761
+ var(--dsw-specific-fly-pane, none),
762
+ radial-gradient(1200px 420px at 12% -10%, rgba(255, 176, 32, 0.06), transparent 70%),
763
+ var(--dsw-alias-bg-base, #070b12);
764
+ } .fly-panel h1 {
765
+ background-color: var(--dsw-specific-fly-plate-bg, rgba(8, 12, 20, 0.92));
766
+ box-shadow: inset 0 0 0 1px var(--dsw-specific-fly-plate-line, rgba(150, 190, 230, 0.22));
767
+ /* 面板标题不是这轮裁决的对象:它旁边没有选项框要协调,原样留在 8px。 */
768
+ border-radius: 8px;
769
+ } .fly-panel h1 {
770
+ text-shadow: none;
771
+ filter: none;
772
+ } .fly-panel h1 {
773
+ display: inline-block;
774
+ padding: 4px 12px;
775
+ } .fly-panel {
776
+ box-shadow:
777
+ inset 0 -1px 0 var(--dsw-specific-fly-slab-under, transparent),
778
+ inset 0 -4px 0 var(--dsw-specific-fly-slab-bottom, transparent),
779
+ inset 0 1px 0 var(--dsw-specific-fly-slab-top, transparent),
780
+ inset 0 -20px 30px -18px var(--dsw-specific-fly-slab-rise, transparent),
781
+ inset 0 8px 8px -8px var(--dsw-specific-fly-slab-falloff, transparent),
782
+ 0 30px 80px -20px rgba(53, 211, 224, 0.3),
783
+ 0 6px 28px -6px rgba(53, 211, 224, 0.2),
784
+ 0 18px 50px -24px rgba(0, 0, 0, 0.6);
785
+ }
786
+
787
+ /* T7:文字上板(两个半区都画,靠 plate token 翻方言:深色近黑,浅色白瓷)。
788
+ 只写 background-color,不碰 border-color —— 带 tone 的芯片(ok/warn/error)
789
+ 的描边与着色规则特异性更高,照旧生效;板只垫在没 tone 的芯片底下。 */
790
+ html[data-fly-palette='neon'] .fly-chip {
791
+ background-color: var(--dsw-specific-fly-plate-bg, rgba(4, 7, 12, 0.88));
792
+ } .fly-panel h1 {
793
+ display: inline-block;
794
+ padding: 4px 12px;
795
+ background-color: var(--dsw-specific-fly-plate-bg, rgba(4, 7, 12, 0.88));
796
+ box-shadow: inset 0 0 0 1px var(--dsw-specific-fly-plate-line, rgba(150, 190, 230, 0.26));
797
+ border-radius: 8px;
798
+ }
799
+ `;
800
+ const name = "fly-smart-panel";
801
+ const inject = ["slots", "sidebarRightTabs"];
802
+ const KIND = "fly.smart";
803
+ const TAB_TITLE = "Smart 工作流";
804
+ const TAB_DESCRIPTION = "smart 任务的阶段板、产物、门记录与路由告警";
805
+ const GUIDE_ORDER = 40;
806
+ const PACKAGE = "dsh-plugin-fly-smart-panel";
807
+ const BASE = "/fly/smart";
808
+ const BOARD = [
809
+ { id: "brainstorm", label: "脑暴", gate: "G1" },
810
+ { id: "design", label: "设计", gate: "G2" },
811
+ { id: "plan", label: "计划", gate: "G3" },
812
+ { id: "acceptance", label: "验收设计", gate: "G4" },
813
+ { id: "executing", label: "执行", gate: null },
814
+ { id: "reviewing", label: "验收", gate: "G5" },
815
+ { id: "done", label: "完成", gate: null }
816
+ ];
817
+ const ARTIFACT_LABEL = {
818
+ brainstorm: "脑暴",
819
+ design: "设计",
820
+ plan: "计划",
821
+ acceptance: "验收设计",
822
+ executing: "执行报告",
823
+ // 设计文档把这一阶段的产物名叫「验收」(review.md = 逐条验收结论),阶段板也这么叫;
824
+ // 标签曾是「评审」——那是**手模型逐阶段评审**的说法(同一页 `<dt>评审</dt>` 用对了)。
825
+ reviewing: "验收",
826
+ summary: "收尾总结"
827
+ };
828
+ const STATUS_LABEL = {
829
+ todo: "待做",
830
+ active: "进行中",
831
+ passed: "已过门",
832
+ archived: "已归档"
833
+ };
834
+ const PHASE_LABEL = Object.fromEntries(BOARD.map((entry) => [entry.id, entry.label]));
835
+ const PRE_STYLE = {
836
+ margin: "0",
837
+ maxHeight: "340px",
838
+ overflow: "auto",
839
+ whiteSpace: "pre-wrap",
840
+ overflowWrap: "anywhere",
841
+ fontFamily: "var(--fly-mono, ui-monospace, SFMono-Regular, monospace)",
842
+ fontSize: "12px",
843
+ lineHeight: 1.6
844
+ };
845
+ const BRIEFING_LABEL = {
846
+ none: "未生成",
847
+ "awaiting-user": "待确认",
848
+ confirmed: "已确认",
849
+ stale: "已过期",
850
+ failed: "生成失败",
851
+ revise: "需修改"
852
+ };
853
+ function briefingStatus(task) {
854
+ const report = task.briefings;
855
+ return report?.[task.state.phase]?.status ?? "none";
856
+ }
857
+ function briefingRoute(task) {
858
+ const report = task.briefings;
859
+ return report?.[task.state.phase]?.route ?? null;
860
+ }
861
+ function reworkExhausted(entry) {
862
+ return typeof entry.reworkUsed === "number" && typeof entry.reworkCap === "number" && entry.reworkCap > 0 && entry.reworkUsed >= entry.reworkCap;
863
+ }
864
+ let browserContext = null;
865
+ class HostError extends Error {
866
+ constructor(status, message) {
867
+ super(message);
868
+ this.name = "HostError";
869
+ this.status = status;
870
+ }
871
+ }
872
+ async function getJson(path) {
873
+ const response = await fetch(path, { headers: { accept: "application/json" } });
874
+ const text = await response.text();
875
+ let payload = null;
876
+ try {
877
+ payload = text === "" ? null : JSON.parse(text);
878
+ } catch {
879
+ payload = null;
880
+ }
881
+ if (!response.ok) {
882
+ const explained = typeof payload === "object" && payload !== null && typeof payload.error === "string" ? payload.error : `${path} → HTTP ${response.status}`;
883
+ throw new HostError(response.status, explained);
884
+ }
885
+ return payload;
886
+ }
887
+ function reworkBudgetOf(state) {
888
+ const max = state.settings?.maxRework ?? 5;
889
+ const grants = (state.reworkGrants ?? []).filter((grant) => grant.phase === state.phase).length;
890
+ const used = (state.history ?? []).filter((entry) => entry.to === state.phase).length;
891
+ return { used, cap: max * (1 + grants), grants };
892
+ }
893
+ function artifactOpenDecisions(task) {
894
+ const markdown = task.artifacts[task.state.phase]?.markdown ?? "";
895
+ if (markdown === "") return null;
896
+ const headings = [];
897
+ let items = 0;
898
+ let inside = false;
899
+ for (const raw of markdown.split("\n")) {
900
+ const line = raw.trim();
901
+ if (/^#{1,6}\s/u.test(line)) {
902
+ inside = line.includes("需要你裁决");
903
+ if (inside) headings.push(line.replace(/^#{1,6}\s*/u, "").trim());
904
+ continue;
905
+ }
906
+ if (inside && /^([-*+]|\d+[.)])\s+\S/u.test(line)) items += 1;
907
+ }
908
+ return headings.length === 0 ? null : { headings, items };
909
+ }
910
+ function rulingsCountOf(task) {
911
+ const rows = task.rulings;
912
+ if (typeof rows === "number") return rows;
913
+ return Array.isArray(rows) ? rows.length : 0;
914
+ }
915
+ function pendingDecisionsOf(task) {
916
+ return task.state.pendingDecisions ?? artifactOpenDecisions(task);
917
+ }
918
+ function decidedCount(task) {
919
+ let total = 0;
920
+ for (const rounds of Object.values(task.state.decisions ?? {})) {
921
+ for (const round of Array.isArray(rounds) ? rounds : []) total += Array.isArray(round?.entries) ? round.entries.length : 0;
922
+ }
923
+ return total;
924
+ }
925
+ function formatTime(value) {
926
+ if (typeof value !== "string" || value === "") return "—";
927
+ const parsed = new Date(value);
928
+ if (Number.isNaN(parsed.getTime())) return value;
929
+ return parsed.toISOString().slice(0, 19).replace("T", " ");
930
+ }
931
+ function formatBytes(bytes) {
932
+ if (typeof bytes !== "number" || !Number.isFinite(bytes)) return "—";
933
+ return bytes < 1024 ? `${bytes} B` : `${(bytes / 1024).toFixed(1)} KB`;
934
+ }
935
+ function formatRoute(provider, model) {
936
+ return provider === null || model === null ? "—" : `${provider}/${model}`;
937
+ }
938
+ function readSessionId(from) {
939
+ if (typeof document === "undefined") return null;
940
+ const owner = from?.closest("[data-sidebar-right-session]") ?? document.querySelector("[data-sidebar-right-session]");
941
+ const value = owner?.getAttribute("data-sidebar-right-session") ?? "";
942
+ return value === "" ? null : value;
943
+ }
944
+ function hostErrorText(payload, status) {
945
+ if (typeof payload === "object" && payload !== null) {
946
+ const message = payload.error;
947
+ if (typeof message === "string" && message !== "") return message;
948
+ }
949
+ return `HTTP ${status}`;
950
+ }
951
+ const SOURCE_LABEL = {
952
+ preset: "preset 行(压过本页)",
953
+ session: "本会话",
954
+ default: "所有会话默认",
955
+ none: "没有任何一层设置 —— 调用工具时会大声报错"
956
+ };
957
+ function reviewEffectiveLabel(config) {
958
+ const value = config?.effectiveReview?.review ?? "";
959
+ const source = config?.effectiveReview?.source ?? null;
960
+ if (value === "") return "(未配置 —— 评审与取证复用 hand 路由)";
961
+ return `${value} · ${source === null ? "未知来源" : SOURCE_LABEL[source]}`;
962
+ }
963
+ function RouteCard({ config, sessionId, onSaved }) {
964
+ const [draft, setDraft] = react.useState("");
965
+ const [saving, setSaving] = react.useState(false);
966
+ const [status, setStatus] = react.useState(null);
967
+ const [ladder, setLadder] = react.useState("unavailable");
968
+ const [options, setOptions] = react.useState([]);
969
+ const inputRef = react.useRef(null);
970
+ const reviewInputRef = react.useRef(null);
971
+ const roleRef = react.useRef("hand");
972
+ const typed = react.useRef(false);
973
+ react.useEffect(() => {
974
+ const next = config?.session?.hand ?? config?.default?.hand ?? "";
975
+ if (typed.current) return;
976
+ if (inputRef.current !== null) inputRef.current.value = next;
977
+ if (reviewInputRef.current !== null) reviewInputRef.current.value = config?.session?.review ?? config?.default?.review ?? "";
978
+ setDraft(next);
979
+ }, [config]);
980
+ react.useEffect(() => {
981
+ if (sessionId === null) {
982
+ setLadder("unavailable");
983
+ setOptions([]);
984
+ return;
985
+ }
986
+ const models = browserContext?.get?.("modelDirectories");
987
+ if (models === void 0 || models === null || typeof models.directoryFor !== "function") {
988
+ setLadder("unavailable");
989
+ return;
990
+ }
991
+ let cancelled = false;
992
+ void (async () => {
993
+ try {
994
+ const snapshot = await models.directoryFor?.(sessionId)?.load();
995
+ if (cancelled) return;
996
+ const next = [];
997
+ for (const group of snapshot?.groups ?? []) {
998
+ for (const model of group.models ?? []) {
999
+ const base = `${group.id}/${model.id}`;
1000
+ next.push({ value: base, label: base });
1001
+ for (const effort of model.reasoning?.efforts ?? []) {
1002
+ next.push({ value: `${base}@${effort.id}`, label: `${base} · ${effort.name ?? effort.id}` });
1003
+ }
1004
+ }
1005
+ }
1006
+ setOptions(next);
1007
+ setLadder(next.length === 0 ? "pending" : "ready");
1008
+ } catch {
1009
+ if (!cancelled) {
1010
+ setOptions([]);
1011
+ setLadder("unavailable");
1012
+ }
1013
+ }
1014
+ })();
1015
+ return () => {
1016
+ cancelled = true;
1017
+ };
1018
+ }, [sessionId]);
1019
+ const presetEffective = config?.presetRow.effective === true;
1020
+ const canSession = sessionId !== null && config?.sessionKnown !== false;
1021
+ const activeBox = () => roleRef.current === "review" ? reviewInputRef.current : inputRef.current;
1022
+ const boxValue = () => activeBox()?.value ?? draft;
1023
+ const save = async (scope, value = boxValue().trim()) => {
1024
+ setSaving(true);
1025
+ setStatus(null);
1026
+ try {
1027
+ const response = await fetch(`${BASE}/routes`, {
1028
+ method: "POST",
1029
+ headers: { "content-type": "application/json" },
1030
+ body: JSON.stringify(scope === "session" ? { [roleRef.current]: value, role: roleRef.current, scope, sessionId } : { [roleRef.current]: value, role: roleRef.current, scope })
1031
+ });
1032
+ const text = await response.text();
1033
+ let payload = null;
1034
+ try {
1035
+ payload = text === "" ? null : JSON.parse(text);
1036
+ } catch {
1037
+ payload = null;
1038
+ }
1039
+ if (!response.ok) {
1040
+ setStatus({ tone: "error", text: hostErrorText(payload, response.status) });
1041
+ return;
1042
+ }
1043
+ const written = payload;
1044
+ typed.current = false;
1045
+ const where = scope === "session" ? "本会话" : "所有会话默认";
1046
+ setStatus({
1047
+ tone: written.effectiveFromPanel ? "ok" : "warn",
1048
+ text: written.changed ? `已保存到${where}(${written.at ?? "—"})${written.effectiveFromPanel ? "" : " · 但 preset 行已有路由,工具面仍用它"}` : `值未变化 —— 磁盘上已经是 ${value === "" ? "(空)" : value}`
1049
+ });
1050
+ onSaved();
1051
+ } catch (error) {
1052
+ setStatus({ tone: "error", text: `请求失败:${error instanceof Error ? error.message : String(error)}` });
1053
+ } finally {
1054
+ setSaving(false);
1055
+ }
1056
+ };
1057
+ return /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "fly-card", "data-probe": "route-config", children: [
1058
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "三模型路由" }),
1059
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-sub", "data-probe": "route-path", children: `配置面 ${config?.path ?? "…"}(hand / review 可写;brain 只读)` }),
1060
+ /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "fly-kv", children: [
1061
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "brain(只读)" }),
1062
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { "data-probe": "brain-route", children: config?.brain?.value ?? "(镜像里没有 brain 键 —— 本页只写 hand / review)" }),
1063
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "hand 当前生效" }),
1064
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { "data-probe": "effective-route", children: `${config?.effective?.hand === void 0 || config.effective.hand === "" ? "(未配置)" : config.effective.hand} · ${SOURCE_LABEL[config?.effective?.source ?? "none"]}` }),
1065
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "review 当前生效" }),
1066
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { "data-probe": "effective-review-route", children: reviewEffectiveLabel(config) }),
1067
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "本会话 hand" }),
1068
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { "data-probe": "session-route", children: config?.session?.hand === void 0 || config.session.hand === "" ? "(未单独设置)" : config.session.hand }),
1069
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "本会话 review" }),
1070
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { "data-probe": "session-review-route", children: config?.session?.review === void 0 || config.session.review === "" ? "(未单独设置)" : config.session.review }),
1071
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "所有会话默认 hand" }),
1072
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { "data-probe": "default-route", children: config?.default?.hand === void 0 || config.default.hand === "" ? "(未设置)" : config.default.hand }),
1073
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "所有会话默认 review" }),
1074
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { "data-probe": "default-review-route", children: config?.default?.review === void 0 || config.default.review === "" ? "(未设置)" : config.default.review })
1075
+ ] }),
1076
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "fly-field", children: [
1077
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "hand 模型(provider/model,可选 @effort)" }),
1078
+ /* @__PURE__ */ jsxRuntime.jsx(
1079
+ "input",
1080
+ {
1081
+ type: "text",
1082
+ "data-fly-smart-field": "hand",
1083
+ ref: inputRef,
1084
+ defaultValue: config?.session?.hand ?? config?.default?.hand ?? "",
1085
+ placeholder: "deepseek/deepseek-flash",
1086
+ spellCheck: false,
1087
+ onFocus: () => {
1088
+ roleRef.current = "hand";
1089
+ },
1090
+ onChange: (event) => {
1091
+ roleRef.current = "hand";
1092
+ typed.current = true;
1093
+ setDraft(event.target.value);
1094
+ }
1095
+ }
1096
+ )
1097
+ ] }),
1098
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "fly-field", children: [
1099
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "review 模型(provider/model,可选 @effort)" }),
1100
+ /* @__PURE__ */ jsxRuntime.jsx(
1101
+ "input",
1102
+ {
1103
+ type: "text",
1104
+ "data-fly-smart-field": "review",
1105
+ ref: reviewInputRef,
1106
+ defaultValue: config?.session?.review ?? config?.default?.review ?? "",
1107
+ placeholder: "deepseek/deepseek-reasoner",
1108
+ spellCheck: false,
1109
+ onFocus: () => {
1110
+ roleRef.current = "review";
1111
+ },
1112
+ onChange: (event) => {
1113
+ roleRef.current = "review";
1114
+ typed.current = true;
1115
+ setDraft(event.target.value);
1116
+ }
1117
+ }
1118
+ )
1119
+ ] }),
1120
+ ladder === "ready" ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "fly-field", children: [
1121
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "从模型目录选择(写入你正在编辑的那个输入框)" }),
1122
+ /* @__PURE__ */ jsxRuntime.jsxs(
1123
+ "select",
1124
+ {
1125
+ "data-fly-smart-field": "ladder",
1126
+ defaultValue: "",
1127
+ onChange: (event) => {
1128
+ if (event.target.value === "") return;
1129
+ typed.current = true;
1130
+ const box = activeBox();
1131
+ if (box !== null) box.value = event.target.value;
1132
+ setDraft(event.target.value);
1133
+ },
1134
+ children: [
1135
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "(从目录里挑一个)" }),
1136
+ options.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, children: option.label }, option.value))
1137
+ ]
1138
+ }
1139
+ )
1140
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-note", "data-probe": "ladder-state", "data-ladder": ladder, children: ladder === "unavailable" ? "模型目录服务不可用(或本页读不到会话 id)—— 请手工填写 provider/model。" : "模型目录还没就绪 —— 可以先手工填写,稍后目录到了再从下拉里挑。" }),
1141
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-actions", children: [
1142
+ /* @__PURE__ */ jsxRuntime.jsx(
1143
+ "button",
1144
+ {
1145
+ className: "fly-btn",
1146
+ type: "button",
1147
+ "data-act": "save-session",
1148
+ disabled: saving || !canSession,
1149
+ onClick: () => {
1150
+ void save("session");
1151
+ },
1152
+ children: "保存到本会话"
1153
+ }
1154
+ ),
1155
+ /* @__PURE__ */ jsxRuntime.jsx(
1156
+ "button",
1157
+ {
1158
+ className: "fly-btn",
1159
+ type: "button",
1160
+ "data-act": "save-default",
1161
+ disabled: saving || presetEffective,
1162
+ onClick: () => {
1163
+ void save("default");
1164
+ },
1165
+ children: "设为所有会话默认"
1166
+ }
1167
+ ),
1168
+ /* @__PURE__ */ jsxRuntime.jsx(
1169
+ "button",
1170
+ {
1171
+ className: "fly-btn",
1172
+ type: "button",
1173
+ "data-act": "clear-session",
1174
+ disabled: saving || !canSession || config?.session === null || config?.session === void 0,
1175
+ onClick: () => {
1176
+ void save("session", "");
1177
+ },
1178
+ children: "清空本会话"
1179
+ }
1180
+ )
1181
+ ] }),
1182
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-note", "data-probe": "route-target", children: "上面三个动作作用于你最后碰过的那个输入框(hand 或 review)—— 点一下要改的那条,再按保存。" }),
1183
+ presetEffective ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-note", "data-probe": "preset-note", children: `preset 行已把路由钉成 ${config?.presetRow.hand ?? ""},它压过本页(也压过镜像)—— 「设为所有会话默认」因此禁用;本会话的写入只有在该行清空后才轮到工具面。` }) : null,
1184
+ status === null ? null : /* @__PURE__ */ jsxRuntime.jsx("p", { className: status.tone === "error" ? "fly-note-error" : "fly-note", "data-probe": "route-status", "data-tone": status.tone, children: status.text })
1185
+ ] });
1186
+ }
1187
+ function phaseStatus(phase, task) {
1188
+ const state = task.state;
1189
+ const archived = typeof state.archivedAt === "string";
1190
+ const cells = task.board ?? BOARD;
1191
+ const order = cells.map((entry) => entry.id);
1192
+ const currentIndex = order.indexOf(state.phase);
1193
+ const index = order.indexOf(phase.id);
1194
+ if (archived) return phase.id === "done" ? "archived" : "passed";
1195
+ if (phase.mergedInto !== void 0) {
1196
+ const pairLanded = task.artifacts.plan !== void 0 && task.artifacts.acceptance !== void 0;
1197
+ if (!pairLanded) return "todo";
1198
+ const currentIndexForPair = order.indexOf(state.phase);
1199
+ const planIndex = order.indexOf(phase.mergedInto);
1200
+ return currentIndexForPair !== -1 && planIndex < currentIndexForPair ? "passed" : "active";
1201
+ }
1202
+ const gatePassed = phase.gate !== null && task.gates.some((record) => record.gate === phase.gate);
1203
+ if (gatePassed || currentIndex !== -1 && index < currentIndex) return "passed";
1204
+ if (index === currentIndex) return "active";
1205
+ return "todo";
1206
+ }
1207
+ function artifactTabs(task) {
1208
+ const phases = ["brainstorm", "design", "plan", "acceptance", "executing", "reviewing"];
1209
+ const present = phases.filter((phase) => task.artifacts[phase] !== void 0);
1210
+ return task.summary === null ? present : [...present, "summary"];
1211
+ }
1212
+ function artifactText(task, phase) {
1213
+ if (phase === "summary") return task.summary?.markdown ?? null;
1214
+ return task.artifacts[phase]?.markdown ?? null;
1215
+ }
1216
+ function MarkdownView({ markdown }) {
1217
+ const lines = markdown.split("\n");
1218
+ const blocks = [];
1219
+ let buffer = [];
1220
+ const flush = () => {
1221
+ if (buffer.length === 0) return;
1222
+ blocks.push({ kind: "text", body: buffer.join("\n") });
1223
+ buffer = [];
1224
+ };
1225
+ for (const line of lines) {
1226
+ const heading = /^(#{1,6})\s+(.*)$/u.exec(line);
1227
+ if (heading === null) {
1228
+ buffer.push(line);
1229
+ continue;
1230
+ }
1231
+ flush();
1232
+ blocks.push({ kind: "heading", body: heading[2], level: heading[1].length });
1233
+ }
1234
+ flush();
1235
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: blocks.map((block, index) => block.kind === "heading" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-sub", "data-heading": block.level, style: { margin: "10px 0 6px" }, children: block.body }, `h-${index}`) : /* @__PURE__ */ jsxRuntime.jsx("pre", { style: PRE_STYLE, children: block.body.replace(/^\n+|\n+$/gu, "") }, `t-${index}`)) });
1236
+ }
1237
+ function SmartPanel() {
1238
+ const [tasks, setTasks] = react.useState([]);
1239
+ const [root, setRoot] = react.useState("");
1240
+ const [selected, setSelected] = react.useState(null);
1241
+ const [task, setTask] = react.useState(null);
1242
+ const [tab, setTab] = react.useState(null);
1243
+ const [notice, setNotice] = react.useState(null);
1244
+ const [busy, setBusy] = react.useState(true);
1245
+ const [routeConfig, setRouteConfig] = react.useState(null);
1246
+ const [legacyCount, setLegacyCount] = react.useState(0);
1247
+ const [unknownWorkspace, setUnknownWorkspace] = react.useState(null);
1248
+ const [sessionId, setSessionId] = react.useState(null);
1249
+ const [jumped, setJumped] = react.useState(null);
1250
+ const rootRef = react.useRef(null);
1251
+ const artifactsRef = react.useRef(null);
1252
+ const scopedSession = react.useCallback(() => {
1253
+ const found = readSessionId(rootRef.current);
1254
+ setSessionId(found);
1255
+ return found;
1256
+ }, []);
1257
+ const load = react.useCallback(async (wanted) => {
1258
+ setBusy(true);
1259
+ const sid = scopedSession();
1260
+ const query = sid === null ? "" : `?sessionId=${encodeURIComponent(sid)}`;
1261
+ try {
1262
+ const namespace = await getJson(`${BASE}${query}`);
1263
+ setRoot(namespace.root);
1264
+ setRouteConfig(namespace.routeConfig ?? null);
1265
+ if (namespace.exists === false) {
1266
+ setTasks([]);
1267
+ setTask(null);
1268
+ setNotice({ kind: "missing", message: namespace.message ?? `${namespace.root} 下还没有任何 smart 任务。` });
1269
+ return;
1270
+ }
1271
+ const index = await getJson(`${BASE}/tasks${query}`);
1272
+ setRoot(index.root);
1273
+ setTasks(index.tasks);
1274
+ setLegacyCount(index.legacyCount ?? 0);
1275
+ const next = wanted ?? index.tasks[0]?.id ?? null;
1276
+ setSelected(next);
1277
+ if (index.unknownWorkspace === true) {
1278
+ setTask(null);
1279
+ setUnknownWorkspace(index.message ?? "无法确定当前工作区。");
1280
+ setNotice(null);
1281
+ return;
1282
+ }
1283
+ setUnknownWorkspace(null);
1284
+ if (next === null) {
1285
+ setTask(null);
1286
+ setNotice({ kind: "missing", message: `${index.root} 下还没有任何 smart 任务。` });
1287
+ return;
1288
+ }
1289
+ const detail = await getJson(`${BASE}/task?id=${encodeURIComponent(next)}${sid === null ? "" : `&sessionId=${encodeURIComponent(sid)}`}`);
1290
+ setTask(detail);
1291
+ setNotice(null);
1292
+ setTab((current) => {
1293
+ const available = artifactTabs(detail);
1294
+ return current !== null && available.includes(current) ? current : available[0] ?? null;
1295
+ });
1296
+ } catch (error) {
1297
+ setTask(null);
1298
+ const message = error instanceof Error ? error.message : String(error);
1299
+ setNotice({ kind: error instanceof HostError && error.status === 503 ? "missing" : "error", message });
1300
+ } finally {
1301
+ setBusy(false);
1302
+ }
1303
+ }, [scopedSession]);
1304
+ react.useEffect(() => {
1305
+ void load();
1306
+ }, [load]);
1307
+ const jump = (phase) => {
1308
+ setTab(phase);
1309
+ setJumped(phase);
1310
+ };
1311
+ react.useEffect(() => {
1312
+ if (jumped === null) return;
1313
+ const card = artifactsRef.current ?? rootRef.current?.querySelector('[data-probe="artifacts"]') ?? null;
1314
+ const button = card?.querySelector(`button[data-artifact="${jumped}"]`);
1315
+ if (button === null || button === void 0) return;
1316
+ const target = button;
1317
+ target.focus({ preventScroll: true });
1318
+ card?.scrollIntoView({ block: "nearest" });
1319
+ }, [jumped, tab]);
1320
+ const hand = task?.routes.hand ?? [];
1321
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "fly-panel", "data-plugin": PACKAGE, "data-session-id": sessionId ?? "", ref: rootRef, children: [
1322
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { children: "Smart 工作流" }),
1323
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "fly-sub", children: [
1324
+ "smart 模式的磁盘事实:阶段板、六个产物、门记录、手模型评审与验收取证。",
1325
+ root === "" ? "" : ` 数据源 ${root}`
1326
+ ] }),
1327
+ /* @__PURE__ */ jsxRuntime.jsx(RouteCard, { config: routeConfig, sessionId, onSaved: () => {
1328
+ void load(selected);
1329
+ } }),
1330
+ unknownWorkspace === null ? null : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-note", "data-probe": "unknown-workspace", children: unknownWorkspace }),
1331
+ legacyCount === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-note", "data-probe": "legacy-count", children: `另有 ${legacyCount} 个任务在兼容根(旧任务仍可读、可继续、可归档)。` }),
1332
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-actions", children: [
1333
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-chip-row", children: [
1334
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-chip": "tasks", children: `任务 ${tasks.length}` }),
1335
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-chip": "root", title: root, children: `根目录 ${root === "" ? "…" : root}` }),
1336
+ task === null ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1337
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-chip": "phase", children: `阶段 ${PHASE_LABEL[task.state.phase] ?? task.state.phase}` }),
1338
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-chip": "gate", children: `开门 ${task.state.awaitingGate ?? "无"}` }),
1339
+ /* @__PURE__ */ jsxRuntime.jsx(
1340
+ "span",
1341
+ {
1342
+ className: "fly-chip",
1343
+ "data-chip": "rework",
1344
+ ...reworkBudgetOf(task.state).used >= reworkBudgetOf(task.state).cap ? { "data-tone": "warn" } : {},
1345
+ title: `本阶段(${PHASE_LABEL[task.state.phase] ?? task.state.phase})返工额度:已用 ${reworkBudgetOf(task.state).used} / 上限 ${reworkBudgetOf(task.state).cap}${reworkBudgetOf(task.state).grants === 0 ? "" : `(含你加额的 ${reworkBudgetOf(task.state).grants} 次)`}${reworkBudgetOf(task.state).used >= reworkBudgetOf(task.state).cap ? "。额度已用尽:下一道门会把「是否继续重做」交给你" : ""}`,
1346
+ children: `返工 ${reworkBudgetOf(task.state).used}/${reworkBudgetOf(task.state).cap}`
1347
+ }
1348
+ ),
1349
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-chip": "consults", children: `评审 ${task.consults.length}` }),
1350
+ /* @__PURE__ */ jsxRuntime.jsx(
1351
+ "span",
1352
+ {
1353
+ className: "fly-chip",
1354
+ "data-chip": "briefing",
1355
+ ...briefingStatus(task) === "stale" || briefingStatus(task) === "failed" ? { "data-tone": "warn" } : {},
1356
+ title: `briefing(${PHASE_LABEL[task.state.phase] ?? task.state.phase}):${BRIEFING_LABEL[briefingStatus(task)] ?? briefingStatus(task)}${briefingRoute(task) === null ? "" : ` · ${briefingRoute(task)}`}`,
1357
+ children: `briefing ${BRIEFING_LABEL[briefingStatus(task)] ?? briefingStatus(task)}`
1358
+ }
1359
+ ),
1360
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-chip": "evidence", children: `取证 ${task.verifications.length}` }),
1361
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-chip": "archived", children: task.state.archivedAt === void 0 ? "未归档" : `归档 ${formatTime(task.state.archivedAt)}` }),
1362
+ pendingDecisionsOf(task) === null ? decidedCount(task) === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-chip": "decisions", children: `已裁决 ${decidedCount(task)}` }) : /* @__PURE__ */ jsxRuntime.jsx(
1363
+ "span",
1364
+ {
1365
+ className: "fly-chip",
1366
+ "data-chip": "decisions",
1367
+ "data-tone": "warn",
1368
+ title: pendingDecisionsOf(task)?.headings.join(" / "),
1369
+ children: `待裁决 ${pendingDecisionsOf(task)?.items}`
1370
+ }
1371
+ ),
1372
+ task.routes.drifted ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-chip": "route", "data-tone": "warn", children: `路由不一致 ${task.routes.executed ?? ""}` }) : task.routes.executed === null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-chip": "route", children: "执行路由未知" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-chip": "route", "data-tone": "ok", children: `路由一致 ${task.routes.executed}` })
1373
+ ] })
1374
+ ] }),
1375
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "fly-btn", type: "button", onClick: () => {
1376
+ void load(selected);
1377
+ }, disabled: busy, children: busy ? "读取…" : "刷新" })
1378
+ ] }),
1379
+ notice === null ? null : notice.kind === "missing" ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "fly-empty", "data-probe": "empty", children: [
1380
+ notice.message,
1381
+ " ",
1382
+ "它会按 脑暴 → 设计 → 计划 → 验收设计 → 执行 → 验收 推进,每个阶段过门前都要问过你。"
1383
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-note-error", "data-probe": "host-error", children: notice.message }),
1384
+ tasks.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "fly-card", "data-probe": "tasks", children: [
1385
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "任务" }),
1386
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-chip-row", children: tasks.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
1387
+ "button",
1388
+ {
1389
+ className: "fly-btn",
1390
+ type: "button",
1391
+ "data-task": entry.id,
1392
+ "data-selected": entry.id === selected ? "true" : "false",
1393
+ onClick: () => {
1394
+ setSelected(entry.id);
1395
+ void load(entry.id);
1396
+ },
1397
+ children: `${entry.title === "" ? entry.id : entry.title} · ${PHASE_LABEL[entry.phase] ?? entry.phase}${entry.pendingDecisions != null ? ` · 待裁决 ${entry.pendingDecisions.items}` : entry.awaitingGate === null ? "" : ` · ${entry.awaitingGate}`}${reworkExhausted(entry) ? " · 额度用尽" : ""}`
1398
+ },
1399
+ entry.id
1400
+ )) })
1401
+ ] }),
1402
+ task === null ? null : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1403
+ task.routes.drifted ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "fly-note-error", "data-probe": "route-warning", children: [
1404
+ `路由告警:执行报告写的是 ${task.routes.executed ?? "未知路由"},`,
1405
+ `手模型评审记录是 ${hand.length === 0 ? "(无)" : hand.join("、")}。`,
1406
+ "设计由一个模型评审、由另一个模型执行 —— 请人工确认这是有意为之。"
1407
+ ] }) : null,
1408
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "fly-card", "data-probe": "board", children: [
1409
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "阶段板" }),
1410
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-grid", children: (task.board ?? BOARD).map((entry) => {
1411
+ const status = phaseStatus(entry, task);
1412
+ const target = entry.id === "done" ? "summary" : entry.id;
1413
+ const artifact = target === "summary" ? task.summary ?? void 0 : task.artifacts[target];
1414
+ const consult = task.consults.find((record) => record.phase === entry.id);
1415
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-card", "data-phase": entry.id, "data-status": status, children: [
1416
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fly-chip-row", children: [
1417
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", children: entry.gate ?? "—" }),
1418
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-chip", "data-phase-status": status, children: entry.label }),
1419
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-tag", "data-phase-status": status, children: STATUS_LABEL[status] }),
1420
+ entry.id === task.state.phase && pendingDecisionsOf(task) != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-tag", "data-chip": "decisions", children: `待裁决 ${pendingDecisionsOf(task)?.items}` }) : null,
1421
+ entry.id === task.state.phase && rulingsCountOf(task) > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-tag", "data-chip": "rulings", title: "模型替你定过的事,收尾总结里会逐条列", children: `自裁 ${rulingsCountOf(task)}` }) : null,
1422
+ entry.id === task.state.phase && reworkBudgetOf(task.state).used >= reworkBudgetOf(task.state).cap ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fly-tag", "data-chip": "rework", children: "额度用尽" }) : null
1423
+ ] }),
1424
+ /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "fly-kv", children: [
1425
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "产物" }),
1426
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: artifact === void 0 ? "—" : /* @__PURE__ */ jsxRuntime.jsx(
1427
+ "button",
1428
+ {
1429
+ className: "fly-btn",
1430
+ type: "button",
1431
+ "data-artifact-jump": entry.id,
1432
+ title: "跳到产物查看器",
1433
+ onClick: () => jump(target),
1434
+ children: `${artifact.file} · ${formatBytes(artifact.bytes)}`
1435
+ }
1436
+ ) }),
1437
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: "评审" }),
1438
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: consult === void 0 ? "—" : formatRoute(consult.provider, consult.model) })
1439
+ ] })
1440
+ ] }, entry.id);
1441
+ }) })
1442
+ ] }),
1443
+ /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "fly-card", "data-probe": "artifacts", ref: artifactsRef, children: [
1444
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "产物查看器" }),
1445
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fly-chip-row", children: artifactTabs(task).map((phase) => /* @__PURE__ */ jsxRuntime.jsx(
1446
+ "button",
1447
+ {
1448
+ className: "fly-btn",
1449
+ type: "button",
1450
+ "data-artifact": phase,
1451
+ "data-selected": phase === tab ? "true" : "false",
1452
+ "data-focused": phase === jumped ? "true" : "false",
1453
+ onClick: () => {
1454
+ setTab(phase);
1455
+ setJumped(null);
1456
+ },
1457
+ children: ARTIFACT_LABEL[phase] ?? phase
1458
+ },
1459
+ phase
1460
+ )) }),
1461
+ tab !== null && artifactText(task, tab) !== null ? /* @__PURE__ */ jsxRuntime.jsx(MarkdownView, { markdown: artifactText(task, tab) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-empty", children: "这个任务还没有产物。" })
1462
+ ] }),
1463
+ /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "fly-card", "data-probe": "gates", children: [
1464
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "门记录" }),
1465
+ task.gates.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-empty", children: "还没有任何门记录(每个设计阶段结束时由用户确认)。" }) : /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "fly-table", children: [
1466
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1467
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "门" }),
1468
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "时间" }),
1469
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "确认人" }),
1470
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "备注" })
1471
+ ] }) }),
1472
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: task.gates.map((record, index) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1473
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: record.gate ?? "—" }),
1474
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: formatTime(record.at) }),
1475
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: record.by ?? "—" }),
1476
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: record.note === void 0 || record.note === "" ? "—" : record.note })
1477
+ ] }, `${record.gate ?? "gate"}-${index}`)) })
1478
+ ] }),
1479
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { marginTop: "16px" }, children: "返工历史" }),
1480
+ task.history.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-empty", children: "没有被退回重做的产物。" }) : /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "fly-table", children: [
1481
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1482
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "时间" }),
1483
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "归档产物" })
1484
+ ] }) }),
1485
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: task.history.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1486
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: formatTime(entry.at) }),
1487
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: `${entry.file} · ${formatBytes(entry.bytes)}` })
1488
+ ] }, entry.file)) })
1489
+ ] })
1490
+ ] }),
1491
+ /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "fly-card", "data-probe": "consults", children: [
1492
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "手模型评审" }),
1493
+ task.consults.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-empty", children: "还没有评审记录(每个设计阶段过门前都要请手模型评审一次)。" }) : /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "fly-table", children: [
1494
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1495
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "阶段" }),
1496
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "时间" }),
1497
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "路由" }),
1498
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "问题" })
1499
+ ] }) }),
1500
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: task.consults.map((record) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1501
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: record.phase }),
1502
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: formatTime(record.at) }),
1503
+ /* @__PURE__ */ jsxRuntime.jsx("td", { "data-route": formatRoute(record.provider, record.model), children: formatRoute(record.provider, record.model) }),
1504
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: record.question === "" ? "—" : record.question })
1505
+ ] }, record.phase)) })
1506
+ ] })
1507
+ ] }),
1508
+ /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "fly-card", "data-probe": "evidence", children: [
1509
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "验收取证" }),
1510
+ task.verifications.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "fly-empty", children: "还没有取证记录(验收阶段由只读子代理逐条核对验收项)。" }) : /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "fly-table", children: [
1511
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1512
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "时间" }),
1513
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "证据" }),
1514
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "路由" })
1515
+ ] }) }),
1516
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: task.verifications.map((row) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1517
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: formatTime(row.at) }),
1518
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: `${row.file}${row.missing ? "(文件缺失)" : ""}` }),
1519
+ /* @__PURE__ */ jsxRuntime.jsx("td", { children: formatRoute(row.provider, row.model) })
1520
+ ] }, row.file)) })
1521
+ ] })
1522
+ ] })
1523
+ ] })
1524
+ ] });
1525
+ }
1526
+ function apply(ctx) {
1527
+ browserContext = ctx;
1528
+ ctx.effect(() => ctx.sidebarRightTabs.register({
1529
+ id: PACKAGE,
1530
+ kind: KIND,
1531
+ title: () => TAB_TITLE,
1532
+ guide: [{
1533
+ id: "open",
1534
+ order: GUIDE_ORDER,
1535
+ title: () => TAB_TITLE,
1536
+ description: () => TAB_DESCRIPTION,
1537
+ icon: SmartMark
1538
+ }]
1539
+ }), "fly-smart-panel: drawer type");
1540
+ ctx.effect(() => {
1541
+ const tag = document.createElement("style");
1542
+ tag.setAttribute("data-plugin", PACKAGE);
1543
+ tag.textContent = WORKBENCH_CSS;
1544
+ document.head.appendChild(tag);
1545
+ return () => {
1546
+ tag.remove();
1547
+ };
1548
+ }, "fly-smart-panel: workbench stylesheet");
1549
+ ctx.effect(() => ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register(
1550
+ { name: "sidebar.right.pane.tab", key: PACKAGE, inject: () => ({}) },
1551
+ SmartPanel
1552
+ )), "fly-smart-panel: drawer body");
1553
+ }
1554
+ function SmartMark({ size = 18, active = false }) {
1555
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "fly-mark", "data-active": active ? "true" : "false", width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
1556
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 6h7M3 12h12M3 18h7", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" }),
1557
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 4v6", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round" }),
1558
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "18", cy: "12", r: "2.2", stroke: "currentColor", strokeWidth: "1.6" })
1559
+ ] });
1560
+ }
1561
+ exports.SmartMark = SmartMark;
1562
+ exports.SmartPanel = SmartPanel;
1563
+ exports.apply = apply;
1564
+ exports.inject = inject;
1565
+ exports.name = name;
1566
+
1567
+ return module.exports;
1568
+ },
1569
+ });