@clipbus/plugin-sdk 0.8.4 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1041 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/preview/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DEFAULT_THEME_KEY: () => DEFAULT_THEME_KEY,
24
+ PLUGIN_CALL_HANDLER_NAME: () => PLUGIN_CALL_HANDLER_NAME,
25
+ THEME_CSS_VAR_NAMES: () => THEME_CSS_VAR_NAMES,
26
+ WINDOW_SET_HEIGHT_METHOD: () => WINDOW_SET_HEIGHT_METHOD,
27
+ applyThemeCssVars: () => applyThemeCssVars,
28
+ clampWidth: () => clampWidth,
29
+ computeWirePayloads: () => computeWirePayloads,
30
+ createPreviewWorkbench: () => createPreviewWorkbench,
31
+ defaultDarkTheme: () => defaultDarkTheme,
32
+ defaultLightTheme: () => defaultLightTheme,
33
+ getThemePreset: () => getThemePreset,
34
+ groupScenariosByMode: () => groupScenariosByMode,
35
+ injectWire: () => injectWire,
36
+ installFakeHost: () => installFakeHost,
37
+ previewThemePresets: () => previewThemePresets,
38
+ resolveViewportWidth: () => resolveViewportWidth
39
+ });
40
+ module.exports = __toCommonJS(index_exports);
41
+
42
+ // src/preview/styles.ts
43
+ 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 */
45
+ .cbp-wb {
46
+ --cbp-wb-backdrop: #16181C;
47
+ --cbp-wb-accent: #43C6AC;
48
+ --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;
52
+ }
53
+
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 */
55
+ .cbp-wb {
56
+ min-height: 100%;
57
+ padding: 24px;
58
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
59
+ font-size: 13px;
60
+ box-sizing: border-box;
61
+ color: var(--cbp-wb-text);
62
+ background: var(--cbp-wb-backdrop);
63
+ }
64
+
65
+ *, .cbp-wb * {
66
+ box-sizing: border-box;
67
+ }
68
+
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. */
76
+ .cbp-wb,
77
+ .cbp-wb * {
78
+ scrollbar-width: thin;
79
+ scrollbar-color: color-mix(in srgb, var(--cbp-wb-text-secondary) 30%, transparent) transparent;
80
+ }
81
+
82
+ .cbp-wb ::-webkit-scrollbar {
83
+ width: 9px;
84
+ height: 9px;
85
+ }
86
+
87
+ .cbp-wb ::-webkit-scrollbar-track {
88
+ background: transparent;
89
+ }
90
+
91
+ .cbp-wb ::-webkit-scrollbar-thumb {
92
+ background-color: color-mix(in srgb, var(--cbp-wb-text-secondary) 30%, transparent);
93
+ border-radius: 999px;
94
+ border: 2px solid transparent;
95
+ background-clip: padding-box;
96
+ }
97
+
98
+ .cbp-wb ::-webkit-scrollbar-thumb:hover {
99
+ background-color: color-mix(in srgb, var(--cbp-wb-text-secondary) 50%, transparent);
100
+ background-clip: padding-box;
101
+ }
102
+
103
+ .cbp-wb ::-webkit-scrollbar-corner {
104
+ background: transparent;
105
+ }
106
+
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 {
109
+ display: flex;
110
+ gap: 12px;
111
+ flex-wrap: wrap;
112
+ align-items: flex-end;
113
+ margin-bottom: 20px;
114
+ }
115
+
116
+ .cbp-wb__control {
117
+ display: grid;
118
+ gap: 6px;
119
+ }
120
+
121
+ .cbp-wb__control-label {
122
+ font-size: 11px;
123
+ font-weight: 700;
124
+ letter-spacing: 0.08em;
125
+ text-transform: uppercase;
126
+ color: var(--cbp-wb-text-secondary);
127
+ }
128
+
129
+ .cbp-wb__control select,
130
+ .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);
137
+ font-size: 13px;
138
+ font-family: inherit;
139
+ }
140
+
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 */
142
+ .cbp-wb__width-slider {
143
+ width: 140px;
144
+ accent-color: var(--cbp-wb-accent);
145
+ cursor: pointer;
146
+ }
147
+
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 {
150
+ display: flex;
151
+ flex-direction: column;
152
+ gap: 20px;
153
+ align-items: center;
154
+ }
155
+
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 {
160
+ 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
+ display: flex;
166
+ flex-direction: column;
167
+ align-items: center;
168
+ }
169
+
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 {
172
+ display: flex;
173
+ justify-content: space-between;
174
+ gap: 12px;
175
+ margin-bottom: 12px;
176
+ font-size: 12px;
177
+ font-weight: 700;
178
+ letter-spacing: 0.04em;
179
+ color: var(--cbp-wb-text-secondary);
180
+ width: 100%;
181
+ }
182
+
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 {
185
+ display: flex;
186
+ flex-direction: column;
187
+ align-items: center;
188
+ gap: 8px;
189
+ width: 100%;
190
+ }
191
+
192
+ /* Body clip box. NO border-radius of its own \u2014 the card shell (its parent)
193
+ owns the rounding. A radius here would clip the card's own corners/border. */
194
+ .cbp-wb__viewport {
195
+ width: 100%;
196
+ /* Native hard ceiling (AttachmentRenderHeightConstants.ceiling = 800): the
197
+ body never grows past this even under the 'auto' content-driven policy. */
198
+ max-height: 800px;
199
+ /* At the ceiling, content SCROLLS inside the (fixed) card chrome \u2014 it does
200
+ not clip away or push the card taller. */
201
+ overflow-y: auto;
202
+ overflow-x: hidden;
203
+ transition: height 0.1s ease;
204
+ }
205
+
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. */
211
+ .cbp-wb__card-shell {
212
+ display: flex;
213
+ flex-direction: column;
214
+ gap: 8px; /* native cardSpacing: body \u2194 action strip */
215
+ padding: 10px; /* native cardPadding */
216
+ border-radius: 7px; /* native radius.panelInner (replaces the 20px viewport clip) */
217
+ /* Border tint = the attachment's tintColor (--cbp-wb-card-tint, set per
218
+ scenario from scenario.accentHex), falling back to the theme accent \u2014
219
+ mirrors native, where the card border uses record.tintColor. */
220
+ border: 1px solid color-mix(in srgb, var(--cbp-wb-card-tint, var(--cbp-wb-accent)) 26%, transparent);
221
+ background: var(--cbp-wb-surface-elevated);
222
+ /* No box-shadow per native spec */
223
+ }
224
+
225
+ .cbp-wb__webview {
226
+ width: 100%;
227
+ height: 100%;
228
+ /* Fixed/bounded body shorter than its content \u2192 scroll inside. (Only the one
229
+ element whose content actually exceeds its box scrolls, so there is never a
230
+ double scrollbar: in 'auto' the viewport scrolls; in fixed/bounded the
231
+ webview does.) */
232
+ overflow-y: auto;
233
+ overflow-x: hidden;
234
+ }
235
+
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. */
238
+ .cbp-wb__strip {
239
+ display: flex;
240
+ flex-wrap: wrap;
241
+ gap: 10px; /* native action HStack spacing */
242
+ justify-content: flex-start; /* left-aligned, not centered */
243
+ }
244
+
245
+ /* No buttons \u2192 no strip box, and the card's flex gap reserves nothing. */
246
+ .cbp-wb__strip:empty {
247
+ display: none;
248
+ }
249
+
250
+ .cbp-wb__button {
251
+ appearance: none;
252
+ border: 1px solid var(--cbp-wb-border);
253
+ border-radius: 999px;
254
+ padding: 4px 10px; /* native actionPadding \u2248 3 v / 8 h \u2014 was 10/16 (too large) */
255
+ background: color-mix(in srgb, var(--cbp-wb-surface-elevated) 60%, transparent);
256
+ color: var(--cbp-wb-text-secondary);
257
+ font-size: 12px;
258
+ font-weight: 600;
259
+ font-family: inherit;
260
+ cursor: pointer;
261
+ }
262
+
263
+ .cbp-wb__button--primary {
264
+ background: var(--cbp-wb-accent);
265
+ color: var(--cbp-wb-backdrop);
266
+ border-color: var(--cbp-wb-accent);
267
+ }
268
+
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;
335
+ }
336
+ `;
337
+
338
+ // src/preview/theme.ts
339
+ var previewThemePresets = [
340
+ // ── Graphite · default dark ────────────────────────────────────────────────
341
+ {
342
+ key: "graphite",
343
+ label: "Graphite (Dark)",
344
+ scheme: "dark",
345
+ backdrop: "#16181C",
346
+ tokens: {
347
+ surface: "rgba(255,255,255,.08)",
348
+ surfaceElevated: "rgba(35,37,42,.78)",
349
+ textPrimary: "#F3F4F6",
350
+ textSecondary: "#C2C6CF",
351
+ textTertiary: "#8D95A3",
352
+ accent: "#43C6AC",
353
+ accentContrast: "#081411",
354
+ border: "rgba(255,255,255,.10)",
355
+ divider: "rgba(255,255,255,.14)",
356
+ success: "#22C55E",
357
+ warning: "#F59E0B",
358
+ danger: "#EF4444"
359
+ }
360
+ },
361
+ // ── Ember · dark warm ─────────────────────────────────────────────────────
362
+ {
363
+ key: "ember",
364
+ label: "Ember (Dark)",
365
+ scheme: "dark",
366
+ backdrop: "#1C120C",
367
+ tokens: {
368
+ surface: "rgba(58,36,26,.32)",
369
+ surfaceElevated: "rgba(43,28,21,.90)",
370
+ textPrimary: "#F7E7D8",
371
+ textSecondary: "#D6B89E",
372
+ textTertiary: "#9E7F69",
373
+ accent: "#F97316",
374
+ accentContrast: "#26160E",
375
+ border: "rgba(240,201,164,.10)",
376
+ divider: "rgba(240,201,164,.14)",
377
+ success: "#22C55E",
378
+ warning: "#F59E0B",
379
+ danger: "#EF4444"
380
+ }
381
+ },
382
+ // ── Porcelain · default light ─────────────────────────────────────────────
383
+ {
384
+ key: "porcelain",
385
+ label: "Porcelain (Light)",
386
+ scheme: "light",
387
+ backdrop: "#ECECEE",
388
+ tokens: {
389
+ surface: "rgba(255,255,255,.68)",
390
+ surfaceElevated: "rgba(249,251,254,.96)",
391
+ textPrimary: "#1F2A37",
392
+ textSecondary: "#4B5C6E",
393
+ textTertiary: "#7A8A99",
394
+ accent: "#2563EB",
395
+ accentContrast: "#FFFFFF",
396
+ border: "rgba(116,135,155,.16)",
397
+ divider: "rgba(116,135,155,.22)",
398
+ success: "#059669",
399
+ warning: "#D97706",
400
+ danger: "#DC2626"
401
+ }
402
+ },
403
+ // ── Sand · light warm ─────────────────────────────────────────────────────
404
+ {
405
+ key: "sand",
406
+ label: "Sand (Light)",
407
+ scheme: "light",
408
+ backdrop: "#EFE9DD",
409
+ tokens: {
410
+ surface: "rgba(255,255,255,.56)",
411
+ surfaceElevated: "rgba(247,241,232,.88)",
412
+ textPrimary: "#2F2419",
413
+ textSecondary: "#5F5246",
414
+ textTertiary: "#8A7C6F",
415
+ accent: "#D97706",
416
+ accentContrast: "#2B2117",
417
+ border: "rgba(142,126,106,.18)",
418
+ divider: "rgba(142,126,106,.24)",
419
+ success: "#15803D",
420
+ warning: "#CA8A04",
421
+ danger: "#DC2626"
422
+ }
423
+ }
424
+ ];
425
+ var DEFAULT_THEME_KEY = "graphite";
426
+ function getThemePreset(key) {
427
+ if (key === "light") return previewThemePresets[2];
428
+ if (!key || key === "dark") return previewThemePresets[0];
429
+ return previewThemePresets.find((p) => p.key === key) ?? previewThemePresets[0];
430
+ }
431
+ var defaultDarkTheme = {
432
+ scheme: "dark",
433
+ tokens: previewThemePresets[0].tokens
434
+ };
435
+ var defaultLightTheme = {
436
+ scheme: "light",
437
+ tokens: previewThemePresets[2].tokens
438
+ };
439
+
440
+ // src/preview/chrome.ts
441
+ var MODE_WIDTHS = {
442
+ attachmentRenderer: { width: 720, min: 560, max: 900 },
443
+ action: { width: 350, min: 300, max: 500 }
444
+ };
445
+ var RENDERER_HEIGHT_CEILING = 800;
446
+ function groupScenariosByMode(scenarios) {
447
+ const map = /* @__PURE__ */ new Map();
448
+ for (const s of scenarios) {
449
+ let group = map.get(s.mode);
450
+ if (!group) {
451
+ group = [];
452
+ map.set(s.mode, group);
453
+ }
454
+ group.push(s);
455
+ }
456
+ return Array.from(map.entries()).map(([mode, list]) => ({ mode, scenarios: list }));
457
+ }
458
+ function resolveViewportWidth(scenario) {
459
+ const def = MODE_WIDTHS[scenario.mode] ?? MODE_WIDTHS.attachmentRenderer;
460
+ return {
461
+ width: scenario.viewport?.width ?? def.width,
462
+ min: scenario.viewport?.widthMin ?? def.min,
463
+ max: scenario.viewport?.widthMax ?? def.max
464
+ };
465
+ }
466
+ function clampWidth(value, min, max) {
467
+ return Math.min(max, Math.max(min, value));
468
+ }
469
+ function renderChrome(root, scenarios, opts, handlers) {
470
+ const query = new URLSearchParams(
471
+ typeof window !== "undefined" ? window.location.search : ""
472
+ );
473
+ const initialView = query.get("view") === "action" ? "action" : "attachmentRenderer";
474
+ let selectedView = initialView;
475
+ const rawThemeKey = query.get("theme") ?? DEFAULT_THEME_KEY;
476
+ let selectedThemeKey = getThemePreset(rawThemeKey).key;
477
+ const rendererScenarios = scenarios.filter((s) => s.mode === "attachmentRenderer");
478
+ const actionScenarios = scenarios.filter((s) => s.mode === "action");
479
+ function scenariosForView(view) {
480
+ return view === "attachmentRenderer" ? rendererScenarios : actionScenarios;
481
+ }
482
+ function resolveInitialScenarioID(view) {
483
+ const requested = query.get("scenario");
484
+ const options = scenariosForView(view);
485
+ if (requested && options.some((s) => s.id === requested)) return requested;
486
+ return options[0]?.id ?? "";
487
+ }
488
+ let selectedScenarioID = resolveInitialScenarioID(initialView);
489
+ let currentWidth = MODE_WIDTHS[initialView].width;
490
+ function activeScenario() {
491
+ const list = scenariosForView(selectedView);
492
+ return list.find((s) => s.id === selectedScenarioID) ?? list[0];
493
+ }
494
+ root.innerHTML = "";
495
+ const wb = div("cbp-wb");
496
+ root.appendChild(wb);
497
+ const controls = div("cbp-wb__controls");
498
+ wb.appendChild(controls);
499
+ const viewOptions = [
500
+ { value: "attachmentRenderer", label: "Renderer" },
501
+ { value: "action", label: "Action" }
502
+ ];
503
+ const viewSelect = buildSelect("cbp-wb-view", "View", viewOptions, selectedView, controls);
504
+ const scenarioAreaWrapper = div("cbp-wb__control");
505
+ controls.appendChild(scenarioAreaWrapper);
506
+ const themeOptions = previewThemePresets.map((p) => ({ value: p.key, label: p.label }));
507
+ const themeSelect = buildSelect(
508
+ "cbp-wb-theme",
509
+ "Theme",
510
+ themeOptions,
511
+ selectedThemeKey,
512
+ controls
513
+ );
514
+ const widthControl = div("cbp-wb__control");
515
+ const widthLabel = document.createElement("label");
516
+ widthLabel.className = "cbp-wb__control-label";
517
+ widthLabel.htmlFor = "cbp-wb-width";
518
+ widthLabel.textContent = `Width: ${currentWidth}px`;
519
+ widthControl.appendChild(widthLabel);
520
+ const widthSlider = document.createElement("input");
521
+ widthSlider.type = "range";
522
+ widthSlider.id = "cbp-wb-width";
523
+ widthSlider.name = "cbp-wb-width";
524
+ widthSlider.className = "cbp-wb__width-slider";
525
+ widthControl.appendChild(widthSlider);
526
+ 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);
539
+ const cardShell = div("cbp-wb__card-shell");
540
+ viewportShell.appendChild(cardShell);
541
+ const viewport = div("cbp-wb__viewport");
542
+ cardShell.appendChild(viewport);
543
+ const webview = div("cbp-wb__webview");
544
+ viewport.appendChild(webview);
545
+ const slotEl = webview;
546
+ const strip = div("cbp-wb__strip");
547
+ 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);
561
+ let logSeq = 0;
562
+ function applyTheme(preset) {
563
+ wb.style.setProperty("--cbp-wb-backdrop", preset.backdrop);
564
+ wb.style.setProperty("--cbp-wb-accent", preset.tokens.accent);
565
+ wb.style.setProperty("--cbp-wb-surface-elevated", preset.tokens.surfaceElevated);
566
+ wb.style.setProperty("--cbp-wb-border", preset.tokens.border);
567
+ wb.style.setProperty("--cbp-wb-text", preset.tokens.textPrimary);
568
+ wb.style.setProperty("--cbp-wb-text-secondary", preset.tokens.textSecondary);
569
+ wb.dataset["themeScheme"] = preset.scheme;
570
+ wb.dataset["themeKey"] = preset.key;
571
+ }
572
+ function currentBodyHeightLabel() {
573
+ const inline = parseInt(viewport.style.height, 10);
574
+ if (!Number.isNaN(inline)) return String(inline);
575
+ const rendered = Math.round(viewport.getBoundingClientRect().height);
576
+ return rendered > 0 ? String(rendered) : "auto";
577
+ }
578
+ function setViewportWidth(px) {
579
+ currentWidth = px;
580
+ cardShell.style.width = `${px}px`;
581
+ widthLabel.textContent = `Width: ${px}px`;
582
+ widthSlider.value = String(px);
583
+ frameSizeLabel.textContent = `${px} \xD7 ${currentBodyHeightLabel()}`;
584
+ }
585
+ function updateWidthSlider(min, max, value) {
586
+ widthSlider.min = String(min);
587
+ widthSlider.max = String(max);
588
+ widthSlider.value = String(value);
589
+ }
590
+ function applyInitialHeight(scenario) {
591
+ const policy = scenario.viewport?.heightPolicy ?? "auto";
592
+ if (policy === "fixed" && scenario.viewport?.height != null) {
593
+ const h = Math.min(scenario.viewport.height, RENDERER_HEIGHT_CEILING);
594
+ viewport.style.height = `${h}px`;
595
+ } else {
596
+ viewport.style.height = "";
597
+ }
598
+ }
599
+ function setViewportHeight(height) {
600
+ const scenario = activeScenario();
601
+ const policy = scenario.viewport?.heightPolicy ?? "auto";
602
+ if (policy === "fixed") return;
603
+ let clamped = Math.min(height, RENDERER_HEIGHT_CEILING);
604
+ if (policy === "bounded") {
605
+ const min = scenario.viewport?.min ?? 0;
606
+ const max = Math.min(scenario.viewport?.max ?? Infinity, RENDERER_HEIGHT_CEILING);
607
+ clamped = Math.min(max, Math.max(min, height));
608
+ }
609
+ viewport.style.height = `${clamped}px`;
610
+ frameSizeLabel.textContent = `${currentWidth} \xD7 ${clamped}`;
611
+ }
612
+ function appendLogEntry(method, payload) {
613
+ logEmpty.style.display = "none";
614
+ 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";
625
+ try {
626
+ payloadDiv.textContent = JSON.stringify(payload, null, 2);
627
+ } catch {
628
+ payloadDiv.textContent = String(payload);
629
+ }
630
+ entry.appendChild(methodSpan);
631
+ entry.appendChild(seqSpan);
632
+ entry.appendChild(payloadDiv);
633
+ logList.insertBefore(entry, logList.firstChild);
634
+ }
635
+ let activeButtons = [];
636
+ let activeDefaultButtonID = null;
637
+ function renderButtons() {
638
+ strip.innerHTML = "";
639
+ for (const btn of activeButtons) {
640
+ const button = document.createElement("button");
641
+ button.type = "button";
642
+ button.className = "cbp-wb__button" + (btn.id === activeDefaultButtonID ? " cbp-wb__button--primary" : "");
643
+ button.textContent = btn.title;
644
+ if (btn.isEnabled === false) {
645
+ button.disabled = true;
646
+ }
647
+ button.addEventListener("click", () => {
648
+ const scenario = activeScenario();
649
+ const eventName = scenario.mode === "action" ? "clipbus-plugin-action-host-invoke" : "clipbus-plugin-attachment-host-invoke";
650
+ window.dispatchEvent(
651
+ new CustomEvent(eventName, { detail: { buttonID: btn.id } })
652
+ );
653
+ });
654
+ strip.appendChild(button);
655
+ }
656
+ }
657
+ function applyButtons(buttons, defaultButtonID) {
658
+ activeButtons = Array.isArray(buttons) ? buttons.map((b) => ({ id: b.id, title: b.title, isEnabled: b.isEnabled })) : [];
659
+ activeDefaultButtonID = defaultButtonID ?? null;
660
+ renderButtons();
661
+ }
662
+ function applyScenarioButtons(scenario) {
663
+ applyButtons(scenario.buttons ?? [], scenario.defaultButtonID ?? null);
664
+ }
665
+ function refreshScenarioArea() {
666
+ scenarioAreaWrapper.innerHTML = "";
667
+ const list = scenariosForView(selectedView);
668
+ if (!list.some((s) => s.id === selectedScenarioID)) {
669
+ selectedScenarioID = list[0]?.id ?? "";
670
+ }
671
+ const lbl = document.createElement("label");
672
+ lbl.className = "cbp-wb__control-label";
673
+ lbl.htmlFor = "cbp-wb-scenario";
674
+ lbl.textContent = "Scenario";
675
+ scenarioAreaWrapper.appendChild(lbl);
676
+ const select = document.createElement("select");
677
+ select.id = "cbp-wb-scenario";
678
+ select.name = "cbp-wb-scenario";
679
+ select.className = "cbp-wb__scenario-select";
680
+ for (const s of list) {
681
+ const opt = document.createElement("option");
682
+ opt.value = s.id;
683
+ opt.textContent = s.label;
684
+ select.appendChild(opt);
685
+ }
686
+ select.value = selectedScenarioID;
687
+ select.addEventListener("change", () => {
688
+ selectedScenarioID = select.value;
689
+ activateCurrentScenario();
690
+ });
691
+ scenarioAreaWrapper.appendChild(select);
692
+ }
693
+ function updateFrameTitle() {
694
+ const scenario = activeScenario();
695
+ frameTitleLabel.textContent = scenario.mode === "attachmentRenderer" ? "Attachment Renderer" : "Draft Action";
696
+ frameSizeLabel.textContent = `${currentWidth} \xD7 ${currentBodyHeightLabel()}`;
697
+ }
698
+ function activateCurrentScenario() {
699
+ const scenario = activeScenario();
700
+ if (!scenario) return;
701
+ if (scenario.accentHex) {
702
+ cardShell.style.setProperty("--cbp-wb-card-tint", scenario.accentHex);
703
+ } else {
704
+ cardShell.style.removeProperty("--cbp-wb-card-tint");
705
+ }
706
+ const { width, min, max } = resolveViewportWidth(scenario);
707
+ const initialWidth = clampWidth(
708
+ scenario.viewport?.width ?? opts.defaultViewport?.width ?? width,
709
+ min,
710
+ max
711
+ );
712
+ setViewportWidth(initialWidth);
713
+ updateWidthSlider(min, max, initialWidth);
714
+ applyInitialHeight(scenario);
715
+ applyScenarioButtons(scenario);
716
+ updateFrameTitle();
717
+ syncQuery();
718
+ handlers.onActivate(scenario, selectedThemeKey);
719
+ }
720
+ function syncQuery() {
721
+ if (typeof window === "undefined") return;
722
+ const next = new URL(window.location.href);
723
+ next.searchParams.set("view", selectedView);
724
+ next.searchParams.set("scenario", selectedScenarioID);
725
+ next.searchParams.set("theme", selectedThemeKey);
726
+ window.history.replaceState({}, "", next.toString());
727
+ }
728
+ viewSelect.addEventListener("change", () => {
729
+ selectedView = viewSelect.value;
730
+ refreshScenarioArea();
731
+ activateCurrentScenario();
732
+ });
733
+ themeSelect.addEventListener("change", () => {
734
+ selectedThemeKey = themeSelect.value;
735
+ const preset = getThemePreset(selectedThemeKey);
736
+ applyTheme(preset);
737
+ syncQuery();
738
+ handlers.onThemeChange(activeScenario(), selectedThemeKey);
739
+ });
740
+ widthSlider.addEventListener("input", () => {
741
+ const { min, max } = resolveViewportWidth(activeScenario());
742
+ const clamped = clampWidth(parseInt(widthSlider.value, 10), min, max);
743
+ setViewportWidth(clamped);
744
+ });
745
+ function start() {
746
+ refreshScenarioArea();
747
+ applyTheme(getThemePreset(selectedThemeKey));
748
+ activateCurrentScenario();
749
+ }
750
+ function destroy() {
751
+ }
752
+ return {
753
+ slotEl,
754
+ setViewportHeight,
755
+ setViewportWidth,
756
+ appendLogEntry,
757
+ applyButtons,
758
+ applyTheme,
759
+ start,
760
+ destroy
761
+ };
762
+ }
763
+ function div(className) {
764
+ const el = document.createElement("div");
765
+ if (className) el.className = className;
766
+ return el;
767
+ }
768
+ function span(className) {
769
+ const el = document.createElement("span");
770
+ if (className) el.className = className;
771
+ return el;
772
+ }
773
+ function buildSelect(id, labelText, options, initialValue, container) {
774
+ const wrapper = div("cbp-wb__control");
775
+ const labelEl = document.createElement("label");
776
+ labelEl.className = "cbp-wb__control-label";
777
+ labelEl.textContent = labelText;
778
+ labelEl.htmlFor = id;
779
+ wrapper.appendChild(labelEl);
780
+ const select = document.createElement("select");
781
+ select.id = id;
782
+ select.name = id;
783
+ for (const opt of options) {
784
+ const option = document.createElement("option");
785
+ option.value = opt.value;
786
+ option.textContent = opt.label;
787
+ select.appendChild(option);
788
+ }
789
+ select.value = initialValue;
790
+ wrapper.appendChild(select);
791
+ container.appendChild(wrapper);
792
+ return select;
793
+ }
794
+
795
+ // src/generated/wireConstants.generated.ts
796
+ var PLUGIN_CALL_HANDLER_NAME = "clipbusPluginCall";
797
+ var WINDOW_SET_HEIGHT_METHOD = "window.setHeight";
798
+
799
+ // src/preview/fakeHost.ts
800
+ function installFakeHost(target, hooks) {
801
+ const t = target;
802
+ if (!t["webkit"]) {
803
+ t["webkit"] = {};
804
+ }
805
+ const webkit = t["webkit"];
806
+ if (!webkit["messageHandlers"]) {
807
+ webkit["messageHandlers"] = {};
808
+ }
809
+ const handlers = webkit["messageHandlers"];
810
+ handlers[PLUGIN_CALL_HANDLER_NAME] = {
811
+ postMessage: async (msg) => {
812
+ const { method, payload } = msg;
813
+ hooks.onCall(method, payload);
814
+ if (method === WINDOW_SET_HEIGHT_METHOD) {
815
+ const height = payload?.height;
816
+ if (typeof height === "number") {
817
+ hooks.onSetHeight(height);
818
+ }
819
+ return defaultReplyFor(method);
820
+ }
821
+ if (method.endsWith(".setButtons")) {
822
+ const p = payload ?? {};
823
+ hooks.onSetButtons(
824
+ Array.isArray(p.buttons) ? p.buttons : [],
825
+ p.defaultButtonID ?? null
826
+ );
827
+ return defaultReplyFor(method);
828
+ }
829
+ return defaultReplyFor(method);
830
+ }
831
+ };
832
+ }
833
+ function defaultReplyFor(method) {
834
+ if (method === "runtime.invoke") {
835
+ return { response: void 0 };
836
+ }
837
+ return {};
838
+ }
839
+
840
+ // src/generated/hostBootstrap.generated.ts
841
+ function emitAttachmentBootstrap(p) {
842
+ if (p.context !== void 0) {
843
+ globalThis["__CLIPBUS_PLUGIN_CONTEXT__"] = p.context;
844
+ globalThis.dispatchEvent(new CustomEvent("clipbus-plugin-context", { detail: p.context }));
845
+ }
846
+ if (p.item !== void 0) {
847
+ globalThis["__CLIPBUS_PLUGIN_ITEM__"] = p.item;
848
+ globalThis.dispatchEvent(new CustomEvent("clipbus-plugin-item", { detail: p.item }));
849
+ }
850
+ if (p.attachment !== void 0) {
851
+ globalThis["__CLIPBUS_PLUGIN_ATTACHMENT__"] = p.attachment;
852
+ globalThis.dispatchEvent(new CustomEvent("clipbus-plugin-attachment", { detail: p.attachment }));
853
+ }
854
+ if (p.theme !== void 0) {
855
+ globalThis["__CLIPBUS_PLUGIN_THEME__"] = p.theme;
856
+ globalThis.dispatchEvent(new CustomEvent("clipbus-plugin-theme", { detail: p.theme }));
857
+ }
858
+ if (p.locale !== void 0) {
859
+ globalThis["__CLIPBUS_PLUGIN_LOCALE__"] = p.locale;
860
+ globalThis.dispatchEvent(new CustomEvent("clipbus-plugin-locale", { detail: p.locale }));
861
+ }
862
+ }
863
+ function emitActionBootstrap(p) {
864
+ if (p.context !== void 0) {
865
+ globalThis["__CLIPBUS_PLUGIN_CONTEXT__"] = p.context;
866
+ globalThis.dispatchEvent(new CustomEvent("clipbus-plugin-context", { detail: p.context }));
867
+ }
868
+ if (p.item !== void 0) {
869
+ globalThis["__CLIPBUS_PLUGIN_ITEM__"] = p.item;
870
+ globalThis.dispatchEvent(new CustomEvent("clipbus-plugin-item", { detail: p.item }));
871
+ }
872
+ if (p.draft !== void 0) {
873
+ globalThis["__CLIPBUS_PLUGIN_DRAFT__"] = p.draft;
874
+ globalThis.dispatchEvent(new CustomEvent("clipbus-plugin-draft", { detail: p.draft }));
875
+ }
876
+ if (p.theme !== void 0) {
877
+ globalThis["__CLIPBUS_PLUGIN_THEME__"] = p.theme;
878
+ globalThis.dispatchEvent(new CustomEvent("clipbus-plugin-theme", { detail: p.theme }));
879
+ }
880
+ if (p.locale !== void 0) {
881
+ globalThis["__CLIPBUS_PLUGIN_LOCALE__"] = p.locale;
882
+ globalThis.dispatchEvent(new CustomEvent("clipbus-plugin-locale", { detail: p.locale }));
883
+ }
884
+ }
885
+
886
+ // src/preview/wire.ts
887
+ function computeWirePayloads(scenario, themeKey = DEFAULT_THEME_KEY) {
888
+ const context = {
889
+ mode: scenario.mode,
890
+ pluginID: scenario.pluginID ?? "plugin.preview.harness"
891
+ };
892
+ const preset = getThemePreset(themeKey);
893
+ const theme = {
894
+ scheme: preset.scheme,
895
+ tokens: { ...preset.tokens }
896
+ };
897
+ const locale = { locale: "en" };
898
+ if (scenario.mode === "attachmentRenderer") {
899
+ return {
900
+ mode: "attachmentRenderer",
901
+ context,
902
+ item: scenario.item,
903
+ attachment: scenario.attachment,
904
+ theme,
905
+ locale
906
+ };
907
+ }
908
+ return {
909
+ mode: "action",
910
+ context,
911
+ item: scenario.item,
912
+ draft: scenario.draft ?? {},
913
+ theme,
914
+ locale
915
+ };
916
+ }
917
+ function injectWire(_target, payloads) {
918
+ if (payloads.mode === "attachmentRenderer") {
919
+ emitAttachmentBootstrap({
920
+ context: payloads.context,
921
+ item: payloads.item,
922
+ attachment: payloads.attachment,
923
+ theme: payloads.theme,
924
+ locale: payloads.locale
925
+ });
926
+ } else {
927
+ emitActionBootstrap({
928
+ context: payloads.context,
929
+ item: payloads.item,
930
+ draft: payloads.draft,
931
+ theme: payloads.theme,
932
+ locale: payloads.locale
933
+ });
934
+ }
935
+ }
936
+ var THEME_CSS_VAR_NAMES = {
937
+ surface: "--clipbus-surface",
938
+ surfaceElevated: "--clipbus-surface-elevated",
939
+ textPrimary: "--clipbus-text-primary",
940
+ textSecondary: "--clipbus-text-secondary",
941
+ textTertiary: "--clipbus-text-tertiary",
942
+ accent: "--clipbus-accent",
943
+ accentContrast: "--clipbus-accent-contrast",
944
+ border: "--clipbus-border",
945
+ divider: "--clipbus-divider",
946
+ success: "--clipbus-success",
947
+ warning: "--clipbus-warning",
948
+ danger: "--clipbus-danger"
949
+ };
950
+ function applyThemeCssVars(target, snapshot) {
951
+ const tokens = snapshot.tokens;
952
+ for (const [field, varName] of Object.entries(THEME_CSS_VAR_NAMES)) {
953
+ const value = tokens[field];
954
+ if (typeof value === "string") target.style.setProperty(varName, value);
955
+ }
956
+ }
957
+
958
+ // src/preview/index.ts
959
+ function createPreviewWorkbench(root, opts) {
960
+ if (opts.scenarios.length === 0) {
961
+ root.textContent = "[preview] No scenarios provided.";
962
+ return;
963
+ }
964
+ injectStyles(root.ownerDocument ?? document);
965
+ let pendingLogEntry = null;
966
+ let pendingSetHeight = null;
967
+ let pendingSetButtons = null;
968
+ installFakeHost(window, {
969
+ onCall(method, payload) {
970
+ pendingLogEntry?.(method, payload);
971
+ },
972
+ onSetHeight(height) {
973
+ pendingSetHeight?.(height);
974
+ },
975
+ onSetButtons(buttons, defaultButtonID) {
976
+ pendingSetButtons?.(buttons, defaultButtonID);
977
+ }
978
+ });
979
+ let currentCleanup = null;
980
+ const {
981
+ slotEl,
982
+ setViewportHeight,
983
+ appendLogEntry,
984
+ applyButtons,
985
+ start,
986
+ destroy
987
+ } = renderChrome(
988
+ root,
989
+ opts.scenarios,
990
+ opts,
991
+ {
992
+ onActivate: (scenario, presetKey) => activateScenario(scenario, presetKey),
993
+ onThemeChange: (scenario, presetKey) => reinjectTheme(scenario, presetKey)
994
+ }
995
+ );
996
+ pendingLogEntry = appendLogEntry;
997
+ pendingSetHeight = setViewportHeight;
998
+ pendingSetButtons = applyButtons;
999
+ function activateScenario(scenario, presetKey) {
1000
+ if (currentCleanup) {
1001
+ try {
1002
+ currentCleanup();
1003
+ } catch {
1004
+ }
1005
+ currentCleanup = null;
1006
+ }
1007
+ slotEl.innerHTML = "";
1008
+ const payloads = computeWirePayloads(scenario, presetKey);
1009
+ injectWire(window, payloads);
1010
+ applyThemeCssVars(slotEl, payloads.theme);
1011
+ currentCleanup = opts.mount(slotEl, { scenario });
1012
+ }
1013
+ function reinjectTheme(scenario, presetKey) {
1014
+ const payloads = computeWirePayloads(scenario, presetKey);
1015
+ injectWire(window, payloads);
1016
+ applyThemeCssVars(slotEl, payloads.theme);
1017
+ }
1018
+ start();
1019
+ root["__cbpWbDestroy__"] = () => {
1020
+ currentCleanup?.();
1021
+ destroy();
1022
+ };
1023
+ }
1024
+ var STYLE_TAG_ID = "cbp-wb-styles";
1025
+ var FAVICON_TAG_ID = "cbp-wb-favicon";
1026
+ function injectStyles(doc) {
1027
+ const head = doc.head ?? doc.documentElement;
1028
+ if (!doc.getElementById(STYLE_TAG_ID)) {
1029
+ const style = doc.createElement("style");
1030
+ style.id = STYLE_TAG_ID;
1031
+ style.textContent = previewStyles;
1032
+ head.appendChild(style);
1033
+ }
1034
+ if (!doc.getElementById(FAVICON_TAG_ID)) {
1035
+ const icon = doc.createElement("link");
1036
+ icon.id = FAVICON_TAG_ID;
1037
+ icon.rel = "icon";
1038
+ icon.href = "data:,";
1039
+ head.appendChild(icon);
1040
+ }
1041
+ }