@michaelmagan/dnumb 0.1.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/AGENTS.md +180 -0
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/dist/analysis/cluster.cjs +114 -0
- package/dist/analysis/cluster.d.cts +68 -0
- package/dist/analysis/cluster.d.ts +68 -0
- package/dist/analysis/cluster.js +109 -0
- package/dist/analysis/distances.cjs +124 -0
- package/dist/analysis/distances.d.cts +58 -0
- package/dist/analysis/distances.d.ts +58 -0
- package/dist/analysis/distances.js +119 -0
- package/dist/analysis/geometry.cjs +60 -0
- package/dist/analysis/geometry.d.cts +34 -0
- package/dist/analysis/geometry.d.ts +34 -0
- package/dist/analysis/geometry.js +53 -0
- package/dist/analysis/layout-tree.cjs +245 -0
- package/dist/analysis/layout-tree.d.cts +45 -0
- package/dist/analysis/layout-tree.d.ts +45 -0
- package/dist/analysis/layout-tree.js +242 -0
- package/dist/analysis/region-query.cjs +71 -0
- package/dist/analysis/region-query.d.cts +25 -0
- package/dist/analysis/region-query.d.ts +25 -0
- package/dist/analysis/region-query.js +67 -0
- package/dist/analysis/types.cjs +7 -0
- package/dist/analysis/types.d.cts +143 -0
- package/dist/analysis/types.d.ts +143 -0
- package/dist/analysis/types.js +6 -0
- package/dist/bundle.cjs +182 -0
- package/dist/bundle.d.cts +91 -0
- package/dist/bundle.d.ts +91 -0
- package/dist/bundle.js +175 -0
- package/dist/cli.cjs +755 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +719 -0
- package/dist/diff/changes.cjs +656 -0
- package/dist/diff/changes.d.cts +2 -0
- package/dist/diff/changes.d.ts +2 -0
- package/dist/diff/changes.js +653 -0
- package/dist/diff/identity.cjs +167 -0
- package/dist/diff/identity.d.cts +62 -0
- package/dist/diff/identity.d.ts +62 -0
- package/dist/diff/identity.js +159 -0
- package/dist/diff/index.cjs +84 -0
- package/dist/diff/index.d.cts +76 -0
- package/dist/diff/index.d.ts +76 -0
- package/dist/diff/index.js +68 -0
- package/dist/diff/match.cjs +156 -0
- package/dist/diff/match.d.cts +17 -0
- package/dist/diff/match.d.ts +17 -0
- package/dist/diff/match.js +152 -0
- package/dist/diff/render.cjs +540 -0
- package/dist/diff/render.d.cts +2 -0
- package/dist/diff/render.d.ts +2 -0
- package/dist/diff/render.js +537 -0
- package/dist/diff/types.cjs +2 -0
- package/dist/diff/types.d.cts +211 -0
- package/dist/diff/types.d.ts +211 -0
- package/dist/diff/types.js +1 -0
- package/dist/digest/index.cjs +8 -0
- package/dist/digest/index.d.cts +4 -0
- package/dist/digest/index.d.ts +4 -0
- package/dist/digest/index.js +2 -0
- package/dist/digest/parse.cjs +163 -0
- package/dist/digest/parse.d.cts +80 -0
- package/dist/digest/parse.d.ts +80 -0
- package/dist/digest/parse.js +160 -0
- package/dist/digest/render.cjs +1575 -0
- package/dist/digest/render.d.cts +41 -0
- package/dist/digest/render.d.ts +41 -0
- package/dist/digest/render.js +1571 -0
- package/dist/digest/types.cjs +10 -0
- package/dist/digest/types.d.cts +86 -0
- package/dist/digest/types.d.ts +86 -0
- package/dist/digest/types.js +9 -0
- package/dist/index.cjs +61 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +19 -0
- package/dist/page.cjs +35 -0
- package/dist/page.d.cts +40 -0
- package/dist/page.d.ts +40 -0
- package/dist/page.js +31 -0
- package/dist/playwright.cjs +138 -0
- package/dist/playwright.d.cts +69 -0
- package/dist/playwright.d.ts +69 -0
- package/dist/playwright.js +131 -0
- package/dist/query.cjs +1152 -0
- package/dist/query.d.cts +47 -0
- package/dist/query.d.ts +47 -0
- package/dist/query.js +1146 -0
- package/dist/snapshot/capture.cjs +80 -0
- package/dist/snapshot/capture.d.cts +34 -0
- package/dist/snapshot/capture.d.ts +34 -0
- package/dist/snapshot/capture.js +77 -0
- package/dist/snapshot/cdp.cjs +450 -0
- package/dist/snapshot/cdp.d.cts +36 -0
- package/dist/snapshot/cdp.d.ts +36 -0
- package/dist/snapshot/cdp.js +447 -0
- package/dist/snapshot/clip.cjs +100 -0
- package/dist/snapshot/clip.d.cts +26 -0
- package/dist/snapshot/clip.d.ts +26 -0
- package/dist/snapshot/clip.js +96 -0
- package/dist/snapshot/collect.cjs +358 -0
- package/dist/snapshot/collect.d.cts +18 -0
- package/dist/snapshot/collect.d.ts +18 -0
- package/dist/snapshot/collect.js +355 -0
- package/dist/snapshot/color.cjs +1039 -0
- package/dist/snapshot/color.d.cts +316 -0
- package/dist/snapshot/color.d.ts +316 -0
- package/dist/snapshot/color.js +1026 -0
- package/dist/snapshot/facts.cjs +167 -0
- package/dist/snapshot/facts.d.cts +48 -0
- package/dist/snapshot/facts.d.ts +48 -0
- package/dist/snapshot/facts.js +161 -0
- package/dist/snapshot/limits.cjs +69 -0
- package/dist/snapshot/limits.d.cts +59 -0
- package/dist/snapshot/limits.d.ts +59 -0
- package/dist/snapshot/limits.js +65 -0
- package/dist/snapshot/not-measured.cjs +44 -0
- package/dist/snapshot/not-measured.d.cts +14 -0
- package/dist/snapshot/not-measured.d.ts +14 -0
- package/dist/snapshot/not-measured.js +41 -0
- package/dist/snapshot/types.cjs +2 -0
- package/dist/snapshot/types.d.cts +238 -0
- package/dist/snapshot/types.d.ts +238 -0
- package/dist/snapshot/types.js +1 -0
- package/dist/spec/index.cjs +9 -0
- package/dist/spec/index.d.cts +15 -0
- package/dist/spec/index.d.ts +15 -0
- package/dist/spec/index.js +6 -0
- package/package.json +141 -0
- package/skills/dnumb/SKILL.md +136 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Limits shared by both capture paths.
|
|
3
|
+
*
|
|
4
|
+
* They live here rather than in either collector because a capture has to mean
|
|
5
|
+
* the same thing whichever path produced it. The first benchmark caught them
|
|
6
|
+
* disagreeing: on a Wikipedia article the in-page walk stopped at 5,000
|
|
7
|
+
* elements and reported `truncated: true`, while the CDP path returned 6,927
|
|
8
|
+
* and hardcoded `truncated: false`. Two captures of one page that are not
|
|
9
|
+
* comparable, and a flag that silently means "maybe".
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The element ceiling. Not a performance guard -- both paths are fast enough
|
|
13
|
+
* at this size -- but a token guard: a digest of 5,000 elements is already far
|
|
14
|
+
* past what any consumer will read, and past it the honest answer is
|
|
15
|
+
* `truncated: true` plus a narrower capture.
|
|
16
|
+
*/
|
|
17
|
+
export const MAX_ELEMENTS = 5000;
|
|
18
|
+
/** Own-text preview length, in characters. */
|
|
19
|
+
export const TEXT_PREVIEW_LENGTH = 60;
|
|
20
|
+
/** Accessible-name length, in characters. */
|
|
21
|
+
export const NAME_LENGTH = 80;
|
|
22
|
+
/** Attributes treated as a stable test id, in priority order. */
|
|
23
|
+
export const TESTID_ATTRS = ['data-testid', 'data-test-id', 'data-test', 'data-qa'];
|
|
24
|
+
/** Tags whose subtrees carry no layout worth measuring. */
|
|
25
|
+
export const SKIPPED_TAGS = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEMPLATE', 'SVG'];
|
|
26
|
+
/**
|
|
27
|
+
* `background-image` / `text-shadow` string cap, in characters.
|
|
28
|
+
*
|
|
29
|
+
* A gradient or shadow's computed value can run to hundreds of characters
|
|
30
|
+
* (many stops, multiple shadows) — `docs/notes/03-color.md` §7 estimates real
|
|
31
|
+
* bundle growth at ~105 bytes/element for the naive uncapped fields, so this
|
|
32
|
+
* caps the two least-bounded ones at the point of capture rather than on
|
|
33
|
+
* render. `snapshot/color.ts` only ever reads the first and last gradient
|
|
34
|
+
* stop and the first shadow color — but "only reads the ends" is exactly
|
|
35
|
+
* why a *plain prefix* cut is wrong for `background-image`: a real 137-char
|
|
36
|
+
* gradient truncated at 120 chars loses its own last stop, the one the
|
|
37
|
+
* gradient token needs (verified against a real capture; A4). See
|
|
38
|
+
* `capBackgroundImage` below for the fix. `text-shadow` has no such gap —
|
|
39
|
+
* `firstShadowColor` only ever reads the *first* comma-separated segment's
|
|
40
|
+
* first color token, so a plain prefix cut is still exactly right for it.
|
|
41
|
+
*/
|
|
42
|
+
export const COLOR_TOKEN_CAP = 120;
|
|
43
|
+
/**
|
|
44
|
+
* Caps `raw` to `cap` characters while preserving *both* ends, joined by an
|
|
45
|
+
* ellipsis, instead of a plain prefix cut. `background-image`'s only two
|
|
46
|
+
* downstream readers (`resolveBackdrop`'s gradient-stop parse and `q color`'s
|
|
47
|
+
* display) need the *first* and *last* color token, never the middle — a
|
|
48
|
+
* plain `slice(0, cap)` can and does drop the last stop off a real multi-stop
|
|
49
|
+
* gradient whose computed string runs past `cap` (A4). Splitting the budget
|
|
50
|
+
* across both ends fixes that regardless of how many stops sit in the middle:
|
|
51
|
+
* the string might read `linear-gradient(...) 0%, …, rgb(255,255,255) 100%)`
|
|
52
|
+
* instead of every stop, but the two that matter always survive.
|
|
53
|
+
*
|
|
54
|
+
* `collect.ts` restates this inline (`page.evaluate` serializes only its own
|
|
55
|
+
* function source, so it cannot import this module) — kept in sync by
|
|
56
|
+
* `test/unit/limits.test.ts`, the same discipline `MAX_ELEMENTS` and the
|
|
57
|
+
* other capture-path constants already use.
|
|
58
|
+
*/
|
|
59
|
+
export function capBackgroundImage(raw, cap = COLOR_TOKEN_CAP) {
|
|
60
|
+
if (raw.length <= cap)
|
|
61
|
+
return raw;
|
|
62
|
+
const ELLIPSIS = '…';
|
|
63
|
+
const half = Math.max(0, Math.floor((cap - ELLIPSIS.length) / 2));
|
|
64
|
+
return `${raw.slice(0, half)}${ELLIPSIS}${raw.slice(raw.length - half)}`;
|
|
65
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatNotMeasured = formatNotMeasured;
|
|
4
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
5
|
+
function iframeSegment(iframes) {
|
|
6
|
+
const total = iframes.sameOrigin + iframes.crossOrigin + iframes.unknownOrigin;
|
|
7
|
+
if (total === 0)
|
|
8
|
+
return null;
|
|
9
|
+
const detail = [];
|
|
10
|
+
if (iframes.crossOrigin > 0)
|
|
11
|
+
detail.push(`${iframes.crossOrigin} cross-origin`);
|
|
12
|
+
if (iframes.unknownOrigin > 0)
|
|
13
|
+
detail.push(`${iframes.unknownOrigin} origin unknown`);
|
|
14
|
+
return `${plural(total, 'iframe')}${detail.length > 0 ? ` (${detail.join(', ')})` : ''}`;
|
|
15
|
+
}
|
|
16
|
+
function shadowRootSegment(shadowRoots) {
|
|
17
|
+
// `closed: 'unknown'` is a constant property of the *capture path*, not a
|
|
18
|
+
// measurement of *this page* -- every walk capture and every legacy bundle
|
|
19
|
+
// carries it regardless of what the page contains, so it does not belong on
|
|
20
|
+
// this counted, page-specific line. That permanent caveat belongs on the
|
|
21
|
+
// digest identity line's strategy segment instead (`— walk (closed shadow
|
|
22
|
+
// roots undetectable)`, in `digest/render.ts`) -- see the `NotMeasured` doc
|
|
23
|
+
// comment for why the count is genuinely unknowable here. This line reports
|
|
24
|
+
// only what was actually counted: the real open-root total, when there is
|
|
25
|
+
// one.
|
|
26
|
+
if (shadowRoots.closed === 'unknown') {
|
|
27
|
+
if (shadowRoots.open === 0)
|
|
28
|
+
return null;
|
|
29
|
+
return plural(shadowRoots.open, 'open shadow root');
|
|
30
|
+
}
|
|
31
|
+
const total = shadowRoots.open + shadowRoots.closed;
|
|
32
|
+
if (total === 0)
|
|
33
|
+
return null;
|
|
34
|
+
const detail = shadowRoots.closed > 0 ? ` (${shadowRoots.closed} closed)` : '';
|
|
35
|
+
return `${plural(total, 'shadow root')}${detail}`;
|
|
36
|
+
}
|
|
37
|
+
/** `"not measured: 2 iframes (1 cross-origin), 14 shadow roots"`, or `null` when there is nothing to disclose. */
|
|
38
|
+
function formatNotMeasured(notMeasured) {
|
|
39
|
+
const segments = [
|
|
40
|
+
iframeSegment(notMeasured.iframes),
|
|
41
|
+
shadowRootSegment(notMeasured.shadowRoots),
|
|
42
|
+
].filter((segment) => segment !== null);
|
|
43
|
+
return segments.length > 0 ? `not measured: ${segments.join(', ')}` : null;
|
|
44
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders `NotMeasured` into the one line both the digest header and the CLI
|
|
3
|
+
* query verbs show. One formatter so the two never describe the same counts
|
|
4
|
+
* in different words -- the failure mode `digest/render.ts`'s own header
|
|
5
|
+
* lines are built to avoid for every other fact this package prints.
|
|
6
|
+
*
|
|
7
|
+
* Returns `null` when there is nothing to disclose, so every call site can
|
|
8
|
+
* treat "print nothing" and "call this" as the same decision -- a page with
|
|
9
|
+
* no shadow DOM and no iframes must not gain a line just because the field
|
|
10
|
+
* exists on the record.
|
|
11
|
+
*/
|
|
12
|
+
import type { NotMeasured } from "./types.cjs";
|
|
13
|
+
/** `"not measured: 2 iframes (1 cross-origin), 14 shadow roots"`, or `null` when there is nothing to disclose. */
|
|
14
|
+
export declare function formatNotMeasured(notMeasured: NotMeasured): string | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders `NotMeasured` into the one line both the digest header and the CLI
|
|
3
|
+
* query verbs show. One formatter so the two never describe the same counts
|
|
4
|
+
* in different words -- the failure mode `digest/render.ts`'s own header
|
|
5
|
+
* lines are built to avoid for every other fact this package prints.
|
|
6
|
+
*
|
|
7
|
+
* Returns `null` when there is nothing to disclose, so every call site can
|
|
8
|
+
* treat "print nothing" and "call this" as the same decision -- a page with
|
|
9
|
+
* no shadow DOM and no iframes must not gain a line just because the field
|
|
10
|
+
* exists on the record.
|
|
11
|
+
*/
|
|
12
|
+
import type { NotMeasured } from "./types.js";
|
|
13
|
+
/** `"not measured: 2 iframes (1 cross-origin), 14 shadow roots"`, or `null` when there is nothing to disclose. */
|
|
14
|
+
export declare function formatNotMeasured(notMeasured: NotMeasured): string | null;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
2
|
+
function iframeSegment(iframes) {
|
|
3
|
+
const total = iframes.sameOrigin + iframes.crossOrigin + iframes.unknownOrigin;
|
|
4
|
+
if (total === 0)
|
|
5
|
+
return null;
|
|
6
|
+
const detail = [];
|
|
7
|
+
if (iframes.crossOrigin > 0)
|
|
8
|
+
detail.push(`${iframes.crossOrigin} cross-origin`);
|
|
9
|
+
if (iframes.unknownOrigin > 0)
|
|
10
|
+
detail.push(`${iframes.unknownOrigin} origin unknown`);
|
|
11
|
+
return `${plural(total, 'iframe')}${detail.length > 0 ? ` (${detail.join(', ')})` : ''}`;
|
|
12
|
+
}
|
|
13
|
+
function shadowRootSegment(shadowRoots) {
|
|
14
|
+
// `closed: 'unknown'` is a constant property of the *capture path*, not a
|
|
15
|
+
// measurement of *this page* -- every walk capture and every legacy bundle
|
|
16
|
+
// carries it regardless of what the page contains, so it does not belong on
|
|
17
|
+
// this counted, page-specific line. That permanent caveat belongs on the
|
|
18
|
+
// digest identity line's strategy segment instead (`— walk (closed shadow
|
|
19
|
+
// roots undetectable)`, in `digest/render.ts`) -- see the `NotMeasured` doc
|
|
20
|
+
// comment for why the count is genuinely unknowable here. This line reports
|
|
21
|
+
// only what was actually counted: the real open-root total, when there is
|
|
22
|
+
// one.
|
|
23
|
+
if (shadowRoots.closed === 'unknown') {
|
|
24
|
+
if (shadowRoots.open === 0)
|
|
25
|
+
return null;
|
|
26
|
+
return plural(shadowRoots.open, 'open shadow root');
|
|
27
|
+
}
|
|
28
|
+
const total = shadowRoots.open + shadowRoots.closed;
|
|
29
|
+
if (total === 0)
|
|
30
|
+
return null;
|
|
31
|
+
const detail = shadowRoots.closed > 0 ? ` (${shadowRoots.closed} closed)` : '';
|
|
32
|
+
return `${plural(total, 'shadow root')}${detail}`;
|
|
33
|
+
}
|
|
34
|
+
/** `"not measured: 2 iframes (1 cross-origin), 14 shadow roots"`, or `null` when there is nothing to disclose. */
|
|
35
|
+
export function formatNotMeasured(notMeasured) {
|
|
36
|
+
const segments = [
|
|
37
|
+
iframeSegment(notMeasured.iframes),
|
|
38
|
+
shadowRootSegment(notMeasured.shadowRoots),
|
|
39
|
+
].filter((segment) => segment !== null);
|
|
40
|
+
return segments.length > 0 ? `not measured: ${segments.join(', ')}` : null;
|
|
41
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record shape everything downstream reads. `collect.ts` builds
|
|
3
|
+
* `ElementRecord[]` inside the page; nothing in this file depends on the DOM or
|
|
4
|
+
* on Playwright, so pure Node-side code — the digest, the diff, the query
|
|
5
|
+
* verbs — can import it freely.
|
|
6
|
+
*
|
|
7
|
+
* Ported from the prototype's `snapshot/types.ts`, with the fields the step-2
|
|
8
|
+
* spike proved load-bearing added: a *computed* role and accessible name (the
|
|
9
|
+
* prototype captured `getAttribute('role')`, which is null on nearly every real
|
|
10
|
+
* element), and the clip fact.
|
|
11
|
+
*/
|
|
12
|
+
import type { BoxSides, Rect } from "../analysis/types.cjs";
|
|
13
|
+
export type { BoxSides, Rect };
|
|
14
|
+
/**
|
|
15
|
+
* Why an element is not fully on screen, and the facts needed to judge whether
|
|
16
|
+
* that matters.
|
|
17
|
+
*
|
|
18
|
+
* Carrying `overflow` and `scrollable` beside the hidden extents is the
|
|
19
|
+
* facts-with-counter-facts model: a clipped box inside `overflow:auto` is a
|
|
20
|
+
* scroll region, and the same box inside `overflow:hidden` is content nobody
|
|
21
|
+
* can reach. The tool reports both and does not decide which one this is.
|
|
22
|
+
*/
|
|
23
|
+
export interface ClipFact {
|
|
24
|
+
/** Id of the nearest ancestor whose `overflow` is not `visible`. */
|
|
25
|
+
by: string;
|
|
26
|
+
byTag: string;
|
|
27
|
+
/** The clipper's `overflow-x`, or `x/y` when the two axes differ. */
|
|
28
|
+
overflow: string;
|
|
29
|
+
/** How far the element extends past the clipper on each side, in px. */
|
|
30
|
+
hidden: BoxSides;
|
|
31
|
+
/** The part of the element that survives the clip. */
|
|
32
|
+
visible: {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Whether a user could scroll the clipper to reveal the hidden part.
|
|
38
|
+
* Not the same question as `scrollHeight > clientHeight`: an
|
|
39
|
+
* `overflow:hidden` box has scroll extent and no reachability.
|
|
40
|
+
*/
|
|
41
|
+
scrollable: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface ElementRecord {
|
|
44
|
+
/** Stable within one capture only — not a DOM id, not stable across runs. */
|
|
45
|
+
id: string;
|
|
46
|
+
tag: string;
|
|
47
|
+
/** `data-testid` / `data-test-id` / `data-test` / `data-qa`, first one present. */
|
|
48
|
+
testId: string | null;
|
|
49
|
+
/** The authored `id` attribute, unfiltered. Identity applies its own guard. */
|
|
50
|
+
domId: string | null;
|
|
51
|
+
/** Computed role — explicit `role` if present, otherwise the implicit one. */
|
|
52
|
+
role: string | null;
|
|
53
|
+
/** Computed accessible name. */
|
|
54
|
+
name: string | null;
|
|
55
|
+
className: string;
|
|
56
|
+
/** From the tag (`h1`-`h6`) or `aria-level`; `null` for non-headings. */
|
|
57
|
+
headingLevel: number | null;
|
|
58
|
+
rect: Rect;
|
|
59
|
+
scrollWidth: number;
|
|
60
|
+
scrollHeight: number;
|
|
61
|
+
clientWidth: number;
|
|
62
|
+
clientHeight: number;
|
|
63
|
+
overflowX: string;
|
|
64
|
+
overflowY: string;
|
|
65
|
+
position: string;
|
|
66
|
+
display: string;
|
|
67
|
+
visibility: string;
|
|
68
|
+
opacity: number;
|
|
69
|
+
margin: BoxSides;
|
|
70
|
+
padding: BoxSides;
|
|
71
|
+
/** Row/column gap in px; only meaningful on grid and flex containers. */
|
|
72
|
+
gap: {
|
|
73
|
+
row: number;
|
|
74
|
+
column: number;
|
|
75
|
+
};
|
|
76
|
+
border: BoxSides;
|
|
77
|
+
color: string;
|
|
78
|
+
backgroundColor: string;
|
|
79
|
+
/**
|
|
80
|
+
* Computed `background-image`, capped to ~120 chars (both ends preserved —
|
|
81
|
+
* see `limits.ts#capBackgroundImage`), `'none'` when unset. Not captured
|
|
82
|
+
* for its own sake — it is the counter-fact that invalidates a resolved
|
|
83
|
+
* backdrop: a gradient or `url()` background means no single color exists
|
|
84
|
+
* behind text painted over it. See `snapshot/color.ts` and
|
|
85
|
+
* `docs/notes/03-color.md` §2–3.
|
|
86
|
+
*
|
|
87
|
+
* Required, like every other field on this interface — both real capture
|
|
88
|
+
* paths always set it to a string. `null` is a distinct, legitimate third
|
|
89
|
+
* state: "this specific capture never measured this field at all" — a
|
|
90
|
+
* bundle written before color capture existed, read back through
|
|
91
|
+
* `bundle.ts#normalizeElement`, which backfills `null` here rather than the
|
|
92
|
+
* false positive claim `'none'` used to be (A3). `resolveBackdrop` and
|
|
93
|
+
* `hasPaint` in `snapshot/color.ts` treat `null` the same way they treat an
|
|
94
|
+
* unresolvable non-ancestor overlay: refuse to guess, report
|
|
95
|
+
* `unmeasured (not-captured ...)`, never silently assume "no image here".
|
|
96
|
+
*
|
|
97
|
+
* A record built by hand (not via `capture()`/`parseBundle`) that omits
|
|
98
|
+
* this field entirely (`undefined`, not `null`), or any of the other three
|
|
99
|
+
* color-capture fields below, gets a clear thrown error naming the missing
|
|
100
|
+
* field the first time `snapshot/color.ts` needs it, rather than an
|
|
101
|
+
* internal `TypeError`; see `requireColorField` there.
|
|
102
|
+
*/
|
|
103
|
+
backgroundImage: string | null;
|
|
104
|
+
/**
|
|
105
|
+
* Computed `text-shadow`, capped to ~120 chars, `'none'` when unset. A
|
|
106
|
+
* counter-fact only — dnumb cannot measure what a shadow does perceptually
|
|
107
|
+
* to a low-contrast ratio, so it discloses that one exists rather than
|
|
108
|
+
* guessing. Required — see `backgroundImage`'s doc comment above.
|
|
109
|
+
*/
|
|
110
|
+
textShadow: string;
|
|
111
|
+
/**
|
|
112
|
+
* Computed `-webkit-text-stroke-width` / `-color`. `'0px'` when unset.
|
|
113
|
+
* Required — see `backgroundImage`'s doc comment above.
|
|
114
|
+
*/
|
|
115
|
+
textStrokeWidth: string;
|
|
116
|
+
textStrokeColor: string;
|
|
117
|
+
fontSize: string;
|
|
118
|
+
fontWeight: number;
|
|
119
|
+
textAlign: string;
|
|
120
|
+
flexDirection: string;
|
|
121
|
+
gridTemplateColumns: string;
|
|
122
|
+
/**
|
|
123
|
+
* The three properties that decide what overflowing content *does*. Without
|
|
124
|
+
* them a measurement of 147px past the box is unreadable: it is truncation if
|
|
125
|
+
* the box clips, a scroll region if it scrolls, harmless paint-over if the
|
|
126
|
+
* overflow is visible, and not a text problem at all if the text can wrap.
|
|
127
|
+
*/
|
|
128
|
+
textOverflow: string;
|
|
129
|
+
whiteSpace: string;
|
|
130
|
+
overflowWrap: string;
|
|
131
|
+
/** First ~60 chars of the element's own direct text content, trimmed. */
|
|
132
|
+
text: string;
|
|
133
|
+
parentId: string | null;
|
|
134
|
+
childIds: string[];
|
|
135
|
+
/** `false` if `display:none`, `visibility:hidden`, `opacity:0`, or zero size. */
|
|
136
|
+
visible: boolean;
|
|
137
|
+
/** Present only when the element extends past a clipping ancestor. */
|
|
138
|
+
clip?: ClipFact;
|
|
139
|
+
ariaExpanded: string | null;
|
|
140
|
+
/**
|
|
141
|
+
* `document.elementsFromPoint` at this element's own box centre, front to
|
|
142
|
+
* back, resolved to the ids of elements this capture knows about -- the
|
|
143
|
+
* `walk` path's raw input for detecting a non-ancestor element painted over
|
|
144
|
+
* or behind this one's text (`snapshot/color.ts`'s overlap check). Computed
|
|
145
|
+
* only for a visible element with its own direct text (the same gate the
|
|
146
|
+
* color fact itself uses -- no text, no ink-on-backdrop question to
|
|
147
|
+
* probe), `null` otherwise. Always `null` on a `cdp` capture, which derives
|
|
148
|
+
* the same signal from `paintOrder` plus rect intersection instead: see
|
|
149
|
+
* `docs/notes/03-color.md` §3 and §7 for why the two paths take different
|
|
150
|
+
* inputs to the identical question.
|
|
151
|
+
*/
|
|
152
|
+
hitStackIds: string[] | null;
|
|
153
|
+
/**
|
|
154
|
+
* CDP `DOMSnapshot`'s paint order for this node. Combined Node-side with
|
|
155
|
+
* rect intersection as the `cdp` path's overlap signal, in place of
|
|
156
|
+
* `hitStackIds` -- see that field's doc comment. `null` on a `walk`
|
|
157
|
+
* capture, and on a `cdp` node with no layout row (nothing painted).
|
|
158
|
+
*/
|
|
159
|
+
paintOrder: number | null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* `<html>` (`documentElement`)'s own computed `background-color` and
|
|
163
|
+
* `color-scheme` — the two facts that decide what the browser's default
|
|
164
|
+
* canvas fill actually is, which is the *true* terminal layer a compositing
|
|
165
|
+
* walk that runs out of ancestors reaches (A2). Neither capture path visits
|
|
166
|
+
* `<html>` as part of the element tree (both start at `<body>` — see
|
|
167
|
+
* `collect.ts`/`cdp.ts`), so without this, `resolveBackdrop`'s ancestor walk
|
|
168
|
+
* has no way to see it and no honest way to know what is behind a
|
|
169
|
+
* fully-transparent `<body>`: guessing white is exactly the fabrication this
|
|
170
|
+
* package exists to refuse, since `color-scheme: dark` with no explicit
|
|
171
|
+
* background anywhere measures as `rgb(18, 18, 18)` on real Chromium, not
|
|
172
|
+
* white (verified, `docs/notes/03-color.md` and this feature's own probes).
|
|
173
|
+
*
|
|
174
|
+
* `colorScheme` is the raw computed string (`'normal'`, `'light'`, `'dark'`,
|
|
175
|
+
* `'light dark'`, ...) rather than a resolved color, because a page that
|
|
176
|
+
* lists both `light` and `dark` genuinely has no single determinable default
|
|
177
|
+
* — it depends on the reader's OS preference, which this capture cannot see
|
|
178
|
+
* — and `snapshot/color.ts#canvasBackground` reports that honestly as
|
|
179
|
+
* `unmeasured (canvas)` rather than picking one.
|
|
180
|
+
*/
|
|
181
|
+
export interface DocumentBackground {
|
|
182
|
+
backgroundColor: string;
|
|
183
|
+
colorScheme: string;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Content this capture walked past without measuring it: shadow DOM and iframe
|
|
187
|
+
* documents that no `ElementRecord` in `elements` describes. Neither capture
|
|
188
|
+
* path enters an iframe, and the portable walk does not enter a shadow root
|
|
189
|
+
* either — so an element living inside one is not folded, not dropped, not
|
|
190
|
+
* marked invisible; it is simply absent, with nothing on this record to say
|
|
191
|
+
* so. That absence is indistinguishable from "does not exist" unless it is
|
|
192
|
+
* disclosed here.
|
|
193
|
+
*
|
|
194
|
+
* A closed shadow root has no signal at all from outside it after the fact —
|
|
195
|
+
* `Element.shadowRoot` is `null` for both "no shadow root" and "closed shadow
|
|
196
|
+
* root", and nothing else in the DOM tells them apart post-hoc. `'unknown'` on
|
|
197
|
+
* that one field is not a placeholder for zero; a portable-walk capture
|
|
198
|
+
* genuinely cannot say whether the count is 0 or 40, and reporting 0 would be
|
|
199
|
+
* exactly the false zero this package exists to refuse. Every other field here
|
|
200
|
+
* is always a real, counted number.
|
|
201
|
+
*/
|
|
202
|
+
export interface NotMeasured {
|
|
203
|
+
shadowRoots: {
|
|
204
|
+
open: number;
|
|
205
|
+
closed: number | 'unknown';
|
|
206
|
+
};
|
|
207
|
+
iframes: {
|
|
208
|
+
sameOrigin: number;
|
|
209
|
+
crossOrigin: number;
|
|
210
|
+
/** Encountered, but this capture path could not resolve its origin. */
|
|
211
|
+
unknownOrigin: number;
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export interface Snapshot {
|
|
215
|
+
elements: ElementRecord[];
|
|
216
|
+
/** True when the collector hit its element cap before finishing the tree. */
|
|
217
|
+
truncated: boolean;
|
|
218
|
+
viewport: {
|
|
219
|
+
width: number;
|
|
220
|
+
height: number;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Optional only so a hand-built `Snapshot` in a test fixture need not name a
|
|
224
|
+
* value it has no opinion about. Both real capture paths — `collect.ts` and
|
|
225
|
+
* `cdp.ts` — always set it; `Bundle.notMeasured` is the field that actually
|
|
226
|
+
* has to be trusted, and it is not optional there.
|
|
227
|
+
*/
|
|
228
|
+
notMeasured?: NotMeasured;
|
|
229
|
+
/**
|
|
230
|
+
* See `DocumentBackground`'s doc comment. Optional for the same reason
|
|
231
|
+
* `notMeasured` is: both real capture paths always set it; a hand-built
|
|
232
|
+
* `Snapshot` need not. `| null` because `Bundle.documentBackground` (which
|
|
233
|
+
* structurally widens this same field — see `bundle.ts`) uses `null` for
|
|
234
|
+
* "a real bundle that predates color capture", and the two need to stay
|
|
235
|
+
* assignable to each other.
|
|
236
|
+
*/
|
|
237
|
+
documentBackground?: DocumentBackground | null;
|
|
238
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record shape everything downstream reads. `collect.ts` builds
|
|
3
|
+
* `ElementRecord[]` inside the page; nothing in this file depends on the DOM or
|
|
4
|
+
* on Playwright, so pure Node-side code — the digest, the diff, the query
|
|
5
|
+
* verbs — can import it freely.
|
|
6
|
+
*
|
|
7
|
+
* Ported from the prototype's `snapshot/types.ts`, with the fields the step-2
|
|
8
|
+
* spike proved load-bearing added: a *computed* role and accessible name (the
|
|
9
|
+
* prototype captured `getAttribute('role')`, which is null on nearly every real
|
|
10
|
+
* element), and the clip fact.
|
|
11
|
+
*/
|
|
12
|
+
import type { BoxSides, Rect } from "../analysis/types.js";
|
|
13
|
+
export type { BoxSides, Rect };
|
|
14
|
+
/**
|
|
15
|
+
* Why an element is not fully on screen, and the facts needed to judge whether
|
|
16
|
+
* that matters.
|
|
17
|
+
*
|
|
18
|
+
* Carrying `overflow` and `scrollable` beside the hidden extents is the
|
|
19
|
+
* facts-with-counter-facts model: a clipped box inside `overflow:auto` is a
|
|
20
|
+
* scroll region, and the same box inside `overflow:hidden` is content nobody
|
|
21
|
+
* can reach. The tool reports both and does not decide which one this is.
|
|
22
|
+
*/
|
|
23
|
+
export interface ClipFact {
|
|
24
|
+
/** Id of the nearest ancestor whose `overflow` is not `visible`. */
|
|
25
|
+
by: string;
|
|
26
|
+
byTag: string;
|
|
27
|
+
/** The clipper's `overflow-x`, or `x/y` when the two axes differ. */
|
|
28
|
+
overflow: string;
|
|
29
|
+
/** How far the element extends past the clipper on each side, in px. */
|
|
30
|
+
hidden: BoxSides;
|
|
31
|
+
/** The part of the element that survives the clip. */
|
|
32
|
+
visible: {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Whether a user could scroll the clipper to reveal the hidden part.
|
|
38
|
+
* Not the same question as `scrollHeight > clientHeight`: an
|
|
39
|
+
* `overflow:hidden` box has scroll extent and no reachability.
|
|
40
|
+
*/
|
|
41
|
+
scrollable: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface ElementRecord {
|
|
44
|
+
/** Stable within one capture only — not a DOM id, not stable across runs. */
|
|
45
|
+
id: string;
|
|
46
|
+
tag: string;
|
|
47
|
+
/** `data-testid` / `data-test-id` / `data-test` / `data-qa`, first one present. */
|
|
48
|
+
testId: string | null;
|
|
49
|
+
/** The authored `id` attribute, unfiltered. Identity applies its own guard. */
|
|
50
|
+
domId: string | null;
|
|
51
|
+
/** Computed role — explicit `role` if present, otherwise the implicit one. */
|
|
52
|
+
role: string | null;
|
|
53
|
+
/** Computed accessible name. */
|
|
54
|
+
name: string | null;
|
|
55
|
+
className: string;
|
|
56
|
+
/** From the tag (`h1`-`h6`) or `aria-level`; `null` for non-headings. */
|
|
57
|
+
headingLevel: number | null;
|
|
58
|
+
rect: Rect;
|
|
59
|
+
scrollWidth: number;
|
|
60
|
+
scrollHeight: number;
|
|
61
|
+
clientWidth: number;
|
|
62
|
+
clientHeight: number;
|
|
63
|
+
overflowX: string;
|
|
64
|
+
overflowY: string;
|
|
65
|
+
position: string;
|
|
66
|
+
display: string;
|
|
67
|
+
visibility: string;
|
|
68
|
+
opacity: number;
|
|
69
|
+
margin: BoxSides;
|
|
70
|
+
padding: BoxSides;
|
|
71
|
+
/** Row/column gap in px; only meaningful on grid and flex containers. */
|
|
72
|
+
gap: {
|
|
73
|
+
row: number;
|
|
74
|
+
column: number;
|
|
75
|
+
};
|
|
76
|
+
border: BoxSides;
|
|
77
|
+
color: string;
|
|
78
|
+
backgroundColor: string;
|
|
79
|
+
/**
|
|
80
|
+
* Computed `background-image`, capped to ~120 chars (both ends preserved —
|
|
81
|
+
* see `limits.ts#capBackgroundImage`), `'none'` when unset. Not captured
|
|
82
|
+
* for its own sake — it is the counter-fact that invalidates a resolved
|
|
83
|
+
* backdrop: a gradient or `url()` background means no single color exists
|
|
84
|
+
* behind text painted over it. See `snapshot/color.ts` and
|
|
85
|
+
* `docs/notes/03-color.md` §2–3.
|
|
86
|
+
*
|
|
87
|
+
* Required, like every other field on this interface — both real capture
|
|
88
|
+
* paths always set it to a string. `null` is a distinct, legitimate third
|
|
89
|
+
* state: "this specific capture never measured this field at all" — a
|
|
90
|
+
* bundle written before color capture existed, read back through
|
|
91
|
+
* `bundle.ts#normalizeElement`, which backfills `null` here rather than the
|
|
92
|
+
* false positive claim `'none'` used to be (A3). `resolveBackdrop` and
|
|
93
|
+
* `hasPaint` in `snapshot/color.ts` treat `null` the same way they treat an
|
|
94
|
+
* unresolvable non-ancestor overlay: refuse to guess, report
|
|
95
|
+
* `unmeasured (not-captured ...)`, never silently assume "no image here".
|
|
96
|
+
*
|
|
97
|
+
* A record built by hand (not via `capture()`/`parseBundle`) that omits
|
|
98
|
+
* this field entirely (`undefined`, not `null`), or any of the other three
|
|
99
|
+
* color-capture fields below, gets a clear thrown error naming the missing
|
|
100
|
+
* field the first time `snapshot/color.ts` needs it, rather than an
|
|
101
|
+
* internal `TypeError`; see `requireColorField` there.
|
|
102
|
+
*/
|
|
103
|
+
backgroundImage: string | null;
|
|
104
|
+
/**
|
|
105
|
+
* Computed `text-shadow`, capped to ~120 chars, `'none'` when unset. A
|
|
106
|
+
* counter-fact only — dnumb cannot measure what a shadow does perceptually
|
|
107
|
+
* to a low-contrast ratio, so it discloses that one exists rather than
|
|
108
|
+
* guessing. Required — see `backgroundImage`'s doc comment above.
|
|
109
|
+
*/
|
|
110
|
+
textShadow: string;
|
|
111
|
+
/**
|
|
112
|
+
* Computed `-webkit-text-stroke-width` / `-color`. `'0px'` when unset.
|
|
113
|
+
* Required — see `backgroundImage`'s doc comment above.
|
|
114
|
+
*/
|
|
115
|
+
textStrokeWidth: string;
|
|
116
|
+
textStrokeColor: string;
|
|
117
|
+
fontSize: string;
|
|
118
|
+
fontWeight: number;
|
|
119
|
+
textAlign: string;
|
|
120
|
+
flexDirection: string;
|
|
121
|
+
gridTemplateColumns: string;
|
|
122
|
+
/**
|
|
123
|
+
* The three properties that decide what overflowing content *does*. Without
|
|
124
|
+
* them a measurement of 147px past the box is unreadable: it is truncation if
|
|
125
|
+
* the box clips, a scroll region if it scrolls, harmless paint-over if the
|
|
126
|
+
* overflow is visible, and not a text problem at all if the text can wrap.
|
|
127
|
+
*/
|
|
128
|
+
textOverflow: string;
|
|
129
|
+
whiteSpace: string;
|
|
130
|
+
overflowWrap: string;
|
|
131
|
+
/** First ~60 chars of the element's own direct text content, trimmed. */
|
|
132
|
+
text: string;
|
|
133
|
+
parentId: string | null;
|
|
134
|
+
childIds: string[];
|
|
135
|
+
/** `false` if `display:none`, `visibility:hidden`, `opacity:0`, or zero size. */
|
|
136
|
+
visible: boolean;
|
|
137
|
+
/** Present only when the element extends past a clipping ancestor. */
|
|
138
|
+
clip?: ClipFact;
|
|
139
|
+
ariaExpanded: string | null;
|
|
140
|
+
/**
|
|
141
|
+
* `document.elementsFromPoint` at this element's own box centre, front to
|
|
142
|
+
* back, resolved to the ids of elements this capture knows about -- the
|
|
143
|
+
* `walk` path's raw input for detecting a non-ancestor element painted over
|
|
144
|
+
* or behind this one's text (`snapshot/color.ts`'s overlap check). Computed
|
|
145
|
+
* only for a visible element with its own direct text (the same gate the
|
|
146
|
+
* color fact itself uses -- no text, no ink-on-backdrop question to
|
|
147
|
+
* probe), `null` otherwise. Always `null` on a `cdp` capture, which derives
|
|
148
|
+
* the same signal from `paintOrder` plus rect intersection instead: see
|
|
149
|
+
* `docs/notes/03-color.md` §3 and §7 for why the two paths take different
|
|
150
|
+
* inputs to the identical question.
|
|
151
|
+
*/
|
|
152
|
+
hitStackIds: string[] | null;
|
|
153
|
+
/**
|
|
154
|
+
* CDP `DOMSnapshot`'s paint order for this node. Combined Node-side with
|
|
155
|
+
* rect intersection as the `cdp` path's overlap signal, in place of
|
|
156
|
+
* `hitStackIds` -- see that field's doc comment. `null` on a `walk`
|
|
157
|
+
* capture, and on a `cdp` node with no layout row (nothing painted).
|
|
158
|
+
*/
|
|
159
|
+
paintOrder: number | null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* `<html>` (`documentElement`)'s own computed `background-color` and
|
|
163
|
+
* `color-scheme` — the two facts that decide what the browser's default
|
|
164
|
+
* canvas fill actually is, which is the *true* terminal layer a compositing
|
|
165
|
+
* walk that runs out of ancestors reaches (A2). Neither capture path visits
|
|
166
|
+
* `<html>` as part of the element tree (both start at `<body>` — see
|
|
167
|
+
* `collect.ts`/`cdp.ts`), so without this, `resolveBackdrop`'s ancestor walk
|
|
168
|
+
* has no way to see it and no honest way to know what is behind a
|
|
169
|
+
* fully-transparent `<body>`: guessing white is exactly the fabrication this
|
|
170
|
+
* package exists to refuse, since `color-scheme: dark` with no explicit
|
|
171
|
+
* background anywhere measures as `rgb(18, 18, 18)` on real Chromium, not
|
|
172
|
+
* white (verified, `docs/notes/03-color.md` and this feature's own probes).
|
|
173
|
+
*
|
|
174
|
+
* `colorScheme` is the raw computed string (`'normal'`, `'light'`, `'dark'`,
|
|
175
|
+
* `'light dark'`, ...) rather than a resolved color, because a page that
|
|
176
|
+
* lists both `light` and `dark` genuinely has no single determinable default
|
|
177
|
+
* — it depends on the reader's OS preference, which this capture cannot see
|
|
178
|
+
* — and `snapshot/color.ts#canvasBackground` reports that honestly as
|
|
179
|
+
* `unmeasured (canvas)` rather than picking one.
|
|
180
|
+
*/
|
|
181
|
+
export interface DocumentBackground {
|
|
182
|
+
backgroundColor: string;
|
|
183
|
+
colorScheme: string;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Content this capture walked past without measuring it: shadow DOM and iframe
|
|
187
|
+
* documents that no `ElementRecord` in `elements` describes. Neither capture
|
|
188
|
+
* path enters an iframe, and the portable walk does not enter a shadow root
|
|
189
|
+
* either — so an element living inside one is not folded, not dropped, not
|
|
190
|
+
* marked invisible; it is simply absent, with nothing on this record to say
|
|
191
|
+
* so. That absence is indistinguishable from "does not exist" unless it is
|
|
192
|
+
* disclosed here.
|
|
193
|
+
*
|
|
194
|
+
* A closed shadow root has no signal at all from outside it after the fact —
|
|
195
|
+
* `Element.shadowRoot` is `null` for both "no shadow root" and "closed shadow
|
|
196
|
+
* root", and nothing else in the DOM tells them apart post-hoc. `'unknown'` on
|
|
197
|
+
* that one field is not a placeholder for zero; a portable-walk capture
|
|
198
|
+
* genuinely cannot say whether the count is 0 or 40, and reporting 0 would be
|
|
199
|
+
* exactly the false zero this package exists to refuse. Every other field here
|
|
200
|
+
* is always a real, counted number.
|
|
201
|
+
*/
|
|
202
|
+
export interface NotMeasured {
|
|
203
|
+
shadowRoots: {
|
|
204
|
+
open: number;
|
|
205
|
+
closed: number | 'unknown';
|
|
206
|
+
};
|
|
207
|
+
iframes: {
|
|
208
|
+
sameOrigin: number;
|
|
209
|
+
crossOrigin: number;
|
|
210
|
+
/** Encountered, but this capture path could not resolve its origin. */
|
|
211
|
+
unknownOrigin: number;
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export interface Snapshot {
|
|
215
|
+
elements: ElementRecord[];
|
|
216
|
+
/** True when the collector hit its element cap before finishing the tree. */
|
|
217
|
+
truncated: boolean;
|
|
218
|
+
viewport: {
|
|
219
|
+
width: number;
|
|
220
|
+
height: number;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Optional only so a hand-built `Snapshot` in a test fixture need not name a
|
|
224
|
+
* value it has no opinion about. Both real capture paths — `collect.ts` and
|
|
225
|
+
* `cdp.ts` — always set it; `Bundle.notMeasured` is the field that actually
|
|
226
|
+
* has to be trusted, and it is not optional there.
|
|
227
|
+
*/
|
|
228
|
+
notMeasured?: NotMeasured;
|
|
229
|
+
/**
|
|
230
|
+
* See `DocumentBackground`'s doc comment. Optional for the same reason
|
|
231
|
+
* `notMeasured` is: both real capture paths always set it; a hand-built
|
|
232
|
+
* `Snapshot` need not. `| null` because `Bundle.documentBackground` (which
|
|
233
|
+
* structurally widens this same field — see `bundle.ts`) uses `null` for
|
|
234
|
+
* "a real bundle that predates color capture", and the two need to stay
|
|
235
|
+
* assignable to each other.
|
|
236
|
+
*/
|
|
237
|
+
documentBackground?: DocumentBackground | null;
|
|
238
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|