@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/client.d.ts +28 -15
- package/dist/client.js +220 -78
- package/dist/core.d.ts +16 -27
- package/dist/core.js +33 -210
- package/dist/index.d.ts +80 -57
- package/dist/index.js +235 -596
- package/dist/{plate-Bv6W-GkA.d.ts → plate-BIr62u7l.d.ts} +55 -30
- package/dist/{plate-DoE1HEXp.js → plate-BRR6d8Se.js} +8 -4
- package/dist/project-DORoPAo7.js +10224 -0
- package/dist/{react.core-BzMG_cDy.d.ts → react.core-BUj8ziwh.d.ts} +3 -3
- package/dist/{react.core-IyFy2b_8.js → react.core-DMIhXHZF.js} +6 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.dev.d.ts +1 -1
- package/dist/react.dev.js +2 -2
- package/dist/react.js +1 -1
- package/dist/serialize-Cs7hUxmK.d.ts +107 -0
- package/package.json +2 -1
- package/dist/breakpoints-CMoFUUOv.js +0 -1393
- package/dist/serialize-B--oY2bV.d.ts +0 -143
package/dist/core.js
CHANGED
|
@@ -1,212 +1,14 @@
|
|
|
1
|
-
import { a as ROOT_ATTR,
|
|
2
|
-
import { a as
|
|
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
|
|
208
|
-
*
|
|
209
|
-
* (
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
11
|
-
*
|
|
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
|
-
* `
|
|
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' | '
|
|
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
|
-
*
|
|
87
|
-
* (`{ c: className, k: chunk[] }`)
|
|
88
|
-
*
|
|
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
|
|
98
|
-
* build-time RenderNode tree plus its scoped
|
|
99
|
-
*
|
|
100
|
-
*
|
|
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.
|
|
152
|
-
*
|
|
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).
|
|
164
|
-
* it and posts it
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* prod adapter ships no capture). See
|
|
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
|
-
/**
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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) =>
|
|
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/
|
|
363
|
+
//#region src/chunk.d.ts
|
|
339
364
|
/**
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
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 +
|
|
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
|
-
*
|
|
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
|
|
566
|
+
export { type LeafKind, type Plate, type PlateNode, type PlateRule, type StoredPlate, type ViewCapture, XrayOptions, collectRefs, handleCapturePost, handleFixturePost, isValidPlateName, readCoverage, readFixtures, renderPlateModule, xrayVitePlugin };
|