@hueest/xray 0.1.0

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,47 @@
1
+ //#region src/breakpoints.ts
2
+ /**
3
+ * Per-plate breakpoint derivation (ADR 0004): the width thresholds that could
4
+ * change this plate come from the `@media` conditions on its own copied rules
5
+ * plus the queries the app evaluated through `matchMedia` during capture.
6
+ * Thresholds partition the width axis into views; identical captures in
7
+ * adjacent views collapse at merge time.
8
+ */
9
+ const FONT_SIZE_PX = 16;
10
+ /** A breakpoint is the first integer px width of the view ABOVE the boundary. */
11
+ function thresholdsOf(condition) {
12
+ const out = [];
13
+ const toPx = (value, unit) => Number.parseFloat(value) * (unit === "px" ? 1 : FONT_SIZE_PX);
14
+ const push = (raw, exclusiveBelow) => {
15
+ const bp = exclusiveBelow ? Number.isInteger(raw) ? raw + 1 : Math.ceil(raw) : Math.ceil(raw);
16
+ if (Number.isFinite(bp) && bp > 0) out.push(bp);
17
+ };
18
+ for (const m of condition.matchAll(/min-width:\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), false);
19
+ for (const m of condition.matchAll(/max-width:\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), true);
20
+ for (const m of condition.matchAll(/width\s*>=?\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), false);
21
+ for (const m of condition.matchAll(/width\s*<=?\s*([\d.]+)(px|em|rem)/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), true);
22
+ for (const m of condition.matchAll(/([\d.]+)(px|em|rem)\s*<=?\s*width/g)) push(toPx(m[1] ?? "", m[2] ?? "px"), false);
23
+ return out;
24
+ }
25
+ /** Width thresholds for this plate: its rules' media conditions + the app's matchMedia queries. */
26
+ function deriveBreakpoints(rules, matchMediaQueries = []) {
27
+ const set = /* @__PURE__ */ new Set();
28
+ for (const rule of rules) for (const condition of rule.media ?? []) for (const bp of thresholdsOf(condition)) set.add(bp);
29
+ for (const query of matchMediaQueries) for (const bp of thresholdsOf(query)) set.add(bp);
30
+ return [...set].toSorted((a, b) => a - b);
31
+ }
32
+ /** The view interval `[min, max)` a given viewport width falls into. */
33
+ function regimeFor(width, breakpoints) {
34
+ let min;
35
+ let max;
36
+ for (const bp of breakpoints) if (bp <= width) min = bp;
37
+ else {
38
+ max = bp;
39
+ break;
40
+ }
41
+ return {
42
+ ...min === void 0 ? {} : { min },
43
+ ...max === void 0 ? {} : { max }
44
+ };
45
+ }
46
+ //#endregion
47
+ export { regimeFor as n, deriveBreakpoints as t };
@@ -0,0 +1,42 @@
1
+ import { s as ViewCapture } from "./plate-DboxMKg-.js";
2
+
3
+ //#region src/client.d.ts
4
+ /**
5
+ * The dev capture client (M2). Runs in the browser during dev only: installs
6
+ * the `__XRAY__` hook `<Skeleton>` calls when content becomes ready,
7
+ * settles, serializes the subtree into a view capture, and hands it to the
8
+ * sink (the plugin's POST endpoint in M3). Re-captures when the viewport
9
+ * crosses into another view while a site is still mounted.
10
+ */
11
+ interface CapturePayload {
12
+ name: string;
13
+ /** This capture, view interval already derived. */
14
+ capture: ViewCapture;
15
+ /** Width thresholds derived for this plate (rules' @media + matchMedia queries). */
16
+ breakpoints: number[];
17
+ }
18
+ interface ClientOptions {
19
+ /** Settle delay (ms) once the subtree goes quiet. Default 200. */
20
+ delay?: number;
21
+ /**
22
+ * Upper bound (ms) on waiting for quiet before capturing anyway. Raise it
23
+ * for pages that stream content for a long time. Default 5000.
24
+ */
25
+ settleCap?: number;
26
+ /**
27
+ * Refuse captures larger than this many nodes — a skeleton that big means
28
+ * the Skeleton sits too high in the component tree. Default 4000.
29
+ */
30
+ maxNodes?: number;
31
+ /**
32
+ * Initial state of the capture toggle: whether browsing re-captures plates.
33
+ * A per-tab override (sessionStorage / `?xray-capture`) wins over it.
34
+ * Defaults to `true` — the plugin passes its own (off-by-default) policy.
35
+ */
36
+ capture?: boolean;
37
+ /** Where finished captures go. */
38
+ sink: (payload: CapturePayload) => void;
39
+ }
40
+ declare function installXrayClient(options: ClientOptions): () => void;
41
+ //#endregion
42
+ export { CapturePayload, ClientOptions, installXrayClient };
package/dist/client.js ADDED
@@ -0,0 +1,354 @@
1
+ import { i as captureRegime, t as CaptureTooLargeError } from "./serialize-Bq6yJnNV.js";
2
+ import { n as regimeFor, t as deriveBreakpoints } from "./breakpoints-CBNlh7lQ.js";
3
+ //#region src/flash.ts
4
+ /**
5
+ * Dev-only radiographic capture effect — pure delight. When a capture lands we
6
+ * briefly "expose" the captured element: a cyan bloom, a negative blink (an
7
+ * x-ray IS a negative — `backdrop-filter: invert`), and a scan-line sweep down
8
+ * the box. Honors `prefers-reduced-motion` (then just a soft bloom). It's a
9
+ * transient `position: fixed`, `pointer-events: none` overlay sitting just under
10
+ * the HUD; it never touches the capture pipeline or the captured DOM.
11
+ */
12
+ const STYLE_ID = "xr-flash-style";
13
+ function ensureStyle() {
14
+ if (document.getElementById(STYLE_ID)) return;
15
+ const style = document.createElement("style");
16
+ style.id = STYLE_ID;
17
+ style.textContent = `
18
+ .xr-flash {
19
+ position: fixed; pointer-events: none; z-index: 2147483646;
20
+ overflow: hidden; border-radius: 4px;
21
+ animation: xr-expose 480ms ease-out forwards;
22
+ }
23
+ .xr-flash::after {
24
+ content: ''; position: absolute; left: 0; right: 0; height: 18%;
25
+ background: linear-gradient(180deg, transparent, rgba(186, 230, 253, 0.9), transparent);
26
+ filter: blur(1px);
27
+ animation: xr-scan 480ms ease-in-out forwards;
28
+ }
29
+ @keyframes xr-expose {
30
+ 0% { background: rgba(186, 230, 253, 0); backdrop-filter: invert(0); opacity: 1 }
31
+ 18% { background: rgba(186, 230, 253, 0.28); backdrop-filter: invert(1) }
32
+ 100% { background: rgba(186, 230, 253, 0); backdrop-filter: invert(0); opacity: 0 }
33
+ }
34
+ @keyframes xr-scan {
35
+ from { transform: translateY(-100%) }
36
+ to { transform: translateY(560%) }
37
+ }
38
+ @keyframes xr-expose-soft {
39
+ 0% { background: rgba(186, 230, 253, 0.25); opacity: 1 }
40
+ 100% { background: rgba(186, 230, 253, 0); opacity: 0 }
41
+ }
42
+ @media (prefers-reduced-motion: reduce) {
43
+ .xr-flash { animation: xr-expose-soft 320ms ease-out forwards }
44
+ .xr-flash::after { display: none }
45
+ }
46
+ `;
47
+ (document.head ?? document.documentElement).append(style);
48
+ }
49
+ /** The element's box, or the union of its children's (a display:contents wrapper has none). */
50
+ function boxOf(el) {
51
+ const own = el.getBoundingClientRect();
52
+ if (own.width > 0 && own.height > 0) return own;
53
+ let left = Infinity;
54
+ let top = Infinity;
55
+ let right = -Infinity;
56
+ let bottom = -Infinity;
57
+ for (const child of el.children) {
58
+ const rect = child.getBoundingClientRect();
59
+ if (rect.width === 0 && rect.height === 0) continue;
60
+ left = Math.min(left, rect.left);
61
+ top = Math.min(top, rect.top);
62
+ right = Math.max(right, rect.right);
63
+ bottom = Math.max(bottom, rect.bottom);
64
+ }
65
+ return Number.isFinite(left) ? new DOMRect(left, top, right - left, bottom - top) : null;
66
+ }
67
+ /** Flash the radiographic effect over `el`'s box. No-op outside a laid-out DOM. */
68
+ function flashCapture(el) {
69
+ if (typeof document === "undefined") return;
70
+ const box = boxOf(el);
71
+ if (!box) return;
72
+ ensureStyle();
73
+ const flash = document.createElement("div");
74
+ flash.className = "xr-flash";
75
+ flash.style.left = `${box.left}px`;
76
+ flash.style.top = `${box.top}px`;
77
+ flash.style.width = `${box.width}px`;
78
+ flash.style.height = `${box.height}px`;
79
+ flash.addEventListener("animationend", () => flash.remove(), { once: true });
80
+ (document.body ?? document.documentElement).append(flash);
81
+ setTimeout(() => flash.remove(), 1e3);
82
+ }
83
+ //#endregion
84
+ //#region src/client.ts
85
+ function isExtensionAck(value) {
86
+ return typeof value === "object" && value !== null && "source" in value && value.source === "xray-ext" && "kind" in value && value.kind === "ack" && "id" in value && typeof value.id === "number" && (!("ok" in value) || typeof value.ok === "boolean") && (!("error" in value) || typeof value.error === "string");
87
+ }
88
+ const DEFAULT_SETTLE_CAP = 5e3;
89
+ const DEFAULT_MAX_NODES = 4e3;
90
+ const RESIZE_DEBOUNCE = 300;
91
+ function installXrayClient(options) {
92
+ const win = window;
93
+ const defaultDelay = options.delay ?? 200;
94
+ const settleCap = options.settleCap ?? DEFAULT_SETTLE_CAP;
95
+ const maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES;
96
+ const sites = /* @__PURE__ */ new Set();
97
+ const matchMediaQueries = /* @__PURE__ */ new Set();
98
+ const livePlates = /* @__PURE__ */ new Map();
99
+ const plateSerialized = /* @__PURE__ */ new Map();
100
+ const plateListeners = /* @__PURE__ */ new Map();
101
+ const plates = {
102
+ get: (name) => livePlates.get(name),
103
+ subscribe: (name, onChange) => {
104
+ let set = plateListeners.get(name);
105
+ if (!set) {
106
+ set = /* @__PURE__ */ new Set();
107
+ plateListeners.set(name, set);
108
+ }
109
+ set.add(onChange);
110
+ return () => set.delete(onChange);
111
+ },
112
+ seed: (name, plate) => {
113
+ if (livePlates.has(name)) return;
114
+ livePlates.set(name, plate);
115
+ plateSerialized.set(name, JSON.stringify(plate));
116
+ }
117
+ };
118
+ const updatePlate = (name, plate) => {
119
+ const json = JSON.stringify(plate);
120
+ if (plateSerialized.get(name) === json) return;
121
+ plateSerialized.set(name, json);
122
+ livePlates.set(name, plate);
123
+ for (const cb of plateListeners.get(name) ?? []) cb();
124
+ };
125
+ const nativeMatchMedia = win.matchMedia.bind(win);
126
+ win.matchMedia = (query) => {
127
+ matchMediaQueries.add(query);
128
+ return nativeMatchMedia(query);
129
+ };
130
+ const lightbox = createToggleStore(win, LIGHTBOX_KEY, LIGHTBOX_PARAM, false);
131
+ const captureEnabled = createToggleStore(win, CAPTURE_KEY, CAPTURE_PARAM, options.capture ?? true);
132
+ const isPrimaryForName = (site) => {
133
+ for (const other of sites) {
134
+ if (other === site || other.disposed || other.name !== site.name || !other.el.isConnected) continue;
135
+ if ((site.el.compareDocumentPosition(other.el) & Node.DOCUMENT_POSITION_PRECEDING) !== 0) return false;
136
+ }
137
+ return true;
138
+ };
139
+ const captureSite = async (site, force = false) => {
140
+ if (!force && !captureEnabled.get() || site.tooLarge || !isPrimaryForName(site)) return;
141
+ await settle(site.el, site.delay, settleCap);
142
+ if (site.disposed || site.tooLarge || !site.el.isConnected || !isPrimaryForName(site)) return;
143
+ const roots = Array.from(site.el.children).filter((child) => child.tagName !== "STYLE");
144
+ if (roots.length === 0) return;
145
+ try {
146
+ const capture = captureRegime(roots, { maxNodes });
147
+ const breakpoints = deriveBreakpoints(capture.rules, [...matchMediaQueries]);
148
+ const span = regimeFor(capture.width, breakpoints);
149
+ const payload = {
150
+ name: site.name,
151
+ capture: {
152
+ ...capture,
153
+ ...span
154
+ },
155
+ breakpoints
156
+ };
157
+ const key = JSON.stringify(span);
158
+ const body = JSON.stringify(payload);
159
+ if (site.sent.get(key) === body) return;
160
+ site.sent.set(key, body);
161
+ options.sink(payload);
162
+ flashCapture(site.el);
163
+ } catch (error) {
164
+ if (error instanceof CaptureTooLargeError) {
165
+ site.tooLarge = true;
166
+ console.warn(`[xray] skipping capture of "${site.name}": ${error.nodeCount} nodes (max ${maxNodes}).`, "A skeleton this big usually means the <Skeleton> sits too high in the tree.");
167
+ return;
168
+ }
169
+ console.warn("[xray] capture failed for", site.name, error);
170
+ }
171
+ };
172
+ let resizeTimer;
173
+ const onResize = () => {
174
+ clearTimeout(resizeTimer);
175
+ resizeTimer = setTimeout(() => {
176
+ for (const site of sites) captureSite(site);
177
+ }, RESIZE_DEBOUNCE);
178
+ };
179
+ win.addEventListener("resize", onResize);
180
+ const unsubscribeCapture = captureEnabled.subscribe(() => {
181
+ if (captureEnabled.get()) for (const site of sites) captureSite(site);
182
+ });
183
+ const extAcks = /* @__PURE__ */ new Map();
184
+ let nextExtReqId = 1;
185
+ const onExtMessage = (event) => {
186
+ if (event.source !== win) return;
187
+ const data = event.data;
188
+ if (isExtensionAck(data)) {
189
+ extAcks.get(data.id)?.({
190
+ ok: data.ok ?? false,
191
+ error: data.error
192
+ });
193
+ extAcks.delete(data.id);
194
+ }
195
+ };
196
+ win.addEventListener("message", onExtMessage);
197
+ const extCommand = (kind, extra = {}) => {
198
+ const id = nextExtReqId++;
199
+ return new Promise((resolve, reject) => {
200
+ extAcks.set(id, (res) => res.ok ? resolve() : reject(new Error(res.error ?? "xray extension error")));
201
+ win.postMessage({
202
+ source: "xray",
203
+ kind,
204
+ id,
205
+ ...extra
206
+ }, "*");
207
+ setTimeout(() => {
208
+ if (extAcks.delete(id)) reject(/* @__PURE__ */ new Error("xray extension did not respond (installed? DevTools closed on this tab?)"));
209
+ }, 1e4);
210
+ });
211
+ };
212
+ const coverageMap = /* @__PURE__ */ new Map();
213
+ const coverageListeners = /* @__PURE__ */ new Set();
214
+ const coverage = {
215
+ get: () => coverageMap,
216
+ set: (name, plateCoverage) => {
217
+ coverageMap.set(name, plateCoverage);
218
+ for (const cb of coverageListeners) cb();
219
+ },
220
+ subscribe: (cb) => {
221
+ coverageListeners.add(cb);
222
+ return () => coverageListeners.delete(cb);
223
+ }
224
+ };
225
+ const sweepWidths = () => {
226
+ const set = new Set(deriveBreakpoints([], [...matchMediaQueries]));
227
+ for (const site of sites) for (const bp of coverageMap.get(site.name)?.breakpoints ?? []) set.add(bp);
228
+ const breakpoints = [...set].toSorted((a, b) => a - b);
229
+ const min = breakpoints[0];
230
+ const first = min === void 0 ? 390 : Math.max(1, Math.min(390, min - 1));
231
+ return [...new Set([first, ...breakpoints])].toSorted((a, b) => a - b);
232
+ };
233
+ const captureAllViews = async () => {
234
+ const widths = sweepWidths();
235
+ try {
236
+ for (const width of widths) {
237
+ await extCommand("emulate", {
238
+ width,
239
+ height: 2400,
240
+ mobile: width < 600
241
+ });
242
+ await Promise.all([...sites].map((site) => captureSite(site, true)));
243
+ }
244
+ } finally {
245
+ await extCommand("reset").catch(() => void 0);
246
+ }
247
+ };
248
+ globalThis.__XRAY__ = {
249
+ lightbox,
250
+ capture: captureEnabled,
251
+ plates,
252
+ updatePlate,
253
+ captureAllViews,
254
+ coverage,
255
+ captured: (name, el, opts) => {
256
+ const site = {
257
+ name,
258
+ el,
259
+ delay: opts?.delay ?? defaultDelay,
260
+ sent: /* @__PURE__ */ new Map(),
261
+ disposed: false,
262
+ tooLarge: false
263
+ };
264
+ sites.add(site);
265
+ captureSite(site);
266
+ return () => {
267
+ site.disposed = true;
268
+ sites.delete(site);
269
+ for (const other of sites) if (other.name === site.name) captureSite(other);
270
+ };
271
+ }
272
+ };
273
+ return () => {
274
+ win.matchMedia = nativeMatchMedia;
275
+ win.removeEventListener("resize", onResize);
276
+ win.removeEventListener("message", onExtMessage);
277
+ clearTimeout(resizeTimer);
278
+ unsubscribeCapture();
279
+ globalThis.__XRAY__ = void 0;
280
+ sites.clear();
281
+ };
282
+ }
283
+ const LIGHTBOX_KEY = "xray:lightbox";
284
+ const LIGHTBOX_PARAM = "xray-lightbox";
285
+ const CAPTURE_KEY = "xray:capture";
286
+ const CAPTURE_PARAM = "xray-capture";
287
+ /**
288
+ * A sticky boolean toggle: per-tab in sessionStorage, seedable to `true` via
289
+ * `?<param>` in the URL, flipped from the HUD or the console
290
+ * (`__XRAY__.lightbox.set(true)`). A stored value wins over `initial`; the
291
+ * URL param forces `true`.
292
+ */
293
+ function createToggleStore(win, key, param, initial) {
294
+ let value = initial;
295
+ try {
296
+ const stored = win.sessionStorage.getItem(key);
297
+ if (stored !== null) value = stored === "1";
298
+ } catch {}
299
+ if (new URL(win.location.href).searchParams.has(param)) value = true;
300
+ const listeners = /* @__PURE__ */ new Set();
301
+ return {
302
+ get: () => value,
303
+ set(next) {
304
+ if (next === value) return;
305
+ value = next;
306
+ try {
307
+ win.sessionStorage.setItem(key, next ? "1" : "0");
308
+ } catch {}
309
+ for (const listener of listeners) listener();
310
+ },
311
+ subscribe(onChange) {
312
+ listeners.add(onChange);
313
+ return () => {
314
+ listeners.delete(onChange);
315
+ };
316
+ }
317
+ };
318
+ }
319
+ /**
320
+ * Resolve once the subtree has been quiet (no mutations, no child resizes)
321
+ * for `delay` ms — fonts loaded first. Charts, lazy images and late embeds
322
+ * made single-shot capture non-deterministic in the M1 harness; this is the
323
+ * cure. Hard-capped so a perpetually-animating subtree still captures.
324
+ */
325
+ function settle(el, delay, cap) {
326
+ const doc = el.ownerDocument;
327
+ return (doc.fonts ? doc.fonts.ready : Promise.resolve()).then(() => new Promise((resolve) => {
328
+ let timer;
329
+ let capTimer;
330
+ const observer = new MutationObserver(bump);
331
+ const resizeObserver = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(bump);
332
+ function done() {
333
+ observer.disconnect();
334
+ resizeObserver?.disconnect();
335
+ clearTimeout(timer);
336
+ clearTimeout(capTimer);
337
+ resolve();
338
+ }
339
+ function bump() {
340
+ clearTimeout(timer);
341
+ timer = setTimeout(done, delay);
342
+ }
343
+ observer.observe(el, {
344
+ subtree: true,
345
+ childList: true,
346
+ characterData: true
347
+ });
348
+ if (resizeObserver) for (const child of Array.from(el.children)) resizeObserver.observe(child);
349
+ timer = setTimeout(done, delay);
350
+ capTimer = setTimeout(done, cap);
351
+ }));
352
+ }
353
+ //#endregion
354
+ export { installXrayClient };
package/dist/core.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { a as PlateNode, c as emptyPlate, i as PlateFile, n as MergedPlate, o as RenderNode, r as Plate, s as ViewCapture, t as LeafKind } from "./plate-DboxMKg-.js";
2
+ import { CaptureOptions, CaptureRegimeOptions, CaptureTooLargeError, capture, captureRegime } from "./serialize.js";
3
+
4
+ //#region src/chunk.d.ts
5
+ /**
6
+ * Turn a merged plate (RenderNode tree) into the shipped plate (chunks). The
7
+ * root's own content classes travel as `rootCls` (the adapter puts them on the
8
+ * root element); its children become `chunks`. A capture-less plate has null
9
+ * chunks and renders the fallback.
10
+ */
11
+ declare function serializePlate(merged: MergedPlate): Plate;
12
+ //#endregion
13
+ //#region src/merge.d.ts
14
+ /**
15
+ * Turn the per-view captures in a plate file into the one plate a Skeleton
16
+ * renders.
17
+ *
18
+ * Each view is kept whole — its own subtree, its own ids, its own rules —
19
+ * and shown only across the viewport range it owns, via `@media display:none`
20
+ * gates. We do NOT merge the views' trees into one. An earlier design (ADR
21
+ * 0004) did, sharing common structure and forking the rest, but aligning two
22
+ * genuinely-different DOM trees (mobile stacked vs desktop two-column) without
23
+ * stable keys proved unreliable: every heuristic mis-paired some nodes and
24
+ * remapped one view's rules onto another's elements, corrupting both. A lone
25
+ * capture is pixel-faithful; keeping each view lone preserves that. The cost
26
+ * is a larger plate (roughly the sum of the views) — bounded by the plate
27
+ * size cap — in exchange for correctness.
28
+ *
29
+ * Pure data-in/data-out — runs in node (plugin load) and in tests.
30
+ */
31
+ declare function mergeViews(file: PlateFile): MergedPlate;
32
+ /** Distinct plate names referenced by the tree's stitches, in first-seen order. */
33
+ declare function collectRefs(tree: RenderNode | null): string[];
34
+ /** Fold a fresh capture into the plate file: union breakpoints, one capture per view. */
35
+ declare function addCapture(file: PlateFile | null, name: string, capture: ViewCapture, breakpoints: readonly number[]): PlateFile;
36
+ //#endregion
37
+ //#region src/core.d.ts
38
+ /**
39
+ * Capture a live DOM subtree straight to a renderable Plate — `capture` followed
40
+ * by `serializePlate` in one call. `roots` are the top-level elements to capture
41
+ * (a component's rendered roots). Pass the result to a `<Skeleton plate>`.
42
+ */
43
+ declare function captureElement(roots: readonly Element[], options: CaptureOptions): Plate;
44
+ /**
45
+ * Render a stitch-free Plate to a self-contained HTML string — the
46
+ * framework-neutral equivalent of `<Skeleton plate loading />`, for when you
47
+ * don't want to pull a framework adapter (demos, plain DOM, server strings).
48
+ * Drop it in with `el.innerHTML = renderPlateHtml(plate)` or React's
49
+ * `dangerouslySetInnerHTML`. The string is self-contained: it carries BASE_CSS
50
+ * + the plate's scoped rules in its own `<style>`. Stitched plates (a nested
51
+ * `<Skeleton>`) need an adapter to mount their child plates, so they throw here.
52
+ */
53
+ declare function renderPlateHtml(plate: Plate): string;
54
+ //#endregion
55
+ export { type CaptureOptions, type CaptureRegimeOptions, CaptureTooLargeError, type LeafKind, type MergedPlate, type Plate, type PlateFile, type PlateNode, type ViewCapture, addCapture, capture, captureElement, captureRegime, collectRefs, emptyPlate, mergeViews, renderPlateHtml, serializePlate };