@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.
@@ -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;
@@ -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,4 +360,4 @@ declare global {
335
360
  var __XRAY__: CaptureHook | undefined;
336
361
  }
337
362
  //#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 };
363
+ export { RenderNode as a, emptyPlate as c, PlateNode as i, CaptureDiagnostic as l, MergedPlate as n, StoredPlate as o, Plate as r, ViewCapture as s, LeafKind as t };
@@ -42,7 +42,8 @@ const LEAF_CLASS = "xr-leaf";
42
42
  * Tokens (ADR 0017 taxonomy). The `--xr-bone-*` family styles an individual
43
43
  * Bone: `--xr-bone-color` (fill), `--xr-bone-highlight-color` (sheen),
44
44
  * `--xr-bone-border-radius` (+ `--xr-bone-text-border-radius` /
45
- * `--xr-bone-media-border-radius`), `--xr-bone-animation-duration`, the pulse
45
+ * `--xr-bone-text-block-border-radius` / `--xr-bone-media-border-radius`),
46
+ * `--xr-bone-animation-duration`, the pulse
46
47
  * range `--xr-bone-pulse-opacity-min`/`--xr-bone-pulse-opacity-max`,
47
48
  * `--xr-bone-animation` (mode: `xr-pulse` | `none` | a custom keyframe), and
48
49
  * `--xr-bone-fill` (override the leaf paint, e.g. a sheen gradient built from the
@@ -83,9 +84,12 @@ const BASE_CSS = `
83
84
  Never transition opacity directly (the pulse animation owns it). */
84
85
  opacity: calc(var(--xr-o) * var(--xr-reveal));
85
86
  }
86
- /* Per-kind defaults: text reads as rounded bars, media as blocks. Re-theme any
87
- kind via [data-xr-root] .${LEAF_CLASS}-text { ... }. */
87
+ /* Per-kind defaults: a single text LINE reads as a rounded bar; a multi-line
88
+ text BLOCK as a soft rect (a tall pill misreads as a button); media carries
89
+ the author radius, defaulting to the same soft rect. Re-theme any kind via
90
+ [data-xr-root] .${LEAF_CLASS}-text { ... }. */
88
91
  .${LEAF_CLASS}-text { border-radius: var(--xr-bone-text-border-radius, 999px) }
92
+ .${LEAF_CLASS}-text-block { border-radius: var(--xr-bone-text-block-border-radius, var(--xr-bone-border-radius, 4px)) }
89
93
  .${LEAF_CLASS}-media { border-radius: var(--xr-bone-media-border-radius, var(--xr-bone-border-radius, 4px)) }
90
94
  @keyframes xr-pulse {
91
95
  from { --xr-o: var(--xr-bone-pulse-opacity-max, 1) }
@@ -142,7 +146,7 @@ const BASE_CSS = `
142
146
  /** The (unserialized) plate an import resolves to before anything has been captured. */
143
147
  function emptyPlate(name) {
144
148
  return {
145
- v: 1,
149
+ v: 2,
146
150
  name,
147
151
  tree: null,
148
152
  css: ""