@hueest/xray 0.5.0 → 0.7.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,52 +1,58 @@
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-BZosujs9.js";
3
- import { t as escapeStyleText } from "./css-escape-N7bOusGW.js";
1
+ import { a as ROOT_ATTR, d as composeCss, f as emptyPlate, o as ROOT_CLASS, t as BASE_CSS } from "./plate-CXIh5_OB.js";
2
+ import { a as serializePlate, c as defineXrayCaptureWalker, i as collectRefs, l as distilPlate, o as CaptureTooLargeError, s as captureView, t as serializePlateIR } from "./serialize-Cj-EHvMv.js";
3
+ import { t as escapeStyleText } from "./css-escape-DJ-BsrNm.js";
4
4
  //#region src/core.ts
5
5
  /**
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>`.
6
+ * Capture a live DOM subtree straight to the two renderable artifacts: the structural `plate`
7
+ * (tree, lowered to chunks) and its scoped `css` stringindependent parts, per the two-artifact
8
+ * contract. Runs the same uniform pipeline the dev client does for a single View — measure
9
+ * (`captureView`), distil (`distilPlate`), project (`serializePlateIR` the structured
10
+ * `StoredPlate`), then lower to chunks (`serializePlate`) in one call. `roots` are the top-level
11
+ * elements to capture (a component's rendered roots). Render however suits: `renderPlateHtml(plate,
12
+ * composeCss(css))`, or `<Skeleton plate={plate} css={composeCss(css)}>` with the React adapter.
12
13
  *
13
- * `options.walker` is the public programmable capture walker (ADR 0018, Q2
14
- * yes, public): the same `XrayCaptureWalker` the `<Skeleton captureWalker>` prop
15
- * threads through in dev, available here for driving capture directly. It is
16
- * capture-only and never persisted into the returned Plate.
14
+ * `options.walker` is the public programmable capture walker (ADR 0018): the same
15
+ * `XrayCaptureWalker` the `<Skeleton captureWalker>` prop threads through in dev, available here
16
+ * for driving capture directly. It is capture-only and never persisted into the returned
17
+ * artifacts.
17
18
  */
18
19
  function captureElement(roots, options) {
19
- const plate = captureView(roots, {
20
+ const plateIR = captureView(roots, {
20
21
  collect: options.collect,
21
22
  walker: options.walker,
22
23
  captureRootIsBoundary: options.captureRootIsBoundary
23
24
  });
24
- plate.name = options.name;
25
- runPlatePasses(plate);
26
- return serializePlate(serializePlateIR(plate));
25
+ plateIR.name = options.name;
26
+ distilPlate(plateIR);
27
+ const stored = serializePlateIR(plateIR);
28
+ return {
29
+ plate: serializePlate(stored),
30
+ css: stored.css
31
+ };
27
32
  }
28
33
  /**
29
- * Render a stitch-free Plate to a self-contained HTML string — the
30
- * framework-neutral equivalent of `<Skeleton plate loading />`, for when you
31
- * don't want to pull a framework adapter (demos, plain DOM, server strings).
32
- * Drop it in with `el.innerHTML = renderPlateHtml(plate)` or React's
33
- * `dangerouslySetInnerHTML`. The string is self-contained: it carries BASE_CSS
34
- * + the plate's scoped rules in its own `<style>`. Stitched plates (a nested
35
- * `<Skeleton>`) need an adapter to mount their child plates, so they throw here.
34
+ * Render a stitch-free Plate to an HTML string — the framework-neutral equivalent of `<Skeleton
35
+ * plate loading />`, for when you don't want to pull a framework adapter (demos, plain DOM, server
36
+ * strings). Drop it in with `el.innerHTML = renderPlateHtml(plate, composeCss(css))` or React's
37
+ * `dangerouslySetInnerHTML`.
38
+ *
39
+ * `css` is the caller's explicit input (two-artifact contract): pass `composeCss(css)` for the
40
+ * standard BASE_CSS + plate-rules composition (BASE_CSS is what makes bones paint and pulse), the
41
+ * raw capture css if the base rules are already on the page, or your own composition. Stitched
42
+ * plates (a nested `<Skeleton>`) need an adapter to mount their child plates, so they throw here.
36
43
  */
37
- function renderPlateHtml(plate) {
44
+ function renderPlateHtml(plate, css) {
38
45
  const chunks = plate.chunks;
39
46
  if (!chunks) return "";
40
47
  const className = plate.rootCls ? `${ROOT_CLASS} ${plate.rootCls}` : ROOT_CLASS;
41
- 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>`;
48
+ return `<div ${ROOT_ATTR}="${plate.name}" class="${className}" aria-hidden="true" aria-busy="true"><style>${escapeStyleText(css)}</style>${chunksToHtml(chunks)}</div>`;
42
49
  }
43
50
  /**
44
- * Flatten chunks to one HTML string. A `string` concatenates; a template chunk
45
- * (`{ t, n }`) expands to its cell repeated `n` times (the cell is stitch-free by
46
- * construction); a stitch-free wrapper (`{ c, k }`) recurses inside a real `<div>`.
47
- * A stitch (`{ r }`) resolves to a child plate at render and needs a framework
48
- * adapter to mount, so a stitched plate throws here. Chunk HTML is xray-generated
49
- * and interpolated raw, exactly as the single-string path was before.
51
+ * Flatten chunks to one HTML string. A `string` concatenates; a template chunk (`{ t, n }`) expands
52
+ * to its cell repeated `n` times (the cell is stitch-free by construction); a stitch-free wrapper
53
+ * (`{ c, k }`) recurses inside a real `<div>`. A stitch (`{ r }`) resolves to a child plate at
54
+ * render and needs a framework adapter to mount, so a stitched plate throws here. Chunk HTML is
55
+ * xray-generated and safe to interpolate raw.
50
56
  */
51
57
  function chunksToHtml(chunks) {
52
58
  let html = "";
@@ -57,4 +63,4 @@ function chunksToHtml(chunks) {
57
63
  return html;
58
64
  }
59
65
  //#endregion
60
- export { CaptureTooLargeError, captureElement, collectRefs, defineXrayCaptureWalker, emptyPlate, renderPlateHtml, serializePlate };
66
+ export { BASE_CSS, CaptureTooLargeError, captureElement, collectRefs, composeCss, defineXrayCaptureWalker, emptyPlate, renderPlateHtml, serializePlate };
@@ -0,0 +1,47 @@
1
+ //#region src/css-escape.ts
2
+ /**
3
+ * Dependency-free raw-text safety for the one place a Plate's CSS string is interpolated into
4
+ * `<style>…</style>` markup.
5
+ *
6
+ * A plate's `css` is normally produced by the capture path (`filterLayoutDecls` + `classify`),
7
+ * which only ever emits well-formed `selector { prop: value }` rules — never a raw-text terminator.
8
+ * But a committed `plates/<name>.json` is UNTRUSTED at render time (it can be hand-edited or arrive
9
+ * over git, ADR 0023), and a corrupt/malicious `css` could carry a `</style>` or an HTML comment
10
+ * delimiter. `<style>` is a raw-text element: the parser ends the element at the first `</style` it
11
+ * sees and resumes parsing the rest as ordinary HTML, so an unescaped terminator in the CSS body
12
+ * turns the tail of the plate into live markup.
13
+ *
14
+ * The React text-child path (`<style>{css}</style>`) is already safe — React escapes the child —
15
+ * but the two STRING paths interpolate `css` directly into a markup string (`renderPlateHtml`
16
+ * builds an HTML string; `react.core.tsx` feeds one to `dangerouslySetInnerHTML`). Those must run
17
+ * the body through this helper first.
18
+ *
19
+ * This lives in its own dependency-free module ON PURPOSE: it is imported by render/browser paths,
20
+ * so it must NOT pull in the Node-only validation code (valibot). It neutralizes only the sequences
21
+ * that can escape a raw-text `<style>` context — a `<` that begins `</style`, and the HTML comment
22
+ * / CDATA delimiters the tokenizer treats specially inside raw text — by inserting a CSS-comment
23
+ * break (`/​**​/`) that the CSS parser ignores. It does not, and is not meant to, sanitize CSS
24
+ * semantically; POST-time validation (`validate.ts`) is what rejects junk declarations.
25
+ *
26
+ * @module
27
+ */
28
+ /**
29
+ * Make a CSS body safe to interpolate into a `<style>…</style>` STRING. Breaks up the only
30
+ * sequences the HTML tokenizer acts on inside a raw-text `<style>` element, so none can terminate
31
+ * the element early or open a comment/CDATA run:
32
+ *
33
+ * - `</style` (the end-tag open, case-insensitive) — inserts the break between `<` and `/style` so
34
+ * the parser never matches the end tag.
35
+ * - `<!--`, `-->`, `<![CDATA[` — the comment/CDATA delimiters; split so neither a comment nor a CDATA
36
+ * section can open.
37
+ *
38
+ * The inserted `/​**​/` is an empty CSS comment: inert to the CSS parser (it cannot appear
39
+ * mid-token in a value the capture emits, and a stray one only ends a token early), so a
40
+ * well-formed plate is rendered byte-for-byte equivalently while a corrupt one can no longer break
41
+ * out of the style element.
42
+ */
43
+ function escapeStyleText(css) {
44
+ return css.replace(/<(\/style)/gi, "</**/$1").replace(/<!--/g, "<!/**/--").replace(/-->/g, "--/**/>").replace(/<!\[CDATA\[/g, "<![/**/CDATA[");
45
+ }
46
+ //#endregion
47
+ export { escapeStyleText as t };
package/dist/hud.d.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  //#region src/hud.d.ts
2
2
  /**
3
- * The opt-in dev HUD (Astro-dev-toolbar-style): a small fixed badge. For each
4
- * plate it shows every detected width band — captured ones lit, the rest dim —
5
- * sourced from the committed plate files (via `__XRAY__.coverage`), not the
6
- * session, so it reflects what's actually on disk.
3
+ * The opt-in dev HUD (Astro-dev-toolbar-style): a small fixed badge. For each plate it shows every
4
+ * detected width band — captured ones lit, the rest dim — sourced from the committed plate files
5
+ * (via `__XRAY__.coverage`), not the session, so it reflects what's actually on disk.
7
6
  *
8
- * It is also the controller for capture-all-views (ADR 0010): it probes for the
9
- * companion extension (which announces itself via its content script) and shows
10
- * a "Capture all views" button when present, or a one-line promotion when not.
11
- * The sweep itself lives in the dev client (`__XRAY__.captureAllViews`).
7
+ * It is also the controller for capture-all-views (ADR 0010): it probes for the companion extension
8
+ * (which announces itself via its content script) and shows a "Capture all views" button when
9
+ * present, or a one-line promotion when not. The sweep itself lives in the dev client
10
+ * (`__XRAY__.captureAllViews`).
12
11
  */
13
12
  interface HudHandle {
14
13
  dispose: () => void;
package/dist/hud.js CHANGED
@@ -3,8 +3,8 @@ function isExtensionHello(value) {
3
3
  return typeof value === "object" && value !== null && "source" in value && value.source === "xray-ext" && "kind" in value && value.kind === "hello";
4
4
  }
5
5
  /**
6
- * A labelled checkbox bound to a sticky toggle store; reads the store on build
7
- * so a console/URL flip stays in sync with the box at the next render.
6
+ * A labelled checkbox bound to a sticky toggle store; reads the store on build so a console/URL
7
+ * flip stays in sync with the box at the next render.
8
8
  */
9
9
  function toggleRow(store, text) {
10
10
  const label = document.createElement("label");
@@ -19,9 +19,9 @@ function toggleRow(store, text) {
19
19
  return label;
20
20
  }
21
21
  /**
22
- * Expand a plate's coverage into the full set of width bands, flagging which are
23
- * captured. The breakpoints partition the width axis; a band is covered when a
24
- * captured span matches it (spans align to band boundaries, ADR 0004).
22
+ * Expand a plate's coverage into the full set of width bands, flagging which are captured. The
23
+ * breakpoints partition the width axis; a band is covered when a captured span matches it (spans
24
+ * align to band boundaries, ADR 0004).
25
25
  */
26
26
  function bandsOf({ breakpoints, spans }) {
27
27
  const sorted = [...breakpoints].toSorted((a, b) => a - b);