@hueest/xray 0.2.0 → 0.4.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.
@@ -1,4 +1,4 @@
1
- import { a as ROOT_ATTR, o as ROOT_CLASS, s as ROOT_INSTANT_ATTR, t as BASE_CSS } from "./plate-DoE1HEXp.js";
1
+ import { a as ROOT_ATTR, o as ROOT_CLASS, s as ROOT_INSTANT_ATTR, t as BASE_CSS } from "./plate-BRR6d8Se.js";
2
2
  import { t as escapeStyleText } from "./css-escape-N7bOusGW.js";
3
3
  import { Fragment, useEffect, useRef, useState } from "react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -74,6 +74,7 @@ function collectStitchNames(chunks) {
74
74
  const walk = (cs) => {
75
75
  for (const chunk of cs) {
76
76
  if (typeof chunk === "string") continue;
77
+ if ("t" in chunk) continue;
77
78
  if ("r" in chunk) names.push(chunk.r);
78
79
  else walk(chunk.k);
79
80
  }
@@ -256,6 +257,10 @@ function renderChunks(chunks, resolveStitch) {
256
257
  style: DISPLAY_CONTENTS,
257
258
  dangerouslySetInnerHTML: { __html: chunk }
258
259
  }, i);
260
+ if ("t" in chunk) return /* @__PURE__ */ jsx("div", {
261
+ style: DISPLAY_CONTENTS,
262
+ dangerouslySetInnerHTML: { __html: chunk.t.repeat(chunk.n) }
263
+ }, i);
259
264
  if ("r" in chunk) return /* @__PURE__ */ jsx(Fragment, { children: resolveStitch(chunk.r) }, i);
260
265
  return /* @__PURE__ */ jsx("div", {
261
266
  className: chunk.c,
@@ -1,5 +1,4 @@
1
- import { r as Plate } from "./plate-Bv6W-GkA.js";
2
- import { o as XrayCaptureWalker } from "./serialize-B--oY2bV.js";
1
+ import { c as XrayCaptureWalker, f as Plate } from "./serialize-BgdGt34A.js";
3
2
  import { ReactNode } from "react";
4
3
 
5
4
  //#region src/react.core.d.ts
@@ -44,10 +43,21 @@ interface SkeletonBase {
44
43
  * The escape hatch beyond the declarative `data-xr-*` annotations: a closure
45
44
  * that decides, per element, whether to ignore it, collapse it to one Bone, or
46
45
  * defer to the built-in walk. Threaded through the dev adapter into
47
- * `captureRegime`; PRODUCTION ignores it entirely (it never captures). On both
46
+ * `captureView`; PRODUCTION ignores it entirely (it never captures). On both
48
47
  * `root` variants of the union, so a manual root may carry one too.
49
48
  */
50
49
  captureWalker?: XrayCaptureWalker;
50
+ /**
51
+ * Per-Skeleton Collect limits (ADR 0022, dev only): override the plugin's
52
+ * `collect` defaults for THIS boundary's capture. Flat primitives — not a nested
53
+ * object — so they stay referentially stable across renders and never churn the
54
+ * dev capture effect's deps. `maxNodes` caps total surviving nodes, `maxBreadth`
55
+ * the widest level, `maxDepth` the nesting depth; an omitted `maxNodes` derives
56
+ * from the resolved breadth × depth. PRODUCTION ignores them (it never captures).
57
+ */
58
+ maxNodes?: number;
59
+ maxBreadth?: number;
60
+ maxDepth?: number;
51
61
  }
52
62
  /**
53
63
  * Props accepted by the React `<Skeleton>` component, generic over the optional
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as SkeletonProps } from "./react.core-BzMG_cDy.js";
1
+ import { t as SkeletonProps } from "./react.core-NhWc9qan.js";
2
2
 
3
3
  //#region src/react.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { t as SkeletonProps } from "./react.core-BzMG_cDy.js";
1
+ import { t as SkeletonProps } from "./react.core-NhWc9qan.js";
2
2
 
3
3
  //#region src/react.dev.d.ts
4
4
  /**
package/dist/react.dev.js CHANGED
@@ -1,5 +1,5 @@
1
- import { n as BOUNDARY_ATTR } from "./plate-DoE1HEXp.js";
2
- import { a as renderPlate, i as markStitchesLive, n as isRenderProp, o as resolveContent, r as isStitchLive, s as useSkeletonTiming, t as collectStitchNames } from "./react.core-IyFy2b_8.js";
1
+ import { n as BOUNDARY_ATTR } from "./plate-BRR6d8Se.js";
2
+ import { a as renderPlate, i as markStitchesLive, n as isRenderProp, o as resolveContent, r as isStitchLive, s as useSkeletonTiming, t as collectStitchNames } from "./react.core-DMIhXHZF.js";
3
3
  import { Suspense, useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
5
5
  //#region src/react.dev.tsx
@@ -88,7 +88,7 @@ function useLivePlate(plate) {
88
88
  * `react.tsx`.
89
89
  */
90
90
  function Skeleton(props) {
91
- const { plate, loading = false, suspense = false, fallback, delay, children, data, captureWalker } = props;
91
+ const { plate, loading = false, suspense = false, fallback, delay, children, data, captureWalker, maxNodes, maxBreadth, maxDepth } = props;
92
92
  const manual = props.root === "manual";
93
93
  const lightBoxOn = useLightBox();
94
94
  const livePlate = useLivePlate(plate);
@@ -112,6 +112,9 @@ function Skeleton(props) {
112
112
  manual,
113
113
  data,
114
114
  captureWalker,
115
+ maxNodes,
116
+ maxBreadth,
117
+ maxDepth,
115
118
  children
116
119
  });
117
120
  if (suspense) return /* @__PURE__ */ jsx(Suspense, {
@@ -128,6 +131,20 @@ function Content(props) {
128
131
  return /* @__PURE__ */ jsx(CaptureBoundary, { ...props });
129
132
  }
130
133
  /**
134
+ * Assemble the per-site Collect limits (ADR 0022) from the flat `<Skeleton>` props
135
+ * into the nested shape `captured` expects, or `undefined` when none are set (so
136
+ * the client's plugin defaults apply). Called inside the capture effect, keyed on
137
+ * the flat primitives, so it never builds an object during render.
138
+ */
139
+ function collectLimits(maxNodes, maxBreadth, maxDepth) {
140
+ if (maxNodes === void 0 && maxBreadth === void 0 && maxDepth === void 0) return void 0;
141
+ return {
142
+ maxNodes,
143
+ maxBreadth,
144
+ maxDepth
145
+ };
146
+ }
147
+ /**
131
148
  * `root="auto"` (default): the existing `display:contents` boundary wrapper marks
132
149
  * the capture seam so a parent capture stops here and references this plate by
133
150
  * name rather than re-serializing the content (ADR 0006). A render prop is called
@@ -135,7 +152,7 @@ function Content(props) {
135
152
  * element, carries the marker); its result renders inside the wrapper exactly as
136
153
  * plain children do.
137
154
  */
138
- function CaptureBoundary({ plate, delay, children, data, captureWalker }) {
155
+ function CaptureBoundary({ plate, delay, children, data, captureWalker, maxNodes, maxBreadth, maxDepth }) {
139
156
  const ref = useRef(null);
140
157
  useEffect(() => {
141
158
  const el = ref.current;
@@ -143,12 +160,16 @@ function CaptureBoundary({ plate, delay, children, data, captureWalker }) {
143
160
  return globalThis.__XRAY__?.captured(plate.name, el, {
144
161
  delay,
145
162
  walker: captureWalker,
146
- mode: "auto"
163
+ mode: "auto",
164
+ collect: collectLimits(maxNodes, maxBreadth, maxDepth)
147
165
  });
148
166
  }, [
149
167
  plate.name,
150
168
  delay,
151
- captureWalker
169
+ captureWalker,
170
+ maxNodes,
171
+ maxBreadth,
172
+ maxDepth
152
173
  ]);
153
174
  const effective = useFixtureData(plate.name, data, isRenderProp(children));
154
175
  return /* @__PURE__ */ jsx("div", {
@@ -171,7 +192,7 @@ function CaptureBoundary({ plate, delay, children, data, captureWalker }) {
171
192
  * can produce. A dev-time effect asserts the ref fired and loudly `console.error`s
172
193
  * when it did not.
173
194
  */
174
- function ManualBoundary({ plate, delay, children, data, captureWalker }) {
195
+ function ManualBoundary({ plate, delay, children, data, captureWalker, maxNodes, maxBreadth, maxDepth }) {
175
196
  const disposeRef = useRef(void 0);
176
197
  const attachedRef = useRef(false);
177
198
  const ref = useCallback((el) => {
@@ -183,12 +204,16 @@ function ManualBoundary({ plate, delay, children, data, captureWalker }) {
183
204
  disposeRef.current = globalThis.__XRAY__?.captured(plate.name, el, {
184
205
  delay,
185
206
  walker: captureWalker,
186
- mode: "manual"
207
+ mode: "manual",
208
+ collect: collectLimits(maxNodes, maxBreadth, maxDepth)
187
209
  });
188
210
  }, [
189
211
  plate.name,
190
212
  delay,
191
- captureWalker
213
+ captureWalker,
214
+ maxNodes,
215
+ maxBreadth,
216
+ maxDepth
192
217
  ]);
193
218
  useEffect(() => () => disposeRef.current?.(), []);
194
219
  useEffect(() => {
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as renderPlate, i as markStitchesLive, o as resolveContent, r as isStitchLive, s as useSkeletonTiming, t as collectStitchNames } from "./react.core-IyFy2b_8.js";
1
+ import { a as renderPlate, i as markStitchesLive, o as resolveContent, r as isStitchLive, s as useSkeletonTiming, t as collectStitchNames } from "./react.core-DMIhXHZF.js";
2
2
  import { Suspense, useEffect, useRef, useState } from "react";
3
3
  import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
4
4
  //#region src/react.tsx
@@ -5,8 +5,8 @@
5
5
  * browser console (`client.ts`) and the Vite server output (`index.ts`) print.
6
6
  *
7
7
  * Motivation: the serializer silently swallows a handful of capture omissions
8
- * (an unreadable cross-origin stylesheet, skipped pseudo-elements, a collapsed
9
- * list run, an over-the-limit subtree). Early adopters asking "why does this
8
+ * (an unreadable cross-origin stylesheet, skipped pseudo-elements, off-screen
9
+ * bones dropped at the fold, an over-the-limit subtree). Early adopters asking "why does this
10
10
  * skeleton look wrong?" had to read the serializer source to find out. These
11
11
  * diagnostics surface those omissions as data the capture process returns, so a
12
12
  * caller can log them — without the core performing any console side effects.
@@ -14,7 +14,7 @@
14
14
  * Strictly DEV-ONLY (see the plan's decisions and ADR 0008): this type is NOT a
15
15
  * public package export and MUST NEVER reach the persisted Plate JSON, the
16
16
  * `MergedPlate`, or the production adapter. It rides on the TRANSIENT
17
- * `ViewCapture.diagnostics` field only, which `captureRegime` populates and the
17
+ * `ViewIR.diagnostics` field only, which `captureView` populates and the
18
18
  * dev client strips before posting (so committed `plates/<name>.json` stays
19
19
  * byte-identical to a capture that produced no diagnostics).
20
20
  *
@@ -44,10 +44,10 @@ interface CaptureDiagnostic {
44
44
  * serializer (or an existing oversized-capture warning folded into the same
45
45
  * vocabulary). Kept as a const union so the message table is exhaustive.
46
46
  */
47
- type DiagnosticCode = 'unreadable-stylesheet' | 'unreadable-import' | 'skipped-dynamic-pseudo' | 'skipped-pseudo-element' | 'unsupported-rule' | 'dropped-tag' | 'pruned-run' | 'too-large' | 'invalid-plate-file';
47
+ type DiagnosticCode = 'unreadable-stylesheet' | 'unreadable-import' | 'skipped-dynamic-pseudo' | 'skipped-pseudo-element' | 'unsupported-rule' | 'dropped-tag' | 'dropped-subset' | 'dropped-cropped' | 'too-large' | 'invalid-plate-file';
48
48
  //#endregion
49
49
  //#region src/plate.d.ts
50
- type LeafKind = 'text' | 'media' | 'box';
50
+ type LeafKind = 'text' | 'text-block' | 'media' | 'box';
51
51
  /**
52
52
  * The shipped structural capture of a component's rendered DOM — the data a
53
53
  * Skeleton renders from (see CONTEXT.md). Self-contained: pre-serialized chunks
@@ -81,21 +81,27 @@ interface Plate {
81
81
  /**
82
82
  * One serialized child of a plate root (or of a stitch-bearing node): a
83
83
  * stitch-free HTML run (`string`), a stitch to a child plate (`{ r: name }`),
84
- * or a stitch-bearing element kept real so its inner stitches can mount
85
- * (`{ c: className, k: chunk[] }`). The framework-neutral render contract: an
86
- * adapter injects the strings and mounts a child plate at each stitch (ADR 0008).
84
+ * a stitch-bearing element kept real so its inner stitches can mount
85
+ * (`{ c: className, k: chunk[] }`), or a templated uniform run (`{ t: html, n }`):
86
+ * the cell's HTML once plus a repeat count the adapter expands to N copies (Phase
87
+ * 2). The framework-neutral render contract: an adapter injects the strings,
88
+ * mounts a child plate at each stitch, and repeats each template (ADR 0008).
87
89
  */
88
90
  type Chunk = string | {
89
91
  r: string;
90
92
  } | {
91
93
  c: string;
92
94
  k: Chunk[];
95
+ } | {
96
+ t: string;
97
+ n: number;
93
98
  };
94
99
  /**
95
- * A plate after view-merge + classify but before chunk serialization: the
96
- * build-time RenderNode tree plus its scoped CSS. Produced by `mergeViews` and
97
- * `capture`, then serialized into the shipped `Plate` (chunks) at load
98
- * (`serializePlate`). Never shipped to the client.
100
+ * A plate after the in-browser Serialize (emit + merge/gate + classify) but
101
+ * before chunk serialization: the build-time RenderNode tree plus its scoped
102
+ * CSS. Produced by `serializePlateIR` (the structural part of `StoredPlate`) and
103
+ * the single-View `capture`, then serialized into the shipped `Plate` (chunks) at
104
+ * load (`serializePlate`). Never shipped to the client.
99
105
  */
100
106
  interface MergedPlate {
101
107
  v: number;
@@ -114,6 +120,12 @@ interface PlateNode {
114
120
  * against `Plate.refs` (prod) or the live store (dev).
115
121
  */
116
122
  ref?: string;
123
+ /**
124
+ * A templated uniform run (Phase 2): render this node's subtree `count` times.
125
+ * The serializer emits the subtree's HTML once plus the count; the adapter
126
+ * expands it to N copies at render. Absent on ordinary nodes.
127
+ */
128
+ count?: number;
117
129
  kids?: PlateNode[];
118
130
  }
119
131
  /**
@@ -126,6 +138,8 @@ interface RenderNode {
126
138
  leaf?: LeafKind;
127
139
  ref?: string;
128
140
  cls?: string;
141
+ /** A templated uniform run (Phase 2): serialize this subtree once + a count the adapter repeats. */
142
+ count?: number;
129
143
  kids?: RenderNode[];
130
144
  }
131
145
  /**
@@ -146,8 +160,11 @@ interface PlateRule {
146
160
  }
147
161
  /**
148
162
  * One capture of a component within one viewport view — `[min, max)` in px,
149
- * either side open-ended when undefined. What the dev client ships to the
150
- * plugin, and what the plate file accumulates (ADR 0004).
163
+ * either side open-ended when undefined. The id-form `{ tree, rules }` shape
164
+ * `emit` projects a measured View into (ADR 0004). NO LONGER the disk/wire shape
165
+ * after the ADR 0022 cutover — `StoredPlate` (below) is. Kept as the
166
+ * framework-neutral per-View capture shape, exported for consumers reading the
167
+ * id-form projection directly.
151
168
  */
152
169
  interface ViewCapture {
153
170
  /** Viewport width at capture time. */
@@ -158,23 +175,31 @@ interface ViewCapture {
158
175
  rules: PlateRule[];
159
176
  /**
160
177
  * TRANSIENT, dev-only capture diagnostics (why this view may be lower
161
- * fidelity). Populated by `captureRegime` in the browser; the dev client logs
162
- * it and posts it (so the Vite server can print the same text), then it is
163
- * STRIPPED at the merge layer (`addCapture`) before persisting. It MUST NEVER
164
- * be persisted: it is not part of the committed `plates/<name>.json` shape,
165
- * the `isViewCapture` validator does not require it, and `addCapture` drops it
166
- * so neither it nor `mergeViews` ever writes it. Absent in production (the
167
- * prod adapter ships no capture). See diagnostics.ts and ADR 0008.
178
+ * fidelity). Captured in the browser (mirrored onto `ViewIR.diagnostics`); the
179
+ * dev client logs it and posts it alongside the `StoredPlate` (so the Vite
180
+ * server can print the same text). It MUST NEVER be persisted: it is not part
181
+ * of the committed
182
+ * `StoredPlate` disk shape, the validator does not carry it, and the POST
183
+ * envelope keeps it in a SIBLING field that never reaches the stored artifact
184
+ * (ADR 0008). Absent in production (the prod adapter ships no capture). See
185
+ * diagnostics.ts and ADR 0008.
168
186
  */
169
187
  diagnostics?: CaptureDiagnostic[];
170
188
  }
171
- /** The committed `plates/<name>.json`: per-view captures, merged at load. */
172
- interface PlateFile {
173
- v: number;
174
- name: string;
175
- /** Width thresholds (view boundaries) derived so far. */
189
+ /**
190
+ * The committed `plates/<name>.json` shape AFTER the ADR 0022 cutover: the whole
191
+ * Plate already projected in the browser (the in-browser Serialize folds emit +
192
+ * the multi-View merge/gate + classify in one session), so disk holds ONE gated,
193
+ * content-addressed tree + css instead of per-View captures merged at load. It is
194
+ * a `MergedPlate` (the structural `{tree, css}` `serializePlate` consumes) PLUS the
195
+ * `breakpoints` the HUD coverage reads — the analysis-only geometry never crosses
196
+ * the wire (ADR 0021). The gen-side runs `serializePlate(stored) → Plate` (chunks)
197
+ * at module-load, leaving the Tailwind/Bundle passthrough a clean gen-side seam.
198
+ * Replaces `PlateFile` (+ `ViewCapture`) on disk and on the `/__xray` wire.
199
+ */
200
+ interface StoredPlate extends MergedPlate {
201
+ /** Width thresholds (view boundaries); the HUD coverage derives its bands from these. */
176
202
  breakpoints: number[];
177
- views: ViewCapture[];
178
203
  }
179
204
  /** The (unserialized) plate an import resolves to before anything has been captured. */
180
205
  declare function emptyPlate(name: string): MergedPlate;
@@ -298,6 +323,11 @@ interface CaptureHook {
298
323
  delay?: number;
299
324
  walker?: unknown;
300
325
  mode?: 'auto' | 'manual';
326
+ collect?: {
327
+ maxNodes?: number;
328
+ maxBreadth?: number;
329
+ maxDepth?: number;
330
+ };
301
331
  }) => (() => void) | undefined;
302
332
  /** Light Box toggle: force every `<Skeleton>` to its skeleton, read by `<Skeleton>`. */
303
333
  lightbox?: ToggleStore;
@@ -305,8 +335,8 @@ interface CaptureHook {
305
335
  capture?: ToggleStore;
306
336
  /** Read side for `<Skeleton>`. */
307
337
  plates?: PlateStore;
308
- /** Write side for the dev client's HMR-event handler. */
309
- updatePlate?: (name: string, plate: Plate) => void;
338
+ /** Write side for the dev client's HMR-event handler. Returns whether the plate changed. */
339
+ updatePlate?: (name: string, plate: Plate) => boolean;
310
340
  /** Dev-only: run the HUD-triggered capture-all-views sweep via the extension (ADR 0010). */
311
341
  captureAllViews?: () => Promise<void>;
312
342
  /** Dev-only per-plate breakpoints + captured spans, from disk; fed by the bootstrap. */
@@ -335,4 +365,137 @@ declare global {
335
365
  var __XRAY__: CaptureHook | undefined;
336
366
  }
337
367
  //#endregion
338
- export { PlateNode as a, emptyPlate as c, PlateFile as i, MergedPlate as n, RenderNode as o, Plate as r, ViewCapture as s, LeafKind as t };
368
+ //#region src/serialize.d.ts
369
+ /**
370
+ * Browser-side capture: walk a live rendered subtree into a Plate — one node
371
+ * tree plus one self-contained, scoped CSS string (ADR 0003). The CSS is
372
+ * lifted from the author rules that matched each node, rewritten to
373
+ * plate-local selectors — it carries `@media` for free and keeps layout live
374
+ * (the M1 bake-off winner; ADR 0005).
375
+ */
376
+ interface CaptureOptions {
377
+ /** Plate name; comes from the virtual-module specifier. */
378
+ name: string;
379
+ /** Collect limits (ADR 0022): refuse a capture whose measured tree exceeds any axis. */
380
+ collect?: CollectLimits;
381
+ /**
382
+ * Optional programmable capture walker (ADR 0018). The declarative `data-xr-*`
383
+ * annotations cover the 90%; this is the escape hatch for the 10% a markup
384
+ * attribute cannot express. Capture-only: it shapes the resulting `PlateNode`
385
+ * tree but is never persisted into the Plate, and the production adapter never
386
+ * captures so it ignores this entirely.
387
+ */
388
+ walker?: XrayCaptureWalker;
389
+ /**
390
+ * Whether the top-level capture roots ARE this plate's own boundary, so the
391
+ * stitch guard must be SKIPPED for them (ADR 0006/0014, finding #6). True ONLY
392
+ * for a MANUAL root: there `site.el` is the consumer's own element and its
393
+ * `data-xr-boundary` marks the boundary of THIS plate, so the root must be
394
+ * serialized as a real Entry (its box/classes/padding/border) rather than a
395
+ * stitch-to-itself. Defaults to `false` — the auto path, where the top-level
396
+ * roots are the wrapper's CHILDREN and any boundary marker on a direct-child
397
+ * root is a NESTED `<Skeleton>` that MUST stitch (the capture-leak guard).
398
+ */
399
+ captureRootIsBoundary?: boolean;
400
+ }
401
+ /**
402
+ * What a capture walker decides for one element (ADR 0018; ADR 0022 §5). The
403
+ * `applyWalker` PASS reads the decision and applies it to the measured IR node —
404
+ * a walker NEVER hand-builds a `PlateNode`, so xray keeps ownership of the Plate
405
+ * format. `ignore` drops the element + its subtree; `bone` collapses it to one
406
+ * Bone leaf (inferring the kind when omitted); `keep` defers to the default
407
+ * classifier (`markInk`).
408
+ */
409
+ type WalkerDecision = {
410
+ type: 'ignore';
411
+ } | {
412
+ type: 'bone';
413
+ kind?: LeafKind;
414
+ } | {
415
+ type: 'keep';
416
+ };
417
+ /**
418
+ * The context handed to a capture walker's `element` hook (ADR 0018). The
419
+ * helpers are PURE — each returns a `WalkerDecision` the `applyWalker` pass
420
+ * applies; they do NOT mutate. `el` is the element under consideration.
421
+ */
422
+ interface CaptureWalkerContext {
423
+ /** The element currently being classified. */
424
+ el: Element;
425
+ /** Drop this element AND its whole subtree (no bone). */
426
+ ignore(): WalkerDecision;
427
+ /**
428
+ * Collapse this element's subtree into ONE Bone leaf. `kind` omitted shares
429
+ * the default classifier's kind-inference heuristic (`inferLeafKind`; ADR 0018,
430
+ * Q4 — ONE code path).
431
+ */
432
+ bone(opts?: {
433
+ kind?: LeafKind;
434
+ }): WalkerDecision;
435
+ /** Defer to the default classifier (`markInk`) — recurse into the subtree. */
436
+ keep(): WalkerDecision;
437
+ }
438
+ /**
439
+ * The programmable capture walker (ADR 0018; ADR 0022 §5). `element` is offered
440
+ * each non-boundary element TOP-DOWN over the measured IR (the `applyWalker`
441
+ * pass, NOT the DOM walk) and returns a `WalkerDecision` the pass applies. The
442
+ * declarative `data-xr-*` annotations cover the 90%; this is the 10% escape
443
+ * hatch for what a markup attribute cannot express.
444
+ */
445
+ interface XrayCaptureWalker {
446
+ /**
447
+ * Called for every non-boundary element node (a stitch carries no element and
448
+ * is skipped — ADR 0006 — so a boundary never reaches here). Return one of
449
+ * `ctx.ignore()`, `ctx.bone()`, or `ctx.keep()`. An `ignore`/`bone` decision
450
+ * stops the descent (the subtree lowers); `keep` recurses.
451
+ */
452
+ element?(ctx: CaptureWalkerContext): WalkerDecision;
453
+ }
454
+ /**
455
+ * Typed identity wrapper for authoring a capture walker (ADR 0018) — the
456
+ * `defineConfig` pattern. It returns its argument unchanged at runtime; its only
457
+ * job is to infer and check the `XrayCaptureWalker` shape at the call site so an
458
+ * author gets completion and a typo in a hook name is caught.
459
+ */
460
+ declare function defineXrayCaptureWalker(walker: XrayCaptureWalker): XrayCaptureWalker;
461
+ /**
462
+ * Thrown when a subtree has more nodes than the capture limit — almost always
463
+ * a `<Skeleton>` sitting too high in the tree. Raised after the cheap walk and
464
+ * before the O(rules × nodes) CSS extraction, so an oversized capture is
465
+ * refused without freezing the main thread on it.
466
+ */
467
+ /** The measured size of a capture's surviving tree, one number per Collect axis (ADR 0022). */
468
+ type CaptureSize = {
469
+ nodes: number;
470
+ breadth: number;
471
+ depth: number;
472
+ };
473
+ /** Which Collect axis a too-large capture tripped. */
474
+ type CollectAxis = 'nodes' | 'breadth' | 'depth';
475
+ declare class CaptureTooLargeError extends Error {
476
+ /** The full measured size of the surviving tree that tripped the gate. */
477
+ readonly size: CaptureSize;
478
+ /** Which axis exceeded its cap, and the cap it exceeded. */
479
+ readonly axis: CollectAxis;
480
+ readonly max: number;
481
+ /** Back-compat alias for `size.nodes` (the sole axis before breadth/depth). */
482
+ readonly nodeCount: number;
483
+ /** Folds the oversized-capture warning into the shared diagnostics vocabulary (diagnostics.ts). */
484
+ readonly code: "too-large";
485
+ constructor(size: CaptureSize, axis: CollectAxis, max: number);
486
+ }
487
+ /**
488
+ * Collect-stage size limits (ADR 0022): refuse to capture a boundary whose
489
+ * measured tree exceeds any axis. `maxNodes` caps the total surviving-node count;
490
+ * `maxBreadth` the widest level (most nodes at any one depth); `maxDepth` the
491
+ * nesting depth. Each axis is enforced only when its cap is defined. The defaults
492
+ * and the `maxNodes = round(maxBreadth × maxDepth / 4)` derivation live in the
493
+ * client (where the other defaults live); this core only enforces what it is handed.
494
+ */
495
+ type CollectLimits = {
496
+ maxNodes?: number;
497
+ maxBreadth?: number;
498
+ maxDepth?: number;
499
+ };
500
+ //#endregion
501
+ export { emptyPlate as _, CollectAxis as a, XrayCaptureWalker as c, MergedPlate as d, Plate as f, ViewCapture as g, StoredPlate as h, CaptureWalkerContext as i, defineXrayCaptureWalker as l, RenderNode as m, CaptureSize as n, CollectLimits as o, PlateNode as p, CaptureTooLargeError as r, WalkerDecision as s, CaptureOptions as t, LeafKind as u, CaptureDiagnostic as v };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hueest/xray",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Auto-captured skeleton loading screens for Vite — capture the structure, ship the plate.",
5
5
  "keywords": [
6
6
  "loading",
@@ -58,6 +58,7 @@
58
58
  "access": "public"
59
59
  },
60
60
  "dependencies": {
61
+ "@chenglou/pretext": "0.0.8",
61
62
  "devalue": "5.8.1",
62
63
  "valibot": "1.4.1"
63
64
  },