@hueest/xray 0.2.0 → 0.3.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.
package/dist/core.js CHANGED
@@ -1,212 +1,14 @@
1
- import { a as ROOT_ATTR, c as SPEC_GATE, d as emptyPlate, i as NODE_CLASS, o as ROOT_CLASS, r as LEAF_CLASS, t as BASE_CSS } from "./plate-DoE1HEXp.js";
2
- import { a as captureRegime, i as capture, n as regimeFor, o as defineXrayCaptureWalker, r as CaptureTooLargeError, u as classify } from "./breakpoints-CMoFUUOv.js";
1
+ import { a as ROOT_ATTR, d as emptyPlate, o as ROOT_CLASS, t as BASE_CSS } from "./plate-BRR6d8Se.js";
2
+ import { a as serializePlate, c as defineXrayCaptureWalker, i as collectRefs, l as runPlatePasses, o as CaptureTooLargeError, s as captureView, t as serializePlateIR } from "./project-DORoPAo7.js";
3
3
  import { t as escapeStyleText } from "./css-escape-N7bOusGW.js";
4
- //#region src/chunk.ts
5
- /**
6
- * Serialize a merged plate's RenderNode tree into the shipped `Plate` (ADR
7
- * 0008). This is the framework-neutral half of rendering: a skeleton is static,
8
- * text-free DOM, so the structure is flattened to HTML strings here, at build,
9
- * once — instead of every adapter walking a tree on the latency-critical first
10
- * paint. The adapter only injects the strings and mounts a child plate at each
11
- * stitch (see the React adapters). No escaping risk: bones carry only the
12
- * content-addressed class tokens (ADR 0007), never user content.
13
- */
14
- function nodeClass(node) {
15
- return NODE_CLASS + (node.leaf ? ` ${LEAF_CLASS} ${LEAF_CLASS}-${node.leaf}` : "") + (node.cls ? ` ${node.cls}` : "");
16
- }
17
- /**
18
- * Whether this subtree holds a stitch (`ref`) anywhere — a stitched subtree
19
- * can't collapse to a flat string, since a `ref` resolves to a child plate at
20
- * render time and must stay a mount point.
21
- */
22
- function hasStitch(node) {
23
- return node.ref !== void 0 || (node.kids?.some(hasStitch) ?? false);
24
- }
25
- /** Serialize a stitch-free subtree to one HTML string (caller guarantees no `ref` below). */
26
- function toHtml(node) {
27
- const kids = node.kids ? node.kids.map(toHtml).join("") : "";
28
- return `<div class="${nodeClass(node)}">${kids}</div>`;
29
- }
30
- /**
31
- * Serialize a node list into chunks: each maximal stitch-free run becomes one
32
- * HTML `string`; a `ref` node becomes `{ r }`; a node that isn't itself a stitch
33
- * but holds one deeper stays a real element (`{ c, k }`) and recurses. Mirrors
34
- * the element shape the runtime renderer would otherwise build per node.
35
- */
36
- function toChunks(nodes) {
37
- const out = [];
38
- let buf = "";
39
- const flush = () => {
40
- if (buf) {
41
- out.push(buf);
42
- buf = "";
43
- }
44
- };
45
- for (const node of nodes) {
46
- if (!hasStitch(node)) {
47
- buf += toHtml(node);
48
- continue;
49
- }
50
- flush();
51
- if (node.ref !== void 0) out.push({ r: node.ref });
52
- else out.push({
53
- c: nodeClass(node),
54
- k: toChunks(node.kids ?? [])
55
- });
56
- }
57
- flush();
58
- return out;
59
- }
60
- /**
61
- * Turn a merged plate (RenderNode tree) into the shipped plate (chunks). The
62
- * root's own content classes travel as `rootCls` (the adapter puts them on the
63
- * root element); its children become `chunks`. A capture-less plate has null
64
- * chunks and renders the fallback.
65
- */
66
- function serializePlate(merged) {
67
- const { v, name, tree, css } = merged;
68
- if (!tree) return {
69
- v,
70
- name,
71
- chunks: null,
72
- css
73
- };
74
- const chunks = toChunks(tree.kids ?? []);
75
- return tree.cls ? {
76
- v,
77
- name,
78
- chunks,
79
- rootCls: tree.cls,
80
- css
81
- } : {
82
- v,
83
- name,
84
- chunks,
85
- css
86
- };
87
- }
88
- //#endregion
89
- //#region src/merge.ts
90
- /**
91
- * Turn the per-view captures in a plate file into the one plate a Skeleton
92
- * renders.
93
- *
94
- * Each view is kept whole — its own subtree, its own ids, its own rules —
95
- * and shown only across the viewport range it owns, via `@media display:none`
96
- * gates. We do NOT merge the views' trees into one. An earlier design (ADR
97
- * 0004) did, sharing common structure and forking the rest, but aligning two
98
- * genuinely-different DOM trees (mobile stacked vs desktop two-column) without
99
- * stable keys proved unreliable: every heuristic mis-paired some nodes and
100
- * remapped one view's rules onto another's elements, corrupting both. A lone
101
- * capture is pixel-faithful; keeping each view lone preserves that. The cost
102
- * is a larger plate (roughly the sum of the views) — bounded by the plate
103
- * size cap — in exchange for correctness.
104
- *
105
- * Pure data-in/data-out — runs in node (plugin load) and in tests.
106
- */
107
- function mergeViews(file) {
108
- const views = file.views;
109
- if (views.length === 0) return emptyPlate(file.name);
110
- if (views.length === 1) {
111
- const [only] = views;
112
- if (!only) return emptyPlate(file.name);
113
- const { tree, css } = classify(only.rules, only.tree, file.name);
114
- return {
115
- v: 1,
116
- name: file.name,
117
- tree,
118
- css
119
- };
120
- }
121
- const starts = views.map((r) => regimeFor(r.width, file.breakpoints).min ?? 0);
122
- let nextId = 1;
123
- const rootKids = [];
124
- const rules = [];
125
- views.forEach((view, i) => {
126
- const idMap = /* @__PURE__ */ new Map();
127
- const kids = (view.tree.kids ?? []).map((kid) => cloneWithIds(kid, idMap, () => nextId++));
128
- rootKids.push(...kids);
129
- rules.push(...remapRules(view.rules, idMap));
130
- const lo = i === 0 ? 0 : starts[i] ?? 0;
131
- const hi = i === views.length - 1 ? Number.POSITIVE_INFINITY : starts[i + 1] ?? 0;
132
- for (const kid of kids) {
133
- if (lo > 0) rules.push(gate(kid.id, `(max-width: ${lo - .02}px)`));
134
- if (hi !== Number.POSITIVE_INFINITY) rules.push(gate(kid.id, `(min-width: ${hi}px)`));
135
- }
136
- });
137
- const { tree, css } = classify(rules, {
138
- id: 0,
139
- kids: rootKids
140
- }, file.name);
141
- return {
142
- v: 1,
143
- name: file.name,
144
- tree,
145
- css
146
- };
147
- }
148
- function gate(id, condition) {
149
- return {
150
- ids: [id],
151
- decls: ["display: none !important"],
152
- media: [condition],
153
- spec: SPEC_GATE,
154
- order: id
155
- };
156
- }
157
- /** Deep-clone a captured subtree into fresh ids, recording the mapping. */
158
- function cloneWithIds(node, idMap, allocate) {
159
- const id = allocate();
160
- idMap.set(node.id, id);
161
- const kids = node.kids?.map((kid) => cloneWithIds(kid, idMap, allocate));
162
- return {
163
- id,
164
- ...node.leaf ? { leaf: node.leaf } : {},
165
- ...node.ref !== void 0 ? { ref: node.ref } : {},
166
- ...kids ? { kids } : {}
167
- };
168
- }
169
- /** Distinct plate names referenced by the tree's stitches, in first-seen order. */
170
- function collectRefs(tree) {
171
- const names = [];
172
- const seen = /* @__PURE__ */ new Set();
173
- const visit = (node) => {
174
- if (node.ref !== void 0 && !seen.has(node.ref)) {
175
- seen.add(node.ref);
176
- names.push(node.ref);
177
- }
178
- for (const kid of node.kids ?? []) visit(kid);
179
- };
180
- if (tree) visit(tree);
181
- return names;
182
- }
183
- function remapRules(rules, idMap) {
184
- return rules.map((rule) => ({
185
- ...rule,
186
- ids: rule.ids.map((id) => idMap.get(id) ?? id)
187
- }));
188
- }
189
- /** Fold a fresh capture into the plate file: union breakpoints, one capture per view. */
190
- function addCapture(file, name, capture, breakpoints) {
191
- const allBreakpoints = [...new Set([...file?.breakpoints ?? [], ...breakpoints])].toSorted((a, b) => a - b);
192
- const spanKey = (width) => JSON.stringify(regimeFor(width, allBreakpoints));
193
- const { diagnostics: _diagnostics, ...persisted } = capture;
194
- const views = [...file?.views ?? [], persisted];
195
- const byView = /* @__PURE__ */ new Map();
196
- for (const view of views) byView.set(spanKey(view.width), view);
197
- return {
198
- v: 1,
199
- name,
200
- breakpoints: allBreakpoints,
201
- views: [...byView.values()].toSorted((a, b) => a.width - b.width)
202
- };
203
- }
204
- //#endregion
205
4
  //#region src/core.ts
206
5
  /**
207
- * Capture a live DOM subtree straight to a renderable Plate `capture` followed
208
- * by `serializePlate` in one call. `roots` are the top-level elements to capture
209
- * (a component's rendered roots). Pass the result to a `<Skeleton plate>`.
6
+ * Capture a live DOM subtree straight to a renderable Plate. Runs the same
7
+ * uniform pipeline the dev client does for a single View measure (`captureView`),
8
+ * reduce (`runPlatePasses`), project (`serializePlateIR` the structured
9
+ * `StoredPlate`), then lower to chunks (`serializePlate`) — in one call. `roots`
10
+ * are the top-level elements to capture (a component's rendered roots). Pass the
11
+ * result to a `<Skeleton plate>`.
210
12
  *
211
13
  * `options.walker` is the public programmable capture walker (ADR 0018, Q2 —
212
14
  * yes, public): the same `XrayCaptureWalker` the `<Skeleton captureWalker>` prop
@@ -214,7 +16,13 @@ function addCapture(file, name, capture, breakpoints) {
214
16
  * capture-only and never persisted into the returned Plate.
215
17
  */
216
18
  function captureElement(roots, options) {
217
- return serializePlate(capture(roots, options));
19
+ const plate = captureView(roots, {
20
+ walker: options.walker,
21
+ captureRootIsBoundary: options.captureRootIsBoundary
22
+ });
23
+ plate.name = options.name;
24
+ runPlatePasses(plate);
25
+ return serializePlate(serializePlateIR(plate));
218
26
  }
219
27
  /**
220
28
  * Render a stitch-free Plate to a self-contained HTML string — the
@@ -228,9 +36,24 @@ function captureElement(roots, options) {
228
36
  function renderPlateHtml(plate) {
229
37
  const chunks = plate.chunks;
230
38
  if (!chunks) return "";
231
- if (chunks.length !== 1 || typeof chunks[0] !== "string") throw new Error("xray: renderPlateHtml renders stitch-free plates only — use a framework adapter for stitched plates");
232
39
  const className = plate.rootCls ? `${ROOT_CLASS} ${plate.rootCls}` : ROOT_CLASS;
233
- return `<div ${ROOT_ATTR}="${plate.name}" class="${className}" aria-hidden="true" aria-busy="true"><style>${BASE_CSS}\n${escapeStyleText(plate.css)}</style>${chunks[0]}</div>`;
40
+ return `<div ${ROOT_ATTR}="${plate.name}" class="${className}" aria-hidden="true" aria-busy="true"><style>${BASE_CSS}\n${escapeStyleText(plate.css)}</style>${chunksToHtml(chunks)}</div>`;
41
+ }
42
+ /**
43
+ * Flatten chunks to one HTML string. A `string` concatenates; a template chunk
44
+ * (`{ t, n }`) expands to its cell repeated `n` times (the cell is stitch-free by
45
+ * construction); a stitch-free wrapper (`{ c, k }`) recurses inside a real `<div>`.
46
+ * A stitch (`{ r }`) resolves to a child plate at render and needs a framework
47
+ * adapter to mount, so a stitched plate throws here. Chunk HTML is xray-generated
48
+ * and interpolated raw, exactly as the single-string path was before.
49
+ */
50
+ function chunksToHtml(chunks) {
51
+ let html = "";
52
+ for (const chunk of chunks) if (typeof chunk === "string") html += chunk;
53
+ else if ("t" in chunk) html += chunk.t.repeat(chunk.n);
54
+ else if ("r" in chunk) throw new Error("xray: renderPlateHtml renders stitch-free plates only — use a framework adapter for stitched plates");
55
+ else html += `<div class="${chunk.c}">${chunksToHtml(chunk.k)}</div>`;
56
+ return html;
234
57
  }
235
58
  //#endregion
236
- export { CaptureTooLargeError, addCapture, capture, captureElement, captureRegime, collectRefs, defineXrayCaptureWalker, emptyPlate, mergeViews, renderPlateHtml, serializePlate };
59
+ export { CaptureTooLargeError, captureElement, collectRefs, defineXrayCaptureWalker, emptyPlate, renderPlateHtml, serializePlate };
package/dist/index.d.ts CHANGED
@@ -7,8 +7,8 @@ import { Plugin } from "vite";
7
7
  * browser console (`client.ts`) and the Vite server output (`index.ts`) print.
8
8
  *
9
9
  * Motivation: the serializer silently swallows a handful of capture omissions
10
- * (an unreadable cross-origin stylesheet, skipped pseudo-elements, a collapsed
11
- * list run, an over-the-limit subtree). Early adopters asking "why does this
10
+ * (an unreadable cross-origin stylesheet, skipped pseudo-elements, off-screen
11
+ * bones dropped at the fold, an over-the-limit subtree). Early adopters asking "why does this
12
12
  * skeleton look wrong?" had to read the serializer source to find out. These
13
13
  * diagnostics surface those omissions as data the capture process returns, so a
14
14
  * caller can log them — without the core performing any console side effects.
@@ -16,7 +16,7 @@ import { Plugin } from "vite";
16
16
  * Strictly DEV-ONLY (see the plan's decisions and ADR 0008): this type is NOT a
17
17
  * public package export and MUST NEVER reach the persisted Plate JSON, the
18
18
  * `MergedPlate`, or the production adapter. It rides on the TRANSIENT
19
- * `ViewCapture.diagnostics` field only, which `captureRegime` populates and the
19
+ * `ViewIR.diagnostics` field only, which `captureView` populates and the
20
20
  * dev client strips before posting (so committed `plates/<name>.json` stays
21
21
  * byte-identical to a capture that produced no diagnostics).
22
22
  *
@@ -46,10 +46,10 @@ interface CaptureDiagnostic {
46
46
  * serializer (or an existing oversized-capture warning folded into the same
47
47
  * vocabulary). Kept as a const union so the message table is exhaustive.
48
48
  */
49
- type DiagnosticCode = 'unreadable-stylesheet' | 'unreadable-import' | 'skipped-dynamic-pseudo' | 'skipped-pseudo-element' | 'unsupported-rule' | 'dropped-tag' | 'pruned-run' | 'too-large' | 'invalid-plate-file';
49
+ 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';
50
50
  //#endregion
51
51
  //#region src/plate.d.ts
52
- type LeafKind = 'text' | 'media' | 'box';
52
+ type LeafKind = 'text' | 'text-block' | 'media' | 'box';
53
53
  /**
54
54
  * The shipped structural capture of a component's rendered DOM — the data a
55
55
  * Skeleton renders from (see CONTEXT.md). Self-contained: pre-serialized chunks
@@ -83,21 +83,27 @@ interface Plate {
83
83
  /**
84
84
  * One serialized child of a plate root (or of a stitch-bearing node): a
85
85
  * stitch-free HTML run (`string`), a stitch to a child plate (`{ r: name }`),
86
- * or a stitch-bearing element kept real so its inner stitches can mount
87
- * (`{ c: className, k: chunk[] }`). The framework-neutral render contract: an
88
- * adapter injects the strings and mounts a child plate at each stitch (ADR 0008).
86
+ * a stitch-bearing element kept real so its inner stitches can mount
87
+ * (`{ c: className, k: chunk[] }`), or a templated uniform run (`{ t: html, n }`):
88
+ * the cell's HTML once plus a repeat count the adapter expands to N copies (Phase
89
+ * 2). The framework-neutral render contract: an adapter injects the strings,
90
+ * mounts a child plate at each stitch, and repeats each template (ADR 0008).
89
91
  */
90
92
  type Chunk = string | {
91
93
  r: string;
92
94
  } | {
93
95
  c: string;
94
96
  k: Chunk[];
97
+ } | {
98
+ t: string;
99
+ n: number;
95
100
  };
96
101
  /**
97
- * A plate after view-merge + classify but before chunk serialization: the
98
- * build-time RenderNode tree plus its scoped CSS. Produced by `mergeViews` and
99
- * `capture`, then serialized into the shipped `Plate` (chunks) at load
100
- * (`serializePlate`). Never shipped to the client.
102
+ * A plate after the in-browser Serialize (emit + merge/gate + classify) but
103
+ * before chunk serialization: the build-time RenderNode tree plus its scoped
104
+ * CSS. Produced by `serializePlateIR` (the structural part of `StoredPlate`) and
105
+ * the single-View `capture`, then serialized into the shipped `Plate` (chunks) at
106
+ * load (`serializePlate`). Never shipped to the client.
101
107
  */
102
108
  interface MergedPlate {
103
109
  v: number;
@@ -116,6 +122,12 @@ interface PlateNode {
116
122
  * against `Plate.refs` (prod) or the live store (dev).
117
123
  */
118
124
  ref?: string;
125
+ /**
126
+ * A templated uniform run (Phase 2): render this node's subtree `count` times.
127
+ * The serializer emits the subtree's HTML once plus the count; the adapter
128
+ * expands it to N copies at render. Absent on ordinary nodes.
129
+ */
130
+ count?: number;
119
131
  kids?: PlateNode[];
120
132
  }
121
133
  /**
@@ -128,6 +140,8 @@ interface RenderNode {
128
140
  leaf?: LeafKind;
129
141
  ref?: string;
130
142
  cls?: string;
143
+ /** A templated uniform run (Phase 2): serialize this subtree once + a count the adapter repeats. */
144
+ count?: number;
131
145
  kids?: RenderNode[];
132
146
  }
133
147
  /**
@@ -148,8 +162,11 @@ interface PlateRule {
148
162
  }
149
163
  /**
150
164
  * One capture of a component within one viewport view — `[min, max)` in px,
151
- * either side open-ended when undefined. What the dev client ships to the
152
- * plugin, and what the plate file accumulates (ADR 0004).
165
+ * either side open-ended when undefined. The id-form `{ tree, rules }` shape
166
+ * `emit` projects a measured View into (ADR 0004). NO LONGER the disk/wire shape
167
+ * after the ADR 0022 cutover — `StoredPlate` (below) is. Kept as the
168
+ * framework-neutral per-View capture shape, exported for consumers reading the
169
+ * id-form projection directly.
153
170
  */
154
171
  interface ViewCapture {
155
172
  /** Viewport width at capture time. */
@@ -160,23 +177,31 @@ interface ViewCapture {
160
177
  rules: PlateRule[];
161
178
  /**
162
179
  * TRANSIENT, dev-only capture diagnostics (why this view may be lower
163
- * fidelity). Populated by `captureRegime` in the browser; the dev client logs
164
- * it and posts it (so the Vite server can print the same text), then it is
165
- * STRIPPED at the merge layer (`addCapture`) before persisting. It MUST NEVER
166
- * be persisted: it is not part of the committed `plates/<name>.json` shape,
167
- * the `isViewCapture` validator does not require it, and `addCapture` drops it
168
- * so neither it nor `mergeViews` ever writes it. Absent in production (the
169
- * prod adapter ships no capture). See diagnostics.ts and ADR 0008.
180
+ * fidelity). Captured in the browser (mirrored onto `ViewIR.diagnostics`); the
181
+ * dev client logs it and posts it alongside the `StoredPlate` (so the Vite
182
+ * server can print the same text). It MUST NEVER be persisted: it is not part
183
+ * of the committed
184
+ * `StoredPlate` disk shape, the validator does not carry it, and the POST
185
+ * envelope keeps it in a SIBLING field that never reaches the stored artifact
186
+ * (ADR 0008). Absent in production (the prod adapter ships no capture). See
187
+ * diagnostics.ts and ADR 0008.
170
188
  */
171
189
  diagnostics?: CaptureDiagnostic[];
172
190
  }
173
- /** The committed `plates/<name>.json`: per-view captures, merged at load. */
174
- interface PlateFile {
175
- v: number;
176
- name: string;
177
- /** Width thresholds (view boundaries) derived so far. */
191
+ /**
192
+ * The committed `plates/<name>.json` shape AFTER the ADR 0022 cutover: the whole
193
+ * Plate already projected in the browser (the in-browser Serialize folds emit +
194
+ * the multi-View merge/gate + classify in one session), so disk holds ONE gated,
195
+ * content-addressed tree + css instead of per-View captures merged at load. It is
196
+ * a `MergedPlate` (the structural `{tree, css}` `serializePlate` consumes) PLUS the
197
+ * `breakpoints` the HUD coverage reads — the analysis-only geometry never crosses
198
+ * the wire (ADR 0021). The gen-side runs `serializePlate(stored) → Plate` (chunks)
199
+ * at module-load, leaving the Tailwind/Bundle passthrough a clean gen-side seam.
200
+ * Replaces `PlateFile` (+ `ViewCapture`) on disk and on the `/__xray` wire.
201
+ */
202
+ interface StoredPlate extends MergedPlate {
203
+ /** Width thresholds (view boundaries); the HUD coverage derives its bands from these. */
178
204
  breakpoints: number[];
179
- views: ViewCapture[];
180
205
  }
181
206
  /**
182
207
  * A dev-only sticky boolean toggle (get/set/subscribe), persisted per tab.
@@ -305,8 +330,8 @@ interface CaptureHook {
305
330
  capture?: ToggleStore;
306
331
  /** Read side for `<Skeleton>`. */
307
332
  plates?: PlateStore;
308
- /** Write side for the dev client's HMR-event handler. */
309
- updatePlate?: (name: string, plate: Plate) => void;
333
+ /** Write side for the dev client's HMR-event handler. Returns whether the plate changed. */
334
+ updatePlate?: (name: string, plate: Plate) => boolean;
310
335
  /** Dev-only: run the HUD-triggered capture-all-views sweep via the extension (ADR 0010). */
311
336
  captureAllViews?: () => Promise<void>;
312
337
  /** Dev-only per-plate breakpoints + captured spans, from disk; fed by the bootstrap. */
@@ -335,29 +360,17 @@ declare global {
335
360
  var __XRAY__: CaptureHook | undefined;
336
361
  }
337
362
  //#endregion
338
- //#region src/merge.d.ts
363
+ //#region src/chunk.d.ts
339
364
  /**
340
- * Turn the per-view captures in a plate file into the one plate a Skeleton
341
- * renders.
342
- *
343
- * Each view is kept whole its own subtree, its own ids, its own rules —
344
- * and shown only across the viewport range it owns, via `@media display:none`
345
- * gates. We do NOT merge the views' trees into one. An earlier design (ADR
346
- * 0004) did, sharing common structure and forking the rest, but aligning two
347
- * genuinely-different DOM trees (mobile stacked vs desktop two-column) without
348
- * stable keys proved unreliable: every heuristic mis-paired some nodes and
349
- * remapped one view's rules onto another's elements, corrupting both. A lone
350
- * capture is pixel-faithful; keeping each view lone preserves that. The cost
351
- * is a larger plate (roughly the sum of the views) — bounded by the plate
352
- * size cap — in exchange for correctness.
353
- *
354
- * Pure data-in/data-out — runs in node (plugin load) and in tests.
365
+ * Distinct plate names referenced by the tree's stitches, in first-seen order.
366
+ * Walks a `RenderNode` tree (the stored, post-classify shape), so it drives the
367
+ * gen-side stitch import graph straight off a `StoredPlate.tree`
368
+ * (`renderPlateModule`, index.ts). Relocated here from the now-deleted `merge.ts`
369
+ * in the ADR 0022 cutover — it is the one node-side primitive that survived the
370
+ * `mergeViews`/`addCapture` removal (the multi-View merge/gate now lives in the
371
+ * in-browser `serializePlateIR`, project.ts).
355
372
  */
356
- declare function mergeViews(file: PlateFile): MergedPlate;
357
- /** Distinct plate names referenced by the tree's stitches, in first-seen order. */
358
373
  declare function collectRefs(tree: RenderNode | null): string[];
359
- /** Fold a fresh capture into the plate file: union breakpoints, one capture per view. */
360
- declare function addCapture(file: PlateFile | null, name: string, capture: ViewCapture, breakpoints: readonly number[]): PlateFile;
361
374
  //#endregion
362
375
  //#region src/name.d.ts
363
376
  /**
@@ -488,16 +501,16 @@ interface CaptureServer {
488
501
  };
489
502
  }
490
503
  /**
491
- * Per-plate breakpoints + captured spans, read from the committed plate files.
504
+ * Per-plate breakpoints + derived coverage bands, read from the committed
505
+ * StoredPlate files. The `spans` derive from each plate's `breakpoints` (ADR 0022
506
+ * cutover — per-View spans no longer exist on disk; a StoredPlate sweeps all
507
+ * widths in one session, so its coverage is complete).
492
508
  *
493
509
  * @internal
494
510
  */
495
511
  declare function readCoverage(dir: string): Record<string, {
496
512
  breakpoints: number[];
497
- spans: {
498
- min?: number;
499
- max?: number;
500
- }[];
513
+ spans: Span[];
501
514
  }>;
502
515
  /**
503
516
  * All recorded Fixtures as `{ <name>: <devalueString> }`, read from the
@@ -522,7 +535,12 @@ declare function readFixtures(dir: string): Record<string, string>;
522
535
  */
523
536
  declare function handleFixturePost(req: CaptureRequest, res: CaptureResponse, fixturePath: (name: string) => string): void;
524
537
  /**
525
- * Fold a posted capture into its plate file and hot-swap the plate in place.
538
+ * Validate a posted `StoredPlate` envelope, write it AS-IS, and hot-swap the
539
+ * plate in place. After the ADR 0022 cutover the dev client posts a finished
540
+ * StoredPlate (the whole Plate already projected in the browser) inside a
541
+ * `{ plate, diagnostics? }` envelope — there is NO server-side accumulation/merge
542
+ * anymore. `diagnostics` is a SIBLING field the server logs and then drops; it
543
+ * never reaches the stored artifact (ADR 0008).
526
544
  *
527
545
  * @internal
528
546
  */
@@ -536,8 +554,13 @@ declare function handleCapturePost(req: CaptureRequest, res: CaptureResponse, se
536
554
  * import itself); an unknown child resolves to its own empty plate, never a
537
555
  * build break.
538
556
  *
557
+ * `name` defaults to the plate's own `name`, but the loader passes the
558
+ * PATH-derived name so a hand-edited StoredPlate whose `name` field drifted from
559
+ * its filename still serializes and self-references under the canonical name
560
+ * (`collectRefs` reads `stored.tree`, a RenderNode, unchanged by the cutover).
561
+ *
539
562
  * @internal
540
563
  */
541
- declare function renderPlateModule(merged: MergedPlate): string;
564
+ declare function renderPlateModule(merged: MergedPlate, name?: string): string;
542
565
  //#endregion
543
- export { type LeafKind, type Plate, type PlateFile, type PlateNode, type PlateRule, type ViewCapture, XrayOptions, addCapture, collectRefs, handleCapturePost, handleFixturePost, isValidPlateName, mergeViews, readCoverage, readFixtures, renderPlateModule, xrayVitePlugin };
566
+ export { type LeafKind, type Plate, type PlateNode, type PlateRule, type StoredPlate, type ViewCapture, XrayOptions, collectRefs, handleCapturePost, handleFixturePost, isValidPlateName, readCoverage, readFixtures, renderPlateModule, xrayVitePlugin };