@clipbus/plugin-sdk 0.8.5 → 0.8.7

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.
@@ -26,6 +26,7 @@ __export(index_exports, {
26
26
  WINDOW_SET_HEIGHT_METHOD: () => WINDOW_SET_HEIGHT_METHOD,
27
27
  applyThemeCssVars: () => applyThemeCssVars,
28
28
  clampWidth: () => clampWidth,
29
+ classifyWireSections: () => classifyWireSections,
29
30
  computeWirePayloads: () => computeWirePayloads,
30
31
  createPreviewWorkbench: () => createPreviewWorkbench,
31
32
  defaultDarkTheme: () => defaultDarkTheme,
@@ -34,6 +35,7 @@ __export(index_exports, {
34
35
  groupScenariosByMode: () => groupScenariosByMode,
35
36
  injectWire: () => injectWire,
36
37
  installFakeHost: () => installFakeHost,
38
+ parsePayloadJson: () => parsePayloadJson,
37
39
  previewThemePresets: () => previewThemePresets,
38
40
  resolveViewportWidth: () => resolveViewportWidth
39
41
  });
@@ -41,42 +43,72 @@ module.exports = __toCommonJS(index_exports);
41
43
 
42
44
  // src/preview/styles.ts
43
45
  var previewStyles = `
44
- /* \u2500\u2500 CSS variable defaults (graphite dark) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
46
+ @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
47
+
48
+ /* \u2500\u2500 Wire Bench chrome tokens (stable \u2014 independent of native theme preset) \u2500 */
49
+ /* Intentional decoupling: the workbench chrome is a tool, not a specimen. */
50
+ /* The electric violet signal colour (#7B61FF) is chosen to not clash with */
51
+ /* any of the 4 native theme accents (teal / orange / blue / amber). */
52
+ .cbp-wb {
53
+ --bench: #14161D;
54
+ --bench-2: #1A1D26;
55
+ --bench-3: #222634;
56
+ --bench-line: rgba(152,164,196,.12);
57
+ --bench-line-2: rgba(152,164,196,.20);
58
+ --bench-ink: #E8EAF2;
59
+ --bench-ink-dim: #969EB2;
60
+ --bench-ink-faint: #646B7E;
61
+ --bench-signal: #7B61FF;
62
+ --bench-signal-2: #B9A8FF;
63
+ --bench-signal-soft: rgba(123,97,255,.16);
64
+ }
65
+
66
+ /* \u2500\u2500 Card-side theme vars (graphite defaults; applyTheme() overwrites these) \u2500 */
67
+ /* These track the active preset so the specimen card (card-shell / buttons) */
68
+ /* still recolours when the theme picker changes \u2014 native fidelity for the */
69
+ /* card interior. The chrome itself never reads these for its own background. */
45
70
  .cbp-wb {
46
- --cbp-wb-backdrop: #16181C;
47
- --cbp-wb-accent: #43C6AC;
71
+ --cbp-wb-backdrop: #16181C;
72
+ --cbp-wb-accent: #43C6AC;
48
73
  --cbp-wb-surface-elevated: rgba(35,37,42,.78);
49
- --cbp-wb-border: rgba(255,255,255,.10);
50
- --cbp-wb-text: #F3F4F6;
51
- --cbp-wb-text-secondary: #C2C6CF;
74
+ --cbp-wb-border: rgba(255,255,255,.10);
75
+ --cbp-wb-text: #F3F4F6;
76
+ --cbp-wb-text-secondary: #C2C6CF;
52
77
  }
53
78
 
54
- /* \u2500\u2500 Root chrome \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
79
+ /* \u2500\u2500 Root chrome \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
80
+ /* Background uses --bench (stable), not --cbp-wb-backdrop (theme-driven). */
81
+ /* Blueprint grid is a visual signature of the instrument chrome. */
55
82
  .cbp-wb {
56
83
  min-height: 100%;
57
- padding: 24px;
58
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
59
- font-size: 13px;
84
+ padding: 28px 26px 34px;
85
+ font-family: "Space Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
86
+ font-size: 14px;
60
87
  box-sizing: border-box;
61
- color: var(--cbp-wb-text);
62
- background: var(--cbp-wb-backdrop);
88
+ color: var(--bench-ink);
89
+ background:
90
+ linear-gradient(var(--bench-line) 1px, transparent 1px),
91
+ linear-gradient(90deg, var(--bench-line) 1px, transparent 1px),
92
+ var(--bench);
93
+ background-size: 32px 32px, 32px 32px, auto;
94
+ background-position: -1px -1px, -1px -1px, 0 0;
95
+ -webkit-font-smoothing: antialiased;
63
96
  }
64
97
 
65
98
  *, .cbp-wb * {
66
99
  box-sizing: border-box;
67
100
  }
68
101
 
69
- /* \u2500\u2500 Theme-aware scrollbars \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
70
- /* The default OS / WebKit scrollbar is an opaque bar (white on dark themes)
71
- that reads as jarring. Restyle every scroll area under the workbench \u2014 chrome
72
- (log panel) AND plugin content inside the slot \u2014 into a thin, translucent,
73
- theme-synced overlay that mirrors the native host's subtle scrollbars. The
74
- --cbp-wb-* vars cascade from the workbench root into the plugin slot, so one
75
- rule set covers both surfaces and recolours automatically on theme switch. */
102
+ /* \u2500\u2500 Theme-aware scrollbars \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
103
+ /* Restyle every scroll area \u2014 chrome AND plugin content inside the slot \u2014 */
104
+ /* into a thin, translucent bench-toned overlay. Uses bench-ink-dim (stable) */
105
+ /* rather than --cbp-wb-text-secondary so the scrollbar doesn't recolour */
106
+ /* when the theme switches (intentional \u2014 chrome palette is stable). */
107
+ /* The --cbp-wb-* vars still cascade into the plugin slot for its own use. */
76
108
  .cbp-wb,
77
109
  .cbp-wb * {
78
110
  scrollbar-width: thin;
79
- scrollbar-color: color-mix(in srgb, var(--cbp-wb-text-secondary) 30%, transparent) transparent;
111
+ scrollbar-color: color-mix(in srgb, var(--bench-ink-dim) 30%, transparent) transparent;
80
112
  }
81
113
 
82
114
  .cbp-wb ::-webkit-scrollbar {
@@ -89,14 +121,14 @@ var previewStyles = `
89
121
  }
90
122
 
91
123
  .cbp-wb ::-webkit-scrollbar-thumb {
92
- background-color: color-mix(in srgb, var(--cbp-wb-text-secondary) 30%, transparent);
124
+ background-color: color-mix(in srgb, var(--bench-ink-dim) 30%, transparent);
93
125
  border-radius: 999px;
94
126
  border: 2px solid transparent;
95
127
  background-clip: padding-box;
96
128
  }
97
129
 
98
130
  .cbp-wb ::-webkit-scrollbar-thumb:hover {
99
- background-color: color-mix(in srgb, var(--cbp-wb-text-secondary) 50%, transparent);
131
+ background-color: color-mix(in srgb, var(--bench-ink-dim) 50%, transparent);
100
132
  background-clip: padding-box;
101
133
  }
102
134
 
@@ -104,91 +136,489 @@ var previewStyles = `
104
136
  background: transparent;
105
137
  }
106
138
 
107
- /* \u2500\u2500 Controls bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
108
- .cbp-wb__controls {
139
+ /* \u2500\u2500 Header: brand wordmark + controls console \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
140
+ .cbp-wb__header {
109
141
  display: flex;
110
- gap: 12px;
142
+ justify-content: space-between;
143
+ align-items: flex-end;
144
+ gap: 24px;
111
145
  flex-wrap: wrap;
146
+ margin-bottom: 16px;
147
+ }
148
+
149
+ .cbp-wb__brand {
150
+ display: flex;
151
+ flex-direction: column;
152
+ gap: 5px;
153
+ }
154
+
155
+ .cbp-wb__brand-sn {
156
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
157
+ font-size: 11.5px;
158
+ letter-spacing: .16em;
159
+ color: var(--bench-ink-faint);
160
+ text-transform: uppercase;
161
+ }
162
+
163
+ .cbp-wb__brand-mark {
164
+ font-size: 28px;
165
+ font-weight: 700;
166
+ letter-spacing: -.01em;
167
+ line-height: 1;
168
+ }
169
+
170
+ .cbp-wb__brand-mark b { color: var(--bench-signal-2); }
171
+ .cbp-wb__brand-mark span { color: var(--bench-ink); }
172
+
173
+ /* \u2500\u2500 Controls console (Surface / Scenario / Theme / Width) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
174
+ .cbp-wb__controls {
175
+ display: flex;
176
+ gap: 11px;
112
177
  align-items: flex-end;
113
- margin-bottom: 20px;
178
+ flex-wrap: wrap;
114
179
  }
115
180
 
116
181
  .cbp-wb__control {
117
- display: grid;
182
+ display: flex;
183
+ flex-direction: column;
118
184
  gap: 6px;
119
185
  }
120
186
 
121
187
  .cbp-wb__control-label {
188
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
122
189
  font-size: 11px;
123
- font-weight: 700;
124
- letter-spacing: 0.08em;
190
+ letter-spacing: .14em;
125
191
  text-transform: uppercase;
126
- color: var(--cbp-wb-text-secondary);
192
+ color: var(--bench-ink-faint);
127
193
  }
128
194
 
195
+ /* Scenario + Theme selects */
129
196
  .cbp-wb__control select,
130
197
  .cbp-wb__scenario-select {
131
- min-width: 160px;
132
- padding: 10px 12px;
133
- border-radius: 12px;
134
- border: 1px solid var(--cbp-wb-border);
135
- background: color-mix(in srgb, var(--cbp-wb-backdrop) 70%, transparent);
136
- color: var(--cbp-wb-text);
198
+ background: var(--bench-2);
199
+ border: 1px solid var(--bench-line);
200
+ border-radius: 11px;
201
+ color: var(--bench-ink);
202
+ font: inherit;
137
203
  font-size: 13px;
138
- font-family: inherit;
204
+ padding: 8px 13px;
205
+ min-width: 158px;
139
206
  }
140
207
 
141
- /* \u2500\u2500 Width slider \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
208
+ /* Width slider */
142
209
  .cbp-wb__width-slider {
143
- width: 140px;
144
- accent-color: var(--cbp-wb-accent);
210
+ accent-color: var(--bench-signal);
211
+ width: 128px;
145
212
  cursor: pointer;
146
213
  }
147
214
 
148
- /* \u2500\u2500 Canvas layout (host frame on top, native-call log below) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
149
- .cbp-wb__canvas {
215
+ /* Current width value shown inline with the label */
216
+ .cbp-wb__width-val {
217
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
218
+ font-size: 12px;
219
+ color: var(--bench-ink-dim);
220
+ }
221
+
222
+ /* \u2500\u2500 Horizontal rule separating header from stage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
223
+ .cbp-wb__rule {
224
+ height: 1px;
225
+ background: var(--bench-line);
226
+ margin: 0 0 22px;
227
+ }
228
+
229
+ /* \u2500\u2500 Stage: workspace (hero) left + dock right \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
230
+ .cbp-wb__stage {
150
231
  display: flex;
151
- flex-direction: column;
152
- gap: 20px;
153
- align-items: center;
232
+ gap: 22px;
233
+ align-items: flex-start;
154
234
  }
155
235
 
156
- /* Host frame hugs the fixed-width viewport (no flex:1 stretch) so a wide
157
- browser window leaves neutral backdrop on the sides instead of a large
158
- empty framed area \u2014 the centered, native-like card the viewport expects. */
159
- .cbp-wb__host-frame {
236
+ /* \u2500\u2500 Workspace \u2014 hero, fills the left column \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
237
+ .cbp-wb__workspace {
238
+ flex: 1 1 auto;
160
239
  min-width: 0;
161
- padding: 16px;
162
- border-radius: 16px;
163
- background: color-mix(in srgb, var(--cbp-wb-surface-elevated) 40%, transparent);
164
- border: 1px solid var(--cbp-wb-border);
165
240
  display: flex;
166
241
  flex-direction: column;
167
- align-items: center;
168
242
  }
169
243
 
170
- /* \u2500\u2500 Frame title \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
171
- .cbp-wb__frame-title {
244
+ .cbp-wb__ws-head {
172
245
  display: flex;
173
- justify-content: space-between;
174
- gap: 12px;
175
- margin-bottom: 12px;
246
+ align-items: baseline;
247
+ gap: 13px;
248
+ margin-bottom: 13px;
249
+ }
250
+
251
+ .cbp-wb__ws-kicker {
252
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
176
253
  font-size: 12px;
254
+ font-weight: 600;
255
+ letter-spacing: .2em;
256
+ color: var(--bench-signal-2);
257
+ }
258
+
259
+ .cbp-wb__ws-title {
260
+ font-size: 23px;
177
261
  font-weight: 700;
178
- letter-spacing: 0.04em;
179
- color: var(--cbp-wb-text-secondary);
262
+ letter-spacing: -.01em;
263
+ }
264
+
265
+ .cbp-wb__ws-sub {
266
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
267
+ font-size: 12px;
268
+ color: var(--bench-ink-faint);
269
+ }
270
+
271
+ /* Mode + scenario chip \u2014 right-aligned in the ws-head */
272
+ .cbp-wb__ws-chip {
273
+ margin-left: auto;
274
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
275
+ font-size: 12px;
276
+ color: var(--bench-ink-dim);
277
+ background: var(--bench-2);
278
+ border: 1px solid var(--bench-line);
279
+ border-radius: 9px;
280
+ padding: 6px 11px;
281
+ white-space: nowrap;
282
+ }
283
+
284
+ /* \u2500\u2500 Workspace surface \u2014 glass stage that holds the specimen card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
285
+ /* min-height fills the viewport column height (hero). The card is anchored */
286
+ /* at the top; excess space becomes the "growth floor" labelling the 800 cap. */
287
+ .cbp-wb__ws-surface {
288
+ position: relative;
289
+ border: 1px solid var(--bench-line);
290
+ border-radius: 18px;
291
+ background:
292
+ radial-gradient(120% 64% at 50% 0%, var(--bench-signal-soft), transparent 56%),
293
+ var(--bench-2);
294
+ min-height: calc(100vh - 150px);
295
+ padding: 34px 36px 18px;
296
+ display: flex;
297
+ flex-direction: column;
298
+ align-items: center;
299
+ gap: 12px;
300
+ overflow: auto;
301
+ }
302
+
303
+ /* Violet accent line across the top edge \u2014 signal that this is live */
304
+ .cbp-wb__ws-surface::before {
305
+ content: "";
306
+ position: absolute;
307
+ left: 0;
308
+ right: 0;
309
+ top: 0;
310
+ height: 2px;
311
+ background: linear-gradient(90deg, transparent, var(--bench-signal-soft), transparent);
312
+ }
313
+
314
+ /* Small tape label anchored top-left inside the surface */
315
+ .cbp-wb__ws-tape {
316
+ position: absolute;
317
+ top: 13px;
318
+ left: 20px;
319
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
320
+ font-size: 11px;
321
+ letter-spacing: .14em;
322
+ text-transform: uppercase;
323
+ color: var(--bench-ink-faint);
324
+ }
325
+
326
+ /* Wrapper that provides a positioning context for the "card internals" tag */
327
+ .cbp-wb__specimen-wrap {
328
+ position: relative;
329
+ margin-top: 20px;
330
+ }
331
+
332
+ /* Dimensions readout (WxH) below the specimen */
333
+ .cbp-wb__ws-dims {
334
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
335
+ font-size: 12px;
336
+ color: var(--bench-ink-faint);
337
+ letter-spacing: .06em;
338
+ }
339
+
340
+ /* Growth floor \u2014 pushes to the bottom of the surface, labels the 800 ceiling */
341
+ /* Turns the blank space below the card into meaningful information: */
342
+ /* "this gap is the room left before the native height cap". */
343
+ .cbp-wb__ws-floor {
344
+ margin-top: auto;
180
345
  width: 100%;
346
+ padding-top: 16px;
347
+ border-top: 1px dashed var(--bench-line-2);
348
+ display: flex;
349
+ justify-content: center;
350
+ gap: 10px;
351
+ align-items: center;
352
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
353
+ font-size: 11.5px;
354
+ letter-spacing: .07em;
355
+ color: var(--bench-ink-faint);
181
356
  }
182
357
 
183
- /* \u2500\u2500 Viewport shell: centers the viewport horizontally \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
184
- .cbp-wb__viewport-shell {
358
+ .cbp-wb__ws-floor-pip {
359
+ color: var(--bench-signal-2);
360
+ font-size: 13px;
361
+ }
362
+
363
+ /* \u2500\u2500 Dock \u2014 two attachment cards, position:sticky \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
364
+ /* Sticks to the top so the workspace can grow vertically without pushing */
365
+ /* the OUT card out of view. Right column is always in frame. */
366
+ .cbp-wb__dock {
367
+ flex: 0 0 470px;
368
+ position: sticky;
369
+ top: 8px;
370
+ align-self: flex-start;
371
+ max-height: calc(100vh - 54px);
372
+ overflow: auto;
185
373
  display: flex;
186
374
  flex-direction: column;
375
+ gap: 16px;
376
+ }
377
+
378
+ /* \u2500\u2500 Attachment card (IN and OUT share this shell) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
379
+ .cbp-wb__att {
380
+ position: relative;
381
+ background: var(--bench-2);
382
+ border: 1px solid var(--bench-line);
383
+ border-radius: 18px;
384
+ display: flex;
385
+ flex-direction: column;
386
+ overflow: hidden;
387
+ }
388
+
389
+ .cbp-wb__att-head {
390
+ display: flex;
391
+ align-items: center;
392
+ gap: 11px;
393
+ padding: 14px 17px;
394
+ border-bottom: 1px solid var(--bench-line);
395
+ flex: none;
396
+ }
397
+
398
+ .cbp-wb__att-code {
399
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
400
+ font-size: 12px;
401
+ font-weight: 600;
402
+ letter-spacing: .16em;
403
+ color: var(--bench-signal-2);
404
+ }
405
+
406
+ .cbp-wb__att-title {
407
+ font-size: 15.5px;
408
+ font-weight: 700;
409
+ }
410
+
411
+ .cbp-wb__att-dir {
412
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
413
+ font-size: 11.5px;
414
+ color: var(--bench-ink-faint);
415
+ }
416
+
417
+ .cbp-wb__att-meta {
418
+ margin-left: auto;
419
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
420
+ font-size: 12px;
421
+ color: var(--bench-ink-dim);
422
+ }
423
+
424
+ .cbp-wb__att-scroll {
425
+ overflow: auto;
426
+ padding: 7px 0;
427
+ }
428
+
429
+ .cbp-wb__att--in .cbp-wb__att-scroll { max-height: 474px; }
430
+ .cbp-wb__att--out .cbp-wb__att-scroll { max-height: 320px; }
431
+
432
+ /* \u2500\u2500 Wire Input sections (IN panel \u2014 host\u2192plugin) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
433
+ /* Section dividers; first child has no top border. */
434
+ .cbp-wb__wire-sec {
435
+ border-top: 1px solid var(--bench-line);
436
+ }
437
+
438
+ .cbp-wb__wire-sec:first-child {
439
+ border-top: 0;
440
+ }
441
+
442
+ /* Clickable header row: chevron + key + kind tag + collapsed summary */
443
+ .cbp-wb__wire-sec-head {
444
+ display: flex;
187
445
  align-items: center;
446
+ gap: 10px;
447
+ padding: 11px 17px;
448
+ cursor: pointer;
449
+ user-select: none;
450
+ }
451
+
452
+ .cbp-wb__wire-sec-chev {
453
+ font-size: 10px;
454
+ width: 10px;
455
+ color: var(--bench-ink-faint);
456
+ flex: none;
457
+ }
458
+
459
+ .cbp-wb__wire-sec--open .cbp-wb__wire-sec-chev {
460
+ color: var(--bench-signal-2);
461
+ }
462
+
463
+ .cbp-wb__wire-sec-key {
464
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
465
+ font-size: 13px;
466
+ font-weight: 600;
467
+ color: var(--bench-ink);
468
+ }
469
+
470
+ .cbp-wb__wire-sec-tag {
471
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
472
+ font-size: 10.5px;
473
+ color: var(--bench-ink-faint);
474
+ letter-spacing: .04em;
475
+ border: 1px solid var(--bench-line);
476
+ border-radius: 5px;
477
+ padding: 1px 6px;
478
+ }
479
+
480
+ /* Content sections are highlighted \u2014 these are the user-facing payloads */
481
+ .cbp-wb__wire-sec-tag--content {
482
+ color: var(--bench-signal-2);
483
+ border-color: color-mix(in srgb, var(--bench-signal) 35%, transparent);
484
+ }
485
+
486
+ .cbp-wb__wire-sec-sum {
487
+ margin-left: auto;
488
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
489
+ font-size: 11.5px;
490
+ color: var(--bench-ink-faint);
491
+ max-width: 44%;
492
+ overflow: hidden;
493
+ text-overflow: ellipsis;
494
+ white-space: nowrap;
495
+ }
496
+
497
+ /* Collapsible payload body \u2014 hidden until the section is open */
498
+ .cbp-wb__wire-sec-body {
499
+ display: none;
500
+ padding: 0 17px 14px 32px;
501
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
502
+ font-size: 13px;
503
+ line-height: 1.65;
504
+ white-space: pre;
505
+ color: var(--bench-ink);
506
+ overflow-x: auto;
507
+ }
508
+
509
+ .cbp-wb__wire-sec--open .cbp-wb__wire-sec-body {
510
+ display: block;
511
+ }
512
+
513
+ /* \u2500\u2500 Native Calls accordion (OUT panel \u2014 plugin\u2192host) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
514
+ /* Single-open: exactly one entry is expanded at a time. Newest entry auto- */
515
+ /* opens on arrival and collapses all prior entries. Clicking an open entry */
516
+ /* closes it; clicking a closed entry opens it and closes the rest. */
517
+ .cbp-wb__calls {
518
+ display: flex;
519
+ flex-direction: column;
188
520
  gap: 8px;
189
- width: 100%;
521
+ padding: 11px 15px;
522
+ }
523
+
524
+ /* Empty-state label shown before any calls arrive */
525
+ .cbp-wb__calls-empty {
526
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
527
+ font-size: 12px;
528
+ color: var(--bench-ink-faint);
529
+ padding: 12px 17px;
530
+ font-style: italic;
531
+ }
532
+
533
+ /* .cbp-wb__log-entry \u2014 ONE per native call (class name frozen by tests and */
534
+ /* native fidelity contract). Serves as the accordion item outer shell. */
535
+ .cbp-wb__log-entry {
536
+ background: var(--bench-3);
537
+ border: 1px solid var(--bench-line);
538
+ border-radius: 11px;
539
+ overflow: hidden;
540
+ }
541
+
542
+ /* Open state: signal-tinted border + soft glow */
543
+ .cbp-wb__log-entry--open {
544
+ border-color: color-mix(in srgb, var(--bench-signal) 42%, transparent);
545
+ box-shadow: 0 0 0 1px var(--bench-signal-soft);
546
+ }
547
+
548
+ /* Clickable top row: chevron + sequence number + method name + summary */
549
+ .cbp-wb__log-entry-top {
550
+ display: flex;
551
+ gap: 10px;
552
+ align-items: center;
553
+ padding: 10px 13px;
554
+ cursor: pointer;
555
+ user-select: none;
556
+ }
557
+
558
+ .cbp-wb__log-entry-chev {
559
+ font-size: 10px;
560
+ width: 10px;
561
+ color: var(--bench-ink-faint);
562
+ flex: none;
190
563
  }
191
564
 
565
+ .cbp-wb__log-entry--open .cbp-wb__log-entry-chev {
566
+ color: var(--bench-signal-2);
567
+ }
568
+
569
+ .cbp-wb__log-entry-seq {
570
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
571
+ font-size: 11px;
572
+ color: var(--bench-ink-faint);
573
+ }
574
+
575
+ /* .cbp-wb__log-entry-method \u2014 method name (class name frozen by tests). */
576
+ .cbp-wb__log-entry-method {
577
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
578
+ font-size: 13px;
579
+ font-weight: 600;
580
+ color: var(--bench-signal-2);
581
+ }
582
+
583
+ /* One-line payload summary visible in collapsed state; hidden when expanded */
584
+ .cbp-wb__log-entry-sum {
585
+ margin-left: auto;
586
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
587
+ font-size: 11.5px;
588
+ color: var(--bench-ink-faint);
589
+ max-width: 50%;
590
+ overflow: hidden;
591
+ text-overflow: ellipsis;
592
+ white-space: nowrap;
593
+ }
594
+
595
+ .cbp-wb__log-entry--open .cbp-wb__log-entry-sum {
596
+ display: none;
597
+ }
598
+
599
+ /* Collapsible payload body \u2014 shown only when the entry is open */
600
+ .cbp-wb__log-entry-body {
601
+ display: none;
602
+ padding: 0 13px 12px 32px;
603
+ font-family: "IBM Plex Mono", ui-monospace, Menlo, monospace;
604
+ font-size: 12.5px;
605
+ line-height: 1.6;
606
+ white-space: pre;
607
+ color: var(--bench-ink);
608
+ overflow-x: auto;
609
+ }
610
+
611
+ .cbp-wb__log-entry--open .cbp-wb__log-entry-body {
612
+ display: block;
613
+ }
614
+
615
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
616
+ /* FROZEN SPECIMEN GEOMETRY \u2014 pixel-identical to HEAD (native measurement */
617
+ /* parity). Class names, padding, radius, border, and overflow rules are all */
618
+ /* verbatim from the prior implementation. Only the surrounding layout */
619
+ /* changed (canvas \u2192 stage / workspace). DO NOT adjust values below. */
620
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
621
+
192
622
  /* Body clip box. NO border-radius of its own \u2014 the card shell (its parent)
193
623
  owns the rounding. A radius here would clip the card's own corners/border. */
194
624
  .cbp-wb__viewport {
@@ -203,17 +633,16 @@ var previewStyles = `
203
633
  transition: height 0.1s ease;
204
634
  }
205
635
 
206
- /* \u2500\u2500 Native card shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
207
- /* Geometry source: AttachmentRenderCardShell.swift:24-59 */
208
- /* 10 pt padding / 7 pt continuous radius / 1 pt accent border / elevated bg */
209
- /* Outer card: stacks the body (viewport) above the host action strip, like */
210
- /* native VStack { body; actions }. Width is set inline; height auto-wraps. */
636
+ /* Native card shell: 10 pt padding / 7 pt radius / 1 pt accent border / elevated bg */
637
+ /* Geometry source: AttachmentRenderCardShell.swift:24-59 */
638
+ /* Outer card stacks the body (viewport) above the host action strip: */
639
+ /* native VStack { body; actions }. Width is set inline; height auto-wraps. */
211
640
  .cbp-wb__card-shell {
212
641
  display: flex;
213
642
  flex-direction: column;
214
643
  gap: 8px; /* native cardSpacing: body \u2194 action strip */
215
644
  padding: 10px; /* native cardPadding */
216
- border-radius: 7px; /* native radius.panelInner (replaces the 20px viewport clip) */
645
+ border-radius: 7px; /* native radius.panelInner */
217
646
  /* Border tint = the attachment's tintColor (--cbp-wb-card-tint, set per
218
647
  scenario from scenario.accentHex), falling back to the theme accent \u2014
219
648
  mirrors native, where the card border uses record.tintColor. */
@@ -233,8 +662,8 @@ var previewStyles = `
233
662
  overflow-x: hidden;
234
663
  }
235
664
 
236
- /* \u2500\u2500 Host button strip (inside the card, below the body) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
237
- /* Native action row: HStack { buttons; Spacer } \u2192 left-aligned, compact. */
665
+ /* Host button strip (inside the card, below the body) */
666
+ /* Native action row: HStack { buttons; Spacer } \u2192 left-aligned, compact. */
238
667
  .cbp-wb__strip {
239
668
  display: flex;
240
669
  flex-wrap: wrap;
@@ -251,7 +680,7 @@ var previewStyles = `
251
680
  appearance: none;
252
681
  border: 1px solid var(--cbp-wb-border);
253
682
  border-radius: 999px;
254
- padding: 4px 10px; /* native actionPadding \u2248 3 v / 8 h \u2014 was 10/16 (too large) */
683
+ padding: 4px 10px; /* native actionPadding \u2248 3 v / 8 h */
255
684
  background: color-mix(in srgb, var(--cbp-wb-surface-elevated) 60%, transparent);
256
685
  color: var(--cbp-wb-text-secondary);
257
686
  font-size: 12px;
@@ -266,72 +695,33 @@ var previewStyles = `
266
695
  border-color: var(--cbp-wb-accent);
267
696
  }
268
697
 
269
- /* \u2500\u2500 Log panel (fixed-width right column) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
270
- .cbp-wb__log {
271
- width: 100%;
272
- max-width: 720px;
273
- padding: 14px 16px;
274
- border-radius: 16px;
275
- background: color-mix(in srgb, var(--cbp-wb-surface-elevated) 50%, transparent);
276
- border: 1px solid var(--cbp-wb-border);
277
- overflow: hidden;
278
- display: flex;
279
- flex-direction: column;
280
- gap: 0;
281
- }
282
-
283
- .cbp-wb__log-title {
284
- margin: 0 0 10px;
285
- font-size: 12px;
286
- font-weight: 800;
287
- letter-spacing: 0.08em;
288
- text-transform: uppercase;
289
- color: var(--cbp-wb-text);
290
- }
291
-
292
- .cbp-wb__log-empty {
293
- font-size: 12px;
294
- color: color-mix(in srgb, var(--cbp-wb-text-secondary) 60%, transparent);
295
- font-style: italic;
296
- }
297
-
298
- .cbp-wb__log-list {
299
- list-style: none;
300
- margin: 0;
301
- padding: 0;
302
- display: flex;
303
- flex-direction: column;
304
- gap: 6px;
305
- max-height: 220px;
306
- overflow-y: auto;
307
- }
308
-
309
- .cbp-wb__log-entry {
310
- border-radius: 8px;
311
- padding: 8px 10px;
312
- background: color-mix(in srgb, var(--cbp-wb-surface-elevated) 60%, transparent);
313
- font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
314
- font-size: 11px;
315
- line-height: 1.5;
316
- word-break: break-all;
317
- }
318
-
319
- .cbp-wb__log-entry-method {
320
- font-weight: 700;
321
- color: var(--cbp-wb-accent);
322
- }
323
-
324
- .cbp-wb__log-entry-seq {
325
- font-weight: 400;
326
- color: var(--cbp-wb-text-secondary);
327
- margin-left: 6px;
328
- }
329
-
330
- .cbp-wb__log-entry-payload {
331
- margin-top: 2px;
332
- color: var(--cbp-wb-text-secondary);
333
- white-space: pre-wrap;
334
- overflow-wrap: anywhere;
698
+ /* \u2500\u2500 Responsive reflow \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
699
+ /* The side-by-side stage assumes a wide display: the renderer card runs up to
700
+ 900px and must sit beside the 470px dock. Below ~1335px the card would clip
701
+ against the dock. Stack instead \u2014 workspace on top at FULL width (the
702
+ specimen is never clipped), dock below as a row of the two attachment cards.
703
+ The dock drops its sticky behaviour here (it's no longer a side rail). */
704
+ @media (max-width: 1335px) {
705
+ .cbp-wb__stage {
706
+ flex-direction: column;
707
+ }
708
+ /* Surface no longer needs to fill the viewport column \u2014 let it wrap the card
709
+ so the dock below is reachable without a long scroll past empty space. */
710
+ .cbp-wb__ws-surface {
711
+ min-height: 420px;
712
+ }
713
+ .cbp-wb__dock {
714
+ position: static;
715
+ flex: none;
716
+ width: 100%;
717
+ max-height: none;
718
+ flex-direction: row;
719
+ align-items: flex-start;
720
+ }
721
+ .cbp-wb__att {
722
+ flex: 1 1 0;
723
+ min-width: 0;
724
+ }
335
725
  }
336
726
  `;
337
727
 
@@ -494,13 +884,25 @@ function renderChrome(root, scenarios, opts, handlers) {
494
884
  root.innerHTML = "";
495
885
  const wb = div("cbp-wb");
496
886
  root.appendChild(wb);
887
+ const header = div("cbp-wb__header");
888
+ wb.appendChild(header);
889
+ const brand = div("cbp-wb__brand");
890
+ const brandSn = document.createElement("div");
891
+ brandSn.className = "cbp-wb__brand-sn";
892
+ brandSn.textContent = "@clipbus/plugin-sdk \xB7 preview";
893
+ const brandMark = document.createElement("div");
894
+ brandMark.className = "cbp-wb__brand-mark";
895
+ brandMark.innerHTML = "<span>WIRE</span> <b>BENCH</b>";
896
+ brand.appendChild(brandSn);
897
+ brand.appendChild(brandMark);
898
+ header.appendChild(brand);
497
899
  const controls = div("cbp-wb__controls");
498
- wb.appendChild(controls);
900
+ header.appendChild(controls);
499
901
  const viewOptions = [
500
902
  { value: "attachmentRenderer", label: "Renderer" },
501
903
  { value: "action", label: "Action" }
502
904
  ];
503
- const viewSelect = buildSelect("cbp-wb-view", "View", viewOptions, selectedView, controls);
905
+ const viewSelect = buildSelect("cbp-wb-view", "Surface", viewOptions, selectedView, controls);
504
906
  const scenarioAreaWrapper = div("cbp-wb__control");
505
907
  controls.appendChild(scenarioAreaWrapper);
506
908
  const themeOptions = previewThemePresets.map((p) => ({ value: p.key, label: p.label }));
@@ -515,7 +917,11 @@ function renderChrome(root, scenarios, opts, handlers) {
515
917
  const widthLabel = document.createElement("label");
516
918
  widthLabel.className = "cbp-wb__control-label";
517
919
  widthLabel.htmlFor = "cbp-wb-width";
518
- widthLabel.textContent = `Width: ${currentWidth}px`;
920
+ widthLabel.textContent = "Width ";
921
+ const widthVal = document.createElement("span");
922
+ widthVal.className = "cbp-wb__width-val";
923
+ widthVal.textContent = `${currentWidth}px`;
924
+ widthLabel.appendChild(widthVal);
519
925
  widthControl.appendChild(widthLabel);
520
926
  const widthSlider = document.createElement("input");
521
927
  widthSlider.type = "range";
@@ -524,20 +930,34 @@ function renderChrome(root, scenarios, opts, handlers) {
524
930
  widthSlider.className = "cbp-wb__width-slider";
525
931
  widthControl.appendChild(widthSlider);
526
932
  controls.appendChild(widthControl);
527
- const canvas = div("cbp-wb__canvas");
528
- wb.appendChild(canvas);
529
- const hostFrame = div("cbp-wb__host-frame");
530
- canvas.appendChild(hostFrame);
531
- const frameTitle = div("cbp-wb__frame-title");
532
- const frameTitleLabel = span("cbp-wb__frame-title-label");
533
- const frameSizeLabel = span("cbp-wb__frame-title-size");
534
- frameTitle.appendChild(frameTitleLabel);
535
- frameTitle.appendChild(frameSizeLabel);
536
- hostFrame.appendChild(frameTitle);
537
- const viewportShell = div("cbp-wb__viewport-shell");
538
- hostFrame.appendChild(viewportShell);
933
+ const rule = div("cbp-wb__rule");
934
+ wb.appendChild(rule);
935
+ const stage = div("cbp-wb__stage");
936
+ wb.appendChild(stage);
937
+ const workspace = div("cbp-wb__workspace");
938
+ stage.appendChild(workspace);
939
+ const wsHead = div("cbp-wb__ws-head");
940
+ const wsKicker = span("cbp-wb__ws-kicker");
941
+ wsKicker.textContent = "PREVIEW";
942
+ const wsTitle = span("cbp-wb__ws-title");
943
+ wsTitle.textContent = "Workspace";
944
+ const wsSub = span("cbp-wb__ws-sub");
945
+ wsSub.textContent = "native-fidelity \xB7 grows with setHeight";
946
+ const wsChip = span("cbp-wb__ws-chip");
947
+ wsHead.appendChild(wsKicker);
948
+ wsHead.appendChild(wsTitle);
949
+ wsHead.appendChild(wsSub);
950
+ wsHead.appendChild(wsChip);
951
+ workspace.appendChild(wsHead);
952
+ const wsSurface = div("cbp-wb__ws-surface");
953
+ workspace.appendChild(wsSurface);
954
+ const wsTape = div("cbp-wb__ws-tape");
955
+ wsTape.textContent = "device under preview \xB7 specimen";
956
+ wsSurface.appendChild(wsTape);
957
+ const specimenWrap = div("cbp-wb__specimen-wrap");
958
+ wsSurface.appendChild(specimenWrap);
539
959
  const cardShell = div("cbp-wb__card-shell");
540
- viewportShell.appendChild(cardShell);
960
+ specimenWrap.appendChild(cardShell);
541
961
  const viewport = div("cbp-wb__viewport");
542
962
  cardShell.appendChild(viewport);
543
963
  const webview = div("cbp-wb__webview");
@@ -545,27 +965,69 @@ function renderChrome(root, scenarios, opts, handlers) {
545
965
  const slotEl = webview;
546
966
  const strip = div("cbp-wb__strip");
547
967
  cardShell.appendChild(strip);
548
- const logPanel = div("cbp-wb__log");
549
- canvas.appendChild(logPanel);
550
- const logTitle = document.createElement("p");
551
- logTitle.className = "cbp-wb__log-title";
552
- logTitle.textContent = "Native Calls";
553
- logPanel.appendChild(logTitle);
554
- const logEmpty = document.createElement("p");
555
- logEmpty.className = "cbp-wb__log-empty";
556
- logEmpty.textContent = "No calls yet.";
557
- logPanel.appendChild(logEmpty);
558
- const logList = document.createElement("ul");
559
- logList.className = "cbp-wb__log-list";
560
- logPanel.appendChild(logList);
968
+ const wsDims = div("cbp-wb__ws-dims");
969
+ wsSurface.appendChild(wsDims);
970
+ const wsFloor = div("cbp-wb__ws-floor");
971
+ const wsFloorPip = span("cbp-wb__ws-floor-pip");
972
+ wsFloorPip.textContent = "\u2195";
973
+ wsFloor.appendChild(wsFloorPip);
974
+ const wsFloorText = document.createTextNode(
975
+ " height follows the plugin \xB7 native ceiling \u2248 800px \xB7 content usually fits one screen"
976
+ );
977
+ wsFloor.appendChild(wsFloorText);
978
+ wsSurface.appendChild(wsFloor);
979
+ const dock = div("cbp-wb__dock");
980
+ stage.appendChild(dock);
981
+ const inAtt = div("cbp-wb__att cbp-wb__att--in");
982
+ dock.appendChild(inAtt);
983
+ const inHead = div("cbp-wb__att-head");
984
+ const inCode = span("cbp-wb__att-code");
985
+ inCode.textContent = "IN";
986
+ const inTitle = span("cbp-wb__att-title");
987
+ inTitle.textContent = "Wire Input";
988
+ const inDir = span("cbp-wb__att-dir");
989
+ inDir.textContent = "host \u2192 plugin";
990
+ const inMeta = span("cbp-wb__att-meta");
991
+ inMeta.textContent = "5 keys";
992
+ inHead.appendChild(inCode);
993
+ inHead.appendChild(inTitle);
994
+ inHead.appendChild(inDir);
995
+ inHead.appendChild(inMeta);
996
+ inAtt.appendChild(inHead);
997
+ const inputContainer = div("cbp-wb__att-scroll");
998
+ inAtt.appendChild(inputContainer);
999
+ const outAtt = div("cbp-wb__att cbp-wb__att--out");
1000
+ dock.appendChild(outAtt);
1001
+ const outHead = div("cbp-wb__att-head");
1002
+ const outCode = span("cbp-wb__att-code");
1003
+ outCode.textContent = "OUT";
1004
+ const outTitle = span("cbp-wb__att-title");
1005
+ outTitle.textContent = "Native Calls";
1006
+ const outDir = span("cbp-wb__att-dir");
1007
+ outDir.textContent = "plugin \u2192 host";
1008
+ const outMeta = span("cbp-wb__att-meta");
1009
+ outMeta.textContent = "0 calls";
1010
+ outHead.appendChild(outCode);
1011
+ outHead.appendChild(outTitle);
1012
+ outHead.appendChild(outDir);
1013
+ outHead.appendChild(outMeta);
1014
+ outAtt.appendChild(outHead);
1015
+ const outScroll = div("cbp-wb__att-scroll");
1016
+ outAtt.appendChild(outScroll);
1017
+ const callsEmpty = document.createElement("p");
1018
+ callsEmpty.className = "cbp-wb__calls-empty";
1019
+ callsEmpty.textContent = "No calls yet.";
1020
+ outScroll.appendChild(callsEmpty);
1021
+ const callsList = div("cbp-wb__calls");
1022
+ outScroll.appendChild(callsList);
561
1023
  let logSeq = 0;
562
1024
  function applyTheme(preset) {
563
- wb.style.setProperty("--cbp-wb-backdrop", preset.backdrop);
564
1025
  wb.style.setProperty("--cbp-wb-accent", preset.tokens.accent);
565
1026
  wb.style.setProperty("--cbp-wb-surface-elevated", preset.tokens.surfaceElevated);
566
1027
  wb.style.setProperty("--cbp-wb-border", preset.tokens.border);
567
1028
  wb.style.setProperty("--cbp-wb-text", preset.tokens.textPrimary);
568
1029
  wb.style.setProperty("--cbp-wb-text-secondary", preset.tokens.textSecondary);
1030
+ wb.style.setProperty("--cbp-wb-backdrop", preset.backdrop);
569
1031
  wb.dataset["themeScheme"] = preset.scheme;
570
1032
  wb.dataset["themeKey"] = preset.key;
571
1033
  }
@@ -578,9 +1040,9 @@ function renderChrome(root, scenarios, opts, handlers) {
578
1040
  function setViewportWidth(px) {
579
1041
  currentWidth = px;
580
1042
  cardShell.style.width = `${px}px`;
581
- widthLabel.textContent = `Width: ${px}px`;
1043
+ widthVal.textContent = `${px}px`;
582
1044
  widthSlider.value = String(px);
583
- frameSizeLabel.textContent = `${px} \xD7 ${currentBodyHeightLabel()}`;
1045
+ wsDims.textContent = `${px} \xD7 ${currentBodyHeightLabel()}`;
584
1046
  }
585
1047
  function updateWidthSlider(min, max, value) {
586
1048
  widthSlider.min = String(min);
@@ -607,30 +1069,66 @@ function renderChrome(root, scenarios, opts, handlers) {
607
1069
  clamped = Math.min(max, Math.max(min, height));
608
1070
  }
609
1071
  viewport.style.height = `${clamped}px`;
610
- frameSizeLabel.textContent = `${currentWidth} \xD7 ${clamped}`;
1072
+ wsDims.textContent = `${currentWidth} \xD7 ${clamped}`;
611
1073
  }
612
1074
  function appendLogEntry(method, payload) {
613
- logEmpty.style.display = "none";
1075
+ callsEmpty.style.display = "none";
614
1076
  logSeq += 1;
615
- const entry = document.createElement("li");
616
- entry.className = "cbp-wb__log-entry";
617
- const methodSpan = document.createElement("span");
618
- methodSpan.className = "cbp-wb__log-entry-method";
619
- methodSpan.textContent = method;
620
- const seqSpan = document.createElement("span");
621
- seqSpan.className = "cbp-wb__log-entry-seq";
622
- seqSpan.textContent = `#${logSeq}`;
623
- const payloadDiv = document.createElement("div");
624
- payloadDiv.className = "cbp-wb__log-entry-payload";
1077
+ let payloadText;
625
1078
  try {
626
- payloadDiv.textContent = JSON.stringify(payload, null, 2);
1079
+ payloadText = JSON.stringify(payload, null, 2);
627
1080
  } catch {
628
- payloadDiv.textContent = String(payload);
1081
+ payloadText = String(payload);
1082
+ }
1083
+ let summaryText;
1084
+ try {
1085
+ const compact = JSON.stringify(payload);
1086
+ summaryText = compact && compact.length > 60 ? compact.slice(0, 57) + "\u2026" : compact ?? "\u2014";
1087
+ } catch {
1088
+ summaryText = String(payload);
1089
+ }
1090
+ const entry = document.createElement("div");
1091
+ entry.className = "cbp-wb__log-entry cbp-wb__log-entry--open";
1092
+ const top = document.createElement("div");
1093
+ top.className = "cbp-wb__log-entry-top";
1094
+ const chev = span("cbp-wb__log-entry-chev");
1095
+ chev.textContent = "\u25BC";
1096
+ const seqEl = span("cbp-wb__log-entry-seq");
1097
+ seqEl.textContent = `#${logSeq}`;
1098
+ const methodSpan = span("cbp-wb__log-entry-method");
1099
+ methodSpan.textContent = method;
1100
+ const sumEl = span("cbp-wb__log-entry-sum");
1101
+ sumEl.textContent = summaryText;
1102
+ top.appendChild(chev);
1103
+ top.appendChild(seqEl);
1104
+ top.appendChild(methodSpan);
1105
+ top.appendChild(sumEl);
1106
+ const body = document.createElement("div");
1107
+ body.className = "cbp-wb__log-entry-body";
1108
+ body.textContent = payloadText;
1109
+ entry.appendChild(top);
1110
+ entry.appendChild(body);
1111
+ collapseAllLogEntries();
1112
+ callsList.insertBefore(entry, callsList.firstChild);
1113
+ outMeta.textContent = `${logSeq} call${logSeq === 1 ? "" : "s"}`;
1114
+ top.addEventListener("click", () => {
1115
+ const isOpen = entry.classList.contains("cbp-wb__log-entry--open");
1116
+ if (isOpen) {
1117
+ entry.classList.remove("cbp-wb__log-entry--open");
1118
+ chev.textContent = "\u25B6";
1119
+ } else {
1120
+ collapseAllLogEntries();
1121
+ entry.classList.add("cbp-wb__log-entry--open");
1122
+ chev.textContent = "\u25BC";
1123
+ }
1124
+ });
1125
+ }
1126
+ function collapseAllLogEntries() {
1127
+ for (const el of callsList.querySelectorAll(".cbp-wb__log-entry")) {
1128
+ el.classList.remove("cbp-wb__log-entry--open");
1129
+ const c = el.querySelector(".cbp-wb__log-entry-chev");
1130
+ if (c) c.textContent = "\u25B6";
629
1131
  }
630
- entry.appendChild(methodSpan);
631
- entry.appendChild(seqSpan);
632
- entry.appendChild(payloadDiv);
633
- logList.insertBefore(entry, logList.firstChild);
634
1132
  }
635
1133
  let activeButtons = [];
636
1134
  let activeDefaultButtonID = null;
@@ -690,10 +1188,11 @@ function renderChrome(root, scenarios, opts, handlers) {
690
1188
  });
691
1189
  scenarioAreaWrapper.appendChild(select);
692
1190
  }
693
- function updateFrameTitle() {
1191
+ function updateWorkspaceHead() {
694
1192
  const scenario = activeScenario();
695
- frameTitleLabel.textContent = scenario.mode === "attachmentRenderer" ? "Attachment Renderer" : "Draft Action";
696
- frameSizeLabel.textContent = `${currentWidth} \xD7 ${currentBodyHeightLabel()}`;
1193
+ const modeLabel = scenario.mode === "attachmentRenderer" ? "attachmentRenderer" : "action";
1194
+ wsChip.textContent = `${modeLabel} \xB7 ${scenario.label}`;
1195
+ wsDims.textContent = `${currentWidth} \xD7 ${currentBodyHeightLabel()}`;
697
1196
  }
698
1197
  function activateCurrentScenario() {
699
1198
  const scenario = activeScenario();
@@ -713,7 +1212,7 @@ function renderChrome(root, scenarios, opts, handlers) {
713
1212
  updateWidthSlider(min, max, initialWidth);
714
1213
  applyInitialHeight(scenario);
715
1214
  applyScenarioButtons(scenario);
716
- updateFrameTitle();
1215
+ updateWorkspaceHead();
717
1216
  syncQuery();
718
1217
  handlers.onActivate(scenario, selectedThemeKey);
719
1218
  }
@@ -751,6 +1250,7 @@ function renderChrome(root, scenarios, opts, handlers) {
751
1250
  }
752
1251
  return {
753
1252
  slotEl,
1253
+ inputContainer,
754
1254
  setViewportHeight,
755
1255
  setViewportWidth,
756
1256
  appendLogEntry,
@@ -955,6 +1455,132 @@ function applyThemeCssVars(target, snapshot) {
955
1455
  }
956
1456
  }
957
1457
 
1458
+ // src/preview/inputPanel.ts
1459
+ function classifyWireSections(payloads) {
1460
+ const sections = [];
1461
+ sections.push({
1462
+ key: "context",
1463
+ kind: "infra",
1464
+ defaultOpen: false,
1465
+ value: payloads.context
1466
+ });
1467
+ sections.push({
1468
+ key: "item",
1469
+ kind: "content",
1470
+ defaultOpen: true,
1471
+ value: payloads.item
1472
+ });
1473
+ if (payloads.mode === "attachmentRenderer") {
1474
+ sections.push({
1475
+ key: "attachment",
1476
+ kind: "content",
1477
+ defaultOpen: true,
1478
+ value: payloads.attachment
1479
+ });
1480
+ } else {
1481
+ sections.push({
1482
+ key: "draft",
1483
+ kind: "content",
1484
+ defaultOpen: true,
1485
+ value: payloads.draft
1486
+ });
1487
+ }
1488
+ sections.push({
1489
+ key: "theme",
1490
+ kind: "infra",
1491
+ defaultOpen: false,
1492
+ value: payloads.theme
1493
+ });
1494
+ sections.push({
1495
+ key: "locale",
1496
+ kind: "infra",
1497
+ defaultOpen: false,
1498
+ value: payloads.locale
1499
+ });
1500
+ return sections;
1501
+ }
1502
+ function parsePayloadJson(raw) {
1503
+ if (typeof raw !== "string") return { ok: false, raw };
1504
+ try {
1505
+ const value = JSON.parse(raw);
1506
+ return { ok: true, value };
1507
+ } catch {
1508
+ return { ok: false, raw };
1509
+ }
1510
+ }
1511
+ function sectionSummary(value) {
1512
+ if (value === void 0 || value === null) return "\u2014";
1513
+ if (typeof value !== "object") return String(value).slice(0, 48);
1514
+ try {
1515
+ const keys = Object.keys(value);
1516
+ const preview = keys.slice(0, 3).join(", ");
1517
+ return `{ ${preview}${keys.length > 3 ? ", \u2026" : ""} }`;
1518
+ } catch {
1519
+ return String(value);
1520
+ }
1521
+ }
1522
+ function formatSectionValue(value) {
1523
+ if (value === void 0) return "undefined";
1524
+ try {
1525
+ return JSON.stringify(
1526
+ value,
1527
+ (_key, v) => {
1528
+ if (_key === "payloadJson" && typeof v === "string") {
1529
+ const parsed = parsePayloadJson(v);
1530
+ return parsed.ok ? parsed.value : v;
1531
+ }
1532
+ return v;
1533
+ },
1534
+ 2
1535
+ );
1536
+ } catch {
1537
+ return String(value);
1538
+ }
1539
+ }
1540
+ function renderWireInput(containerEl, payloads) {
1541
+ containerEl.innerHTML = "";
1542
+ const sections = classifyWireSections(payloads);
1543
+ for (const section of sections) {
1544
+ const isOpen = section.defaultOpen;
1545
+ const sec = document.createElement("div");
1546
+ sec.className = "cbp-wb__wire-sec" + (isOpen ? " cbp-wb__wire-sec--open" : "");
1547
+ const head = document.createElement("div");
1548
+ head.className = "cbp-wb__wire-sec-head";
1549
+ const chev = document.createElement("span");
1550
+ chev.className = "cbp-wb__wire-sec-chev";
1551
+ chev.textContent = isOpen ? "\u25BC" : "\u25B6";
1552
+ const keySpan = document.createElement("span");
1553
+ keySpan.className = "cbp-wb__wire-sec-key";
1554
+ keySpan.textContent = section.key;
1555
+ const tag = document.createElement("span");
1556
+ tag.className = "cbp-wb__wire-sec-tag" + (section.kind === "content" ? " cbp-wb__wire-sec-tag--content" : "");
1557
+ tag.textContent = section.kind;
1558
+ const sum = document.createElement("span");
1559
+ sum.className = "cbp-wb__wire-sec-sum";
1560
+ sum.textContent = sectionSummary(section.value);
1561
+ head.appendChild(chev);
1562
+ head.appendChild(keySpan);
1563
+ head.appendChild(tag);
1564
+ head.appendChild(sum);
1565
+ const body = document.createElement("div");
1566
+ body.className = "cbp-wb__wire-sec-body";
1567
+ body.textContent = formatSectionValue(section.value);
1568
+ head.addEventListener("click", () => {
1569
+ const nowOpen = sec.classList.contains("cbp-wb__wire-sec--open");
1570
+ if (nowOpen) {
1571
+ sec.classList.remove("cbp-wb__wire-sec--open");
1572
+ chev.textContent = "\u25B6";
1573
+ } else {
1574
+ sec.classList.add("cbp-wb__wire-sec--open");
1575
+ chev.textContent = "\u25BC";
1576
+ }
1577
+ });
1578
+ sec.appendChild(head);
1579
+ sec.appendChild(body);
1580
+ containerEl.appendChild(sec);
1581
+ }
1582
+ }
1583
+
958
1584
  // src/preview/index.ts
959
1585
  function createPreviewWorkbench(root, opts) {
960
1586
  if (opts.scenarios.length === 0) {
@@ -979,6 +1605,7 @@ function createPreviewWorkbench(root, opts) {
979
1605
  let currentCleanup = null;
980
1606
  const {
981
1607
  slotEl,
1608
+ inputContainer,
982
1609
  setViewportHeight,
983
1610
  appendLogEntry,
984
1611
  applyButtons,
@@ -1008,12 +1635,14 @@ function createPreviewWorkbench(root, opts) {
1008
1635
  const payloads = computeWirePayloads(scenario, presetKey);
1009
1636
  injectWire(window, payloads);
1010
1637
  applyThemeCssVars(slotEl, payloads.theme);
1638
+ renderWireInput(inputContainer, payloads);
1011
1639
  currentCleanup = opts.mount(slotEl, { scenario });
1012
1640
  }
1013
1641
  function reinjectTheme(scenario, presetKey) {
1014
1642
  const payloads = computeWirePayloads(scenario, presetKey);
1015
1643
  injectWire(window, payloads);
1016
1644
  applyThemeCssVars(slotEl, payloads.theme);
1645
+ renderWireInput(inputContainer, payloads);
1017
1646
  }
1018
1647
  start();
1019
1648
  root["__cbpWbDestroy__"] = () => {