@reticlehq/browser 2.13.0 → 2.14.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/actions/actions-dom.d.ts +0 -20
- package/dist/actions/actions-dom.js +0 -66
- package/dist/actions/actions-press.d.ts +10 -0
- package/dist/actions/actions-press.js +77 -0
- package/dist/actions/actions.d.ts +0 -89
- package/dist/actions/actions.js +24 -358
- package/dist/actions/appeared-text.d.ts +0 -20
- package/dist/actions/appeared-text.js +0 -36
- package/dist/actions/danger-context.d.ts +3 -0
- package/dist/actions/danger-context.js +39 -0
- package/dist/actions/scroll.d.ts +0 -9
- package/dist/actions/scroll.js +0 -7
- package/dist/actions/synthetic-input.d.ts +0 -7
- package/dist/actions/synthetic-input.js +0 -27
- package/dist/actions/value-input.d.ts +0 -30
- package/dist/actions/value-input.js +0 -33
- package/dist/commands/commands.d.ts +0 -2
- package/dist/commands/commands.js +0 -67
- package/dist/connect-options.d.ts +2 -96
- package/dist/dom/a11y.d.ts +0 -29
- package/dist/dom/a11y.js +1 -88
- package/dist/dom/auto-anchor.d.ts +0 -40
- package/dist/dom/auto-anchor.js +0 -33
- package/dist/dom/chart.d.ts +0 -61
- package/dist/dom/chart.js +0 -60
- package/dist/dom/desktop-capture.d.ts +0 -30
- package/dist/dom/desktop-capture.js +0 -72
- package/dist/dom/dom-ignore.d.ts +0 -30
- package/dist/dom/dom-ignore.js +0 -47
- package/dist/dom/occlusion.d.ts +0 -7
- package/dist/dom/occlusion.js +0 -7
- package/dist/dom/query.d.ts +0 -14
- package/dist/dom/query.js +48 -214
- package/dist/dom/realm.d.ts +0 -20
- package/dist/dom/realm.js +0 -28
- package/dist/dom/refs.d.ts +0 -76
- package/dist/dom/refs.js +2 -133
- package/dist/dom/shadow-registry.d.ts +0 -33
- package/dist/dom/shadow-registry.js +2 -72
- package/dist/dom/snapshot.d.ts +2 -43
- package/dist/dom/snapshot.js +26 -151
- package/dist/dom/source.d.ts +0 -44
- package/dist/dom/source.js +0 -45
- package/dist/dom/theme.d.ts +0 -37
- package/dist/dom/theme.js +2 -52
- package/dist/dom/virtualized.d.ts +0 -17
- package/dist/dom/virtualized.js +0 -27
- package/dist/edit-epoch.d.ts +0 -22
- package/dist/edit-epoch.js +0 -52
- package/dist/index.js +1 -23
- package/dist/observers/animation.d.ts +0 -1
- package/dist/observers/animation.js +0 -3
- package/dist/observers/blind-spots.d.ts +0 -26
- package/dist/observers/blind-spots.js +2 -61
- package/dist/observers/console.d.ts +1 -2
- package/dist/observers/console.js +41 -30
- package/dist/observers/context-open.d.ts +1 -11
- package/dist/observers/context-open.js +10 -14
- package/dist/observers/departure.d.ts +3 -0
- package/dist/observers/departure.js +52 -0
- package/dist/observers/dialogs.d.ts +2 -0
- package/dist/observers/dialogs.js +39 -0
- package/dist/observers/dom.js +0 -44
- package/dist/observers/download.d.ts +1 -2
- package/dist/observers/download.js +38 -55
- package/dist/observers/focus.d.ts +0 -6
- package/dist/observers/focus.js +0 -7
- package/dist/observers/frames.d.ts +0 -9
- package/dist/observers/frames.js +1 -28
- package/dist/observers/health.d.ts +0 -5
- package/dist/observers/health.js +1 -33
- package/dist/observers/install-all.d.ts +2 -0
- package/dist/observers/install-all.js +26 -27
- package/dist/observers/ipc.d.ts +0 -25
- package/dist/observers/ipc.js +0 -65
- package/dist/observers/navigation.d.ts +2 -0
- package/dist/observers/navigation.js +33 -0
- package/dist/observers/network-body.d.ts +5 -28
- package/dist/observers/network-body.js +11 -108
- package/dist/observers/network-redact.d.ts +1 -20
- package/dist/observers/network-redact.js +1 -80
- package/dist/observers/network-stream.d.ts +0 -21
- package/dist/observers/network-stream.js +1 -44
- package/dist/observers/network.d.ts +2 -26
- package/dist/observers/network.js +177 -294
- package/dist/observers/perf.d.ts +0 -10
- package/dist/observers/perf.js +0 -16
- package/dist/observers/route.d.ts +1 -2
- package/dist/observers/route.js +13 -19
- package/dist/observers/scroll.d.ts +0 -1
- package/dist/observers/scroll.js +0 -9
- package/dist/observers/sdk-failure.d.ts +1 -9
- package/dist/observers/sdk-failure.js +1 -30
- package/dist/observers/state.d.ts +1 -11
- package/dist/observers/state.js +13 -41
- package/dist/observers/storage.d.ts +0 -14
- package/dist/observers/storage.js +10 -34
- package/dist/observers/types.d.ts +0 -15
- package/dist/observers/types.js +0 -14
- package/dist/patching/capture-method.d.ts +0 -24
- package/dist/patching/capture-method.js +0 -26
- package/dist/presenter/overlay.d.ts +0 -1
- package/dist/presenter/overlay.js +0 -1
- package/dist/presenter/presenter-brand.d.ts +0 -19
- package/dist/presenter/presenter-brand.js +0 -21
- package/dist/presenter/presenter-config.d.ts +1 -105
- package/dist/presenter/presenter-config.js +2 -91
- package/dist/presenter/presenter-controls.d.ts +0 -29
- package/dist/presenter/presenter-controls.js +3 -131
- package/dist/presenter/presenter-dock-layout.d.ts +0 -3
- package/dist/presenter/presenter-dock-layout.js +0 -4
- package/dist/presenter/presenter-drag.d.ts +2 -24
- package/dist/presenter/presenter-drag.js +0 -26
- package/dist/presenter/presenter-effects.d.ts +0 -9
- package/dist/presenter/presenter-effects.js +0 -9
- package/dist/presenter/presenter-glow.d.ts +0 -21
- package/dist/presenter/presenter-glow.js +1 -27
- package/dist/presenter/presenter-heroicons-data.d.ts +0 -8
- package/dist/presenter/presenter-heroicons-data.js +0 -8
- package/dist/presenter/presenter-hud-chrome.d.ts +0 -10
- package/dist/presenter/presenter-hud-chrome.js +0 -10
- package/dist/presenter/presenter-icons.d.ts +0 -6
- package/dist/presenter/presenter-icons.js +0 -6
- package/dist/presenter/presenter-log.d.ts +0 -38
- package/dist/presenter/presenter-log.js +1 -45
- package/dist/presenter/presenter-report-copy.d.ts +5 -66
- package/dist/presenter/presenter-report-copy.js +5 -66
- package/dist/presenter/presenter-report-styles.d.ts +0 -6
- package/dist/presenter/presenter-report-styles.js +6 -6
- package/dist/presenter/presenter-report.d.ts +4 -7
- package/dist/presenter/presenter-report.js +18 -76
- package/dist/presenter/presenter-run-state.d.ts +0 -7
- package/dist/presenter/presenter-run-state.js +0 -5
- package/dist/presenter/presenter-settings-styles.d.ts +0 -1
- package/dist/presenter/presenter-settings-styles.js +0 -1
- package/dist/presenter/presenter-settings.d.ts +1 -23
- package/dist/presenter/presenter-settings.js +1 -31
- package/dist/presenter/presenter-shell-styles.js +2 -5
- package/dist/presenter/presenter-shell.d.ts +2 -18
- package/dist/presenter/presenter-shell.js +1 -73
- package/dist/presenter/presenter-styles.d.ts +0 -9
- package/dist/presenter/presenter-styles.js +0 -9
- package/dist/presenter/presenter-tally.d.ts +0 -5
- package/dist/presenter/presenter-tally.js +0 -4
- package/dist/presenter/presenter-test-helpers.d.ts +0 -2
- package/dist/presenter/presenter-test-helpers.js +0 -2
- package/dist/presenter/presenter-verbs.js +0 -7
- package/dist/presenter/presenter-workspace.d.ts +0 -4
- package/dist/presenter/presenter-workspace.js +1 -8
- package/dist/presenter/presenter.d.ts +0 -60
- package/dist/presenter/presenter.js +4 -118
- package/dist/recorder/recorder-styles.d.ts +0 -6
- package/dist/recorder/recorder-styles.js +0 -6
- package/dist/recorder/recorder.d.ts +0 -34
- package/dist/recorder/recorder.js +0 -49
- package/dist/registry/adapters.d.ts +0 -10
- package/dist/registry/adapters.js +0 -10
- package/dist/registry/auto-testids.d.ts +0 -19
- package/dist/registry/auto-testids.js +1 -21
- package/dist/registry/capabilities.d.ts +0 -31
- package/dist/registry/capabilities.js +1 -46
- package/dist/registry/commit-and-signal.d.ts +0 -6
- package/dist/registry/commit-and-signal.js +1 -7
- package/dist/registry/domains.d.ts +0 -8
- package/dist/registry/domains.js +0 -7
- package/dist/registry/emitter.d.ts +0 -14
- package/dist/registry/emitter.js +0 -10
- package/dist/registry/store-adapters.d.ts +0 -186
- package/dist/registry/store-adapters.js +0 -163
- package/dist/registry/stores.d.ts +0 -33
- package/dist/registry/stores.js +0 -59
- package/dist/reticle-presenter-helpers.d.ts +0 -13
- package/dist/reticle-presenter-helpers.js +0 -14
- package/dist/reticle.d.ts +1 -52
- package/dist/reticle.js +20 -145
- package/dist/review/annotator-styles.js +0 -7
- package/dist/review/annotator.d.ts +2 -13
- package/dist/review/annotator.js +0 -40
- package/dist/review/mark-anchor.d.ts +0 -11
- package/dist/review/mark-anchor.js +0 -8
- package/dist/security/serialization.d.ts +0 -20
- package/dist/security/serialization.js +1 -66
- package/dist/session-continuity.d.ts +0 -16
- package/dist/session-continuity.js +1 -20
- package/dist/timers/clock.d.ts +1 -14
- package/dist/timers/clock.js +59 -60
- package/dist/timers/native-console.js +0 -4
- package/dist/timers/native-timers.d.ts +0 -25
- package/dist/timers/native-timers.js +3 -33
- package/dist/transport/transport.d.ts +0 -51
- package/dist/transport/transport.js +3 -129
- package/dist/transport/unreachable-message.d.ts +0 -17
- package/dist/transport/unreachable-message.js +0 -17
- package/dist/util/captured-method.d.ts +0 -35
- package/dist/util/captured-method.js +0 -35
- package/package.json +2 -2
package/dist/dom/realm.js
CHANGED
|
@@ -1,35 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-realm type tests.
|
|
3
|
-
*
|
|
4
|
-
* `instanceof` compares against ONE realm's constructor. An element inside a same-origin `<iframe>`
|
|
5
|
-
* lives in that document's realm, so `frameButton instanceof HTMLElement` — evaluated with the top
|
|
6
|
-
* window's `HTMLElement` — is FALSE for a perfectly ordinary button.
|
|
7
|
-
*
|
|
8
|
-
* That is not theoretical. Once `query` learned to descend into same-origin frames, it returned the
|
|
9
|
-
* frame's button correctly and `act` then rejected the ref with "is not an HTMLElement": found, and
|
|
10
|
-
* unusable. A find you cannot act on is worse than a miss, because it reads as a product bug in the
|
|
11
|
-
* app rather than in the tool.
|
|
12
|
-
*
|
|
13
|
-
* These resolve the constructor from the NODE's own document instead, so an element is measured
|
|
14
|
-
* against its own realm. Same-realm nodes behave exactly as `instanceof` did.
|
|
15
|
-
*/
|
|
16
|
-
/** The constructor named `name` in the realm that owns `node`, or undefined if there isn't one. */
|
|
17
1
|
function ctorIn(node, name) {
|
|
18
2
|
if (null === node || typeof node !== 'object')
|
|
19
3
|
return undefined;
|
|
20
|
-
// A Document owns itself; every other node reports an ownerDocument.
|
|
21
4
|
const doc = node.ownerDocument ?? node;
|
|
22
5
|
const view = doc.defaultView;
|
|
23
6
|
if (null === view || view === undefined)
|
|
24
7
|
return undefined;
|
|
25
8
|
return view[name];
|
|
26
9
|
}
|
|
27
|
-
/**
|
|
28
|
-
* `node instanceof <name>`, evaluated in the node's own realm.
|
|
29
|
-
*
|
|
30
|
-
* Falls back to the ambient constructor when the node has no view — a detached document, or a jsdom
|
|
31
|
-
* fragment — so behaviour outside a browser is unchanged.
|
|
32
|
-
*/
|
|
33
10
|
function isIn(node, name, ambient) {
|
|
34
11
|
const ctor = ctorIn(node, name) ?? ambient;
|
|
35
12
|
return 'function' === typeof ctor && node instanceof ctor;
|
|
@@ -43,11 +20,6 @@ export const isSelect = (n) => isIn(n, 'HTMLSelectElement', 'undefined' === type
|
|
|
43
20
|
export const isButton = (n) => isIn(n, 'HTMLButtonElement', 'undefined' === typeof HTMLButtonElement ? undefined : HTMLButtonElement);
|
|
44
21
|
export const isForm = (n) => isIn(n, 'HTMLFormElement', 'undefined' === typeof HTMLFormElement ? undefined : HTMLFormElement);
|
|
45
22
|
export const isFrame = (n) => isIn(n, 'HTMLIFrameElement', 'undefined' === typeof HTMLIFrameElement ? undefined : HTMLIFrameElement);
|
|
46
|
-
/**
|
|
47
|
-
* The prototype whose native value setter drives `el` — React and friends install their own accessor
|
|
48
|
-
* on the instance, so a value write has to go through the prototype's. It must come from the
|
|
49
|
-
* ELEMENT's realm for the same reason as above.
|
|
50
|
-
*/
|
|
51
23
|
export function valuePrototypeOf(el) {
|
|
52
24
|
const name = isTextArea(el) ? 'HTMLTextAreaElement' : 'HTMLInputElement';
|
|
53
25
|
const ctor = ctorIn(el, name);
|
package/dist/dom/refs.d.ts
CHANGED
|
@@ -1,90 +1,14 @@
|
|
|
1
1
|
import { type Ref } from '@reticlehq/core';
|
|
2
|
-
/**
|
|
3
|
-
* Stable, human-meaningful element handles. Each element gets a ref like `e7`; the same
|
|
4
|
-
* element keeps its ref across snapshots, and a ref re-resolves to its element as long as
|
|
5
|
-
* the element is still in the document.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* How many ref -> element entries are retained.
|
|
9
|
-
*
|
|
10
|
-
* The forward map is weak, so elements are always collectable; this bounds the BOOKKEEPING, which was
|
|
11
|
-
* not. Refs are minted far more often than an agent asks about them — every meaningful DOM addition,
|
|
12
|
-
* every transitionend, every scroll reveal — and the only eviction used to be "the agent happened to
|
|
13
|
-
* resolve this exact dead ref". A busy app over a long session therefore accumulated entries for
|
|
14
|
-
* elements that had been garbage for hours.
|
|
15
|
-
*
|
|
16
|
-
* Sized generously because eviction is not free of consequence (see refFor): the useful lifetime of a
|
|
17
|
-
* ref is one agent turn, and this is far more than any one turn produces.
|
|
18
|
-
*/
|
|
19
2
|
export declare const MAX_TRACKED_REFS = 10000;
|
|
20
|
-
/**
|
|
21
|
-
* A ref, made safe to interpolate into an error message.
|
|
22
|
-
*
|
|
23
|
-
* A ref Reticle mints is `e7` — three characters. A ref a CALLER passes is whatever they typed, and
|
|
24
|
-
* an agent guessing at the schema can pass 100KB of it. Every stale-ref message interpolates that
|
|
25
|
-
* value directly, so the caller's own bad argument came back as a 100KB error.
|
|
26
|
-
*
|
|
27
|
-
* The server caps error messages centrally, but it cannot help here: the browser's transport
|
|
28
|
-
* serializer truncates from the TAIL first, which severs "… no longer resolves to an element" — the
|
|
29
|
-
* exact substring the server's recovery table matches on. A stale ref then arrives classified as a
|
|
30
|
-
* possible Reticle defect. So the echo is bounded at the source, where the untrusted value enters
|
|
31
|
-
* the message.
|
|
32
|
-
*/
|
|
33
3
|
export declare function echoRef(ref: string): string;
|
|
34
|
-
/**
|
|
35
|
-
* How many numbers a document claims at once.
|
|
36
|
-
*
|
|
37
|
-
* The reservation is a synchronous storage write, so it cannot happen per mint — mints are frequent
|
|
38
|
-
* (every meaningful DOM addition, every transitionend, every scroll reveal). One write per this many
|
|
39
|
-
* mints is free.
|
|
40
|
-
*
|
|
41
|
-
* Sized to keep refs SHORT, and the first attempt at that reasoned about the wrong axis. It bounded
|
|
42
|
-
* how many navigations it took to reach seven digits and concluded "a hundred" — but the cost is not
|
|
43
|
-
* paid at seven digits, it is paid at every digit, on every ref, in every snapshot and every error
|
|
44
|
-
* message, on every turn. At 10,000 the SECOND document already mints five-digit refs.
|
|
45
|
-
*
|
|
46
|
-
* Measured on one unchanged three-element page across a session: 61 tokens on the first document,
|
|
47
|
-
* 64 after a reload, 65 later — +6.5% for the same page, entirely in ref width. The benchmark saw the
|
|
48
|
-
* same thing as a ~4% rise in average observation cost, which is what a benchmark is for.
|
|
49
|
-
*
|
|
50
|
-
* So the block is small. A document claims another one when it outmints this, which costs one more
|
|
51
|
-
* storage write and is exactly proportional: a page with thousands of elements has a long snapshot
|
|
52
|
-
* anyway, and a small page — the common case, and the one an agent drives repeatedly — keeps
|
|
53
|
-
* three-digit refs across dozens of navigations.
|
|
54
|
-
*/
|
|
55
4
|
export declare const REF_BLOCK = 100;
|
|
56
5
|
export declare class RefRegistry {
|
|
57
6
|
#private;
|
|
58
|
-
/**
|
|
59
|
-
* Storage is injected rather than read from the global for the same reason the clock is: it is an
|
|
60
|
-
* ambient dependency that decides behaviour, and the cross-document property is untestable if the
|
|
61
|
-
* only way to get a second document is to actually navigate.
|
|
62
|
-
*/
|
|
63
7
|
constructor(storage?: () => Storage | undefined);
|
|
64
|
-
/** How many reverse entries are currently retained. Exposed so the bound can be asserted. */
|
|
65
8
|
get size(): number;
|
|
66
|
-
/**
|
|
67
|
-
* A hot update just replaced modules in this document: every ref handed out so far was minted
|
|
68
|
-
* against the DOM the previous version of that code produced.
|
|
69
|
-
*
|
|
70
|
-
* Only the boundary is recorded. Nothing is evicted — a hot update is not a navigation, and most
|
|
71
|
-
* of the page survives one, so refs minted before it very often still resolve. This exists so the
|
|
72
|
-
* ones that DON'T can say why.
|
|
73
|
-
*/
|
|
74
9
|
markEdited(): void;
|
|
75
|
-
/** Was this ref handed out before the last hot update? False while no update has been observed. */
|
|
76
10
|
mintedBeforeLastEdit(ref: string): boolean;
|
|
77
|
-
/** Get the existing ref for an element, or mint a new one. */
|
|
78
11
|
refFor(el: Element): Ref;
|
|
79
|
-
/**
|
|
80
|
-
* Resolve a ref back to its element, or null if it's gone/detached.
|
|
81
|
-
*
|
|
82
|
-
* Takes a plain `string` ON PURPOSE: this is the untrusted-input path — the ref comes from the agent
|
|
83
|
-
* over the wire, and a miss is answered with null rather than an error. Requiring a branded Ref here
|
|
84
|
-
* would force a meaningless cast at every wire boundary and buy nothing; the brand's value is on the
|
|
85
|
-
* MINT (refFor) so our own code cannot pass, say, a sessionId where a handle is expected.
|
|
86
|
-
*/
|
|
87
12
|
resolve(ref: string): Element | null;
|
|
88
13
|
}
|
|
89
|
-
/** Process-wide registry shared by snapshot, query, and the action executor. */
|
|
90
14
|
export declare const refs: RefRegistry;
|
package/dist/dom/refs.js
CHANGED
|
@@ -1,94 +1,14 @@
|
|
|
1
1
|
import { TRANSPORT_LIMITS, asRef } from '@reticlehq/core';
|
|
2
|
-
/**
|
|
3
|
-
* Stable, human-meaningful element handles. Each element gets a ref like `e7`; the same
|
|
4
|
-
* element keeps its ref across snapshots, and a ref re-resolves to its element as long as
|
|
5
|
-
* the element is still in the document.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* How many ref -> element entries are retained.
|
|
9
|
-
*
|
|
10
|
-
* The forward map is weak, so elements are always collectable; this bounds the BOOKKEEPING, which was
|
|
11
|
-
* not. Refs are minted far more often than an agent asks about them — every meaningful DOM addition,
|
|
12
|
-
* every transitionend, every scroll reveal — and the only eviction used to be "the agent happened to
|
|
13
|
-
* resolve this exact dead ref". A busy app over a long session therefore accumulated entries for
|
|
14
|
-
* elements that had been garbage for hours.
|
|
15
|
-
*
|
|
16
|
-
* Sized generously because eviction is not free of consequence (see refFor): the useful lifetime of a
|
|
17
|
-
* ref is one agent turn, and this is far more than any one turn produces.
|
|
18
|
-
*/
|
|
19
2
|
export const MAX_TRACKED_REFS = 10000;
|
|
20
|
-
/** Marks a ref that was too long to echo in full. */
|
|
21
3
|
const REF_ELISION = '…';
|
|
22
|
-
/** Every ref Reticle mints is this letter followed by its sequence number. */
|
|
23
4
|
const REF_PREFIX = 'e';
|
|
24
|
-
/**
|
|
25
|
-
* A ref, made safe to interpolate into an error message.
|
|
26
|
-
*
|
|
27
|
-
* A ref Reticle mints is `e7` — three characters. A ref a CALLER passes is whatever they typed, and
|
|
28
|
-
* an agent guessing at the schema can pass 100KB of it. Every stale-ref message interpolates that
|
|
29
|
-
* value directly, so the caller's own bad argument came back as a 100KB error.
|
|
30
|
-
*
|
|
31
|
-
* The server caps error messages centrally, but it cannot help here: the browser's transport
|
|
32
|
-
* serializer truncates from the TAIL first, which severs "… no longer resolves to an element" — the
|
|
33
|
-
* exact substring the server's recovery table matches on. A stale ref then arrives classified as a
|
|
34
|
-
* possible Reticle defect. So the echo is bounded at the source, where the untrusted value enters
|
|
35
|
-
* the message.
|
|
36
|
-
*/
|
|
37
5
|
export function echoRef(ref) {
|
|
38
6
|
const max = TRANSPORT_LIMITS.MAX_REF_LENGTH;
|
|
39
7
|
return max >= ref.length ? ref : `${ref.slice(0, max)}${REF_ELISION}`;
|
|
40
8
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Full-sweep cadence. The deref-sweep is O(retained), and a page legitimately holding ≥ MAX_TRACKED_REFS
|
|
43
|
-
* live elements (a 10k-node dashboard) can never get under the cap that way, so running it on EVERY mint
|
|
44
|
-
* over the cap turned each mint into a 10k-entry scan — measured 264µs vs 0.20µs (1,300x) on the app's
|
|
45
|
-
* main thread, ~2.6% of it forever on a churning page. The cap is instead held by an O(1) oldest-drop per
|
|
46
|
-
* over-cap mint, and the expensive dead-entry sweep runs once per this many mints (amortized O(1)).
|
|
47
|
-
*/
|
|
48
9
|
const SWEEP_EVERY_MINTS = 1000;
|
|
49
|
-
/**
|
|
50
|
-
* Where a document records the ref numbers it has claimed, so the next one starts past them.
|
|
51
|
-
*
|
|
52
|
-
* Refs are per-DOCUMENT: a reload or a cross-page link tears this module down and the next document
|
|
53
|
-
* used to start minting from `e1` again. So `e7` from page A was a valid, resolvable, DIFFERENT
|
|
54
|
-
* element on page B — nothing refused, the wrong element was acted on, and the result came back `ok`.
|
|
55
|
-
* The SPA case was always safe (WeakRef liveness + isConnected); this was the other half, and the
|
|
56
|
-
* more dangerous one, because it produced a false green instead of an error.
|
|
57
|
-
*
|
|
58
|
-
* Deliberately NOT a change to the ref grammar. Stamping refs `e7@3` would have worked too, but every
|
|
59
|
-
* agent passes refs back verbatim and every saved flow on disk already holds bare ones, so it buys a
|
|
60
|
-
* wire-format migration for a property the sequence itself can carry. A number that never restarts
|
|
61
|
-
* means a stale ref simply misses the map, and the refusal that already exists — already worded for
|
|
62
|
-
* exactly this case — fires unchanged.
|
|
63
|
-
*
|
|
64
|
-
* Namespaced like the session key it sits beside; `sessionStorage` is the right scope for the same
|
|
65
|
-
* reason session continuity uses it (survives reloads and same-tab navigations, not shared with
|
|
66
|
-
* another tab, which is a different session with its own registry anyway).
|
|
67
|
-
*/
|
|
68
10
|
const REF_BASE_KEY = '__reticle_ref_base';
|
|
69
|
-
/**
|
|
70
|
-
* How many numbers a document claims at once.
|
|
71
|
-
*
|
|
72
|
-
* The reservation is a synchronous storage write, so it cannot happen per mint — mints are frequent
|
|
73
|
-
* (every meaningful DOM addition, every transitionend, every scroll reveal). One write per this many
|
|
74
|
-
* mints is free.
|
|
75
|
-
*
|
|
76
|
-
* Sized to keep refs SHORT, and the first attempt at that reasoned about the wrong axis. It bounded
|
|
77
|
-
* how many navigations it took to reach seven digits and concluded "a hundred" — but the cost is not
|
|
78
|
-
* paid at seven digits, it is paid at every digit, on every ref, in every snapshot and every error
|
|
79
|
-
* message, on every turn. At 10,000 the SECOND document already mints five-digit refs.
|
|
80
|
-
*
|
|
81
|
-
* Measured on one unchanged three-element page across a session: 61 tokens on the first document,
|
|
82
|
-
* 64 after a reload, 65 later — +6.5% for the same page, entirely in ref width. The benchmark saw the
|
|
83
|
-
* same thing as a ~4% rise in average observation cost, which is what a benchmark is for.
|
|
84
|
-
*
|
|
85
|
-
* So the block is small. A document claims another one when it outmints this, which costs one more
|
|
86
|
-
* storage write and is exactly proportional: a page with thousands of elements has a long snapshot
|
|
87
|
-
* anyway, and a small page — the common case, and the one an agent drives repeatedly — keeps
|
|
88
|
-
* three-digit refs across dozens of navigations.
|
|
89
|
-
*/
|
|
90
11
|
export const REF_BLOCK = 100;
|
|
91
|
-
/** The reserved-block high-water mark, or 0 when storage is unavailable or its value is junk. */
|
|
92
12
|
function readBase(store) {
|
|
93
13
|
try {
|
|
94
14
|
const raw = store?.getItem(REF_BASE_KEY);
|
|
@@ -96,7 +16,7 @@ function readBase(store) {
|
|
|
96
16
|
return Number.isSafeInteger(value) && value > 0 ? value : 0;
|
|
97
17
|
}
|
|
98
18
|
catch {
|
|
99
|
-
return 0;
|
|
19
|
+
return 0;
|
|
100
20
|
}
|
|
101
21
|
}
|
|
102
22
|
export class RefRegistry {
|
|
@@ -104,34 +24,19 @@ export class RefRegistry {
|
|
|
104
24
|
#fromRef = new Map();
|
|
105
25
|
#storage;
|
|
106
26
|
#seq = 0;
|
|
107
|
-
/** The end of the block currently reserved. Minting past it claims the next one. */
|
|
108
27
|
#reserved = 0;
|
|
109
28
|
#mintsSinceSweep = 0;
|
|
110
|
-
/**
|
|
111
|
-
* The highest number handed out before the last hot update was applied, or 0 while none has been.
|
|
112
|
-
*
|
|
113
|
-
* A watermark rather than a per-ref stamp: the sequence never restarts within a document, so one
|
|
114
|
-
* number separates "minted before the code changed" from "minted after it" for every ref at once.
|
|
115
|
-
* The alternative — an epoch on every map entry — is bookkeeping paid on every mint for an answer
|
|
116
|
-
* needed only on the rare refusal.
|
|
117
|
-
*/
|
|
118
29
|
#editedAtSeq = 0;
|
|
119
|
-
/**
|
|
120
|
-
* Storage is injected rather than read from the global for the same reason the clock is: it is an
|
|
121
|
-
* ambient dependency that decides behaviour, and the cross-document property is untestable if the
|
|
122
|
-
* only way to get a second document is to actually navigate.
|
|
123
|
-
*/
|
|
124
30
|
constructor(storage = () => globalThis.sessionStorage) {
|
|
125
31
|
this.#storage = storage;
|
|
126
32
|
}
|
|
127
|
-
/** Read the tab's high-water mark and claim the next block, BEFORE any of it is handed out. */
|
|
128
33
|
#reserve() {
|
|
129
34
|
let store;
|
|
130
35
|
try {
|
|
131
36
|
store = this.#storage();
|
|
132
37
|
}
|
|
133
38
|
catch {
|
|
134
|
-
store = undefined;
|
|
39
|
+
store = undefined;
|
|
135
40
|
}
|
|
136
41
|
const base = Math.max(readBase(store), this.#seq);
|
|
137
42
|
this.#reserved = base + REF_BLOCK;
|
|
@@ -139,25 +44,17 @@ export class RefRegistry {
|
|
|
139
44
|
store?.setItem(REF_BASE_KEY, String(this.#reserved));
|
|
140
45
|
}
|
|
141
46
|
catch {
|
|
142
|
-
/* same: never block a mint on storage */
|
|
143
47
|
}
|
|
144
|
-
// Claimed on the FIRST mint, not at unload — a page that crashes, is killed, or navigates away
|
|
145
|
-
// without firing unload has still permanently spent its numbers.
|
|
146
48
|
this.#seq = base;
|
|
147
49
|
}
|
|
148
|
-
/** How many reverse entries are currently retained. Exposed so the bound can be asserted. */
|
|
149
50
|
get size() {
|
|
150
51
|
return this.#fromRef.size;
|
|
151
52
|
}
|
|
152
|
-
/** O(1): drop the least-recently-minted reverse entry. Map iterates in insertion order, so the front
|
|
153
|
-
* is the oldest. A still-live element whose entry is dropped re-registers on its next refFor. */
|
|
154
53
|
#evictOldest() {
|
|
155
54
|
const oldest = this.#fromRef.keys().next();
|
|
156
55
|
if (oldest.done !== true)
|
|
157
56
|
this.#fromRef.delete(oldest.value);
|
|
158
57
|
}
|
|
159
|
-
/** Full pass: drop every entry whose element has been collected or detached (frees genuinely dead
|
|
160
|
-
* bookkeeping), then any remaining excess by age. Amortized — called once per SWEEP_EVERY_MINTS. */
|
|
161
58
|
#sweep() {
|
|
162
59
|
for (const [ref, weak] of this.#fromRef) {
|
|
163
60
|
const el = weak.deref();
|
|
@@ -167,33 +64,18 @@ export class RefRegistry {
|
|
|
167
64
|
while (this.#fromRef.size > MAX_TRACKED_REFS)
|
|
168
65
|
this.#evictOldest();
|
|
169
66
|
}
|
|
170
|
-
/**
|
|
171
|
-
* A hot update just replaced modules in this document: every ref handed out so far was minted
|
|
172
|
-
* against the DOM the previous version of that code produced.
|
|
173
|
-
*
|
|
174
|
-
* Only the boundary is recorded. Nothing is evicted — a hot update is not a navigation, and most
|
|
175
|
-
* of the page survives one, so refs minted before it very often still resolve. This exists so the
|
|
176
|
-
* ones that DON'T can say why.
|
|
177
|
-
*/
|
|
178
67
|
markEdited() {
|
|
179
68
|
this.#editedAtSeq = this.#seq;
|
|
180
69
|
}
|
|
181
|
-
/** Was this ref handed out before the last hot update? False while no update has been observed. */
|
|
182
70
|
mintedBeforeLastEdit(ref) {
|
|
183
71
|
if (0 === this.#editedAtSeq || !ref.startsWith(REF_PREFIX))
|
|
184
72
|
return false;
|
|
185
73
|
const n = Number(ref.slice(REF_PREFIX.length));
|
|
186
74
|
return Number.isSafeInteger(n) && n > 0 && n <= this.#editedAtSeq;
|
|
187
75
|
}
|
|
188
|
-
/** Get the existing ref for an element, or mint a new one. */
|
|
189
76
|
refFor(el) {
|
|
190
77
|
const existing = this.#toRef.get(el);
|
|
191
78
|
if (existing !== undefined) {
|
|
192
|
-
// Re-register if this element's entry was evicted while it was off the agent's radar. Without
|
|
193
|
-
// this, an element that is still on the page could hold a ref that no longer resolves — the
|
|
194
|
-
// eviction would surface as "element not found", which is a wrong answer rather than a slow one.
|
|
195
|
-
// Either way, TOUCH it to the recent end (LRU): an element being surfaced right now must not be
|
|
196
|
-
// the oldest-drop victim of a mint storm before the agent gets to act on it.
|
|
197
79
|
const weak = this.#fromRef.get(existing);
|
|
198
80
|
this.#fromRef.delete(existing);
|
|
199
81
|
this.#fromRef.set(existing, weak ?? new WeakRef(el));
|
|
@@ -205,7 +87,6 @@ export class RefRegistry {
|
|
|
205
87
|
const ref = asRef(`${REF_PREFIX}${String(this.#seq)}`);
|
|
206
88
|
this.#toRef.set(el, ref);
|
|
207
89
|
this.#fromRef.set(ref, new WeakRef(el));
|
|
208
|
-
// Keep bounded cheaply on every mint; run the full dead-entry sweep only periodically.
|
|
209
90
|
this.#mintsSinceSweep += 1;
|
|
210
91
|
if (this.#mintsSinceSweep >= SWEEP_EVERY_MINTS) {
|
|
211
92
|
this.#mintsSinceSweep = 0;
|
|
@@ -216,14 +97,6 @@ export class RefRegistry {
|
|
|
216
97
|
}
|
|
217
98
|
return ref;
|
|
218
99
|
}
|
|
219
|
-
/**
|
|
220
|
-
* Resolve a ref back to its element, or null if it's gone/detached.
|
|
221
|
-
*
|
|
222
|
-
* Takes a plain `string` ON PURPOSE: this is the untrusted-input path — the ref comes from the agent
|
|
223
|
-
* over the wire, and a miss is answered with null rather than an error. Requiring a branded Ref here
|
|
224
|
-
* would force a meaningless cast at every wire boundary and buy nothing; the brand's value is on the
|
|
225
|
-
* MINT (refFor) so our own code cannot pass, say, a sessionId where a handle is expected.
|
|
226
|
-
*/
|
|
227
100
|
resolve(ref) {
|
|
228
101
|
const weak = this.#fromRef.get(ref);
|
|
229
102
|
if (weak === undefined)
|
|
@@ -233,13 +106,9 @@ export class RefRegistry {
|
|
|
233
106
|
this.#fromRef.delete(ref);
|
|
234
107
|
return null;
|
|
235
108
|
}
|
|
236
|
-
// LRU touch: a ref the agent just resolved is in ACTIVE use — move it to the most-recent end so an
|
|
237
|
-
// oldest-drop eviction between this resolve and the follow-up act can't reclaim it and turn a live
|
|
238
|
-
// element into a false "ref no longer resolves". O(1) — Map preserves insertion order.
|
|
239
109
|
this.#fromRef.delete(ref);
|
|
240
110
|
this.#fromRef.set(ref, weak);
|
|
241
111
|
return el;
|
|
242
112
|
}
|
|
243
113
|
}
|
|
244
|
-
/** Process-wide registry shared by snapshot, query, and the action executor. */
|
|
245
114
|
export const refs = new RefRegistry();
|
|
@@ -1,40 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Every shadow root this SDK has seen created, open or closed.
|
|
3
|
-
*
|
|
4
|
-
* Two problems are solved by the same record.
|
|
5
|
-
*
|
|
6
|
-
* **Observation.** A `MutationObserver` on `documentElement` does NOT cross a shadow boundary, so a
|
|
7
|
-
* click that changes text inside a web component produced an entirely empty observe window. Measured
|
|
8
|
-
* on a console with a shadow-DOM alert badge: the click landed, the badge changed, and `observe`
|
|
9
|
-
* reported zero DOM events — indistinguishable from an app that ignored the click. The DOM observer
|
|
10
|
-
* needs a list of roots to attach to, and no API enumerates them.
|
|
11
|
-
*
|
|
12
|
-
* **Closed roots.** `element.shadowRoot` is null forever for `mode: 'closed'`, so a closed root is
|
|
13
|
-
* unreadable AFTER the fact — but `attachShadow` still RETURNS it to its caller. Patching that call
|
|
14
|
-
* captures the root at the one instant it is exposed. This only works when the SDK is installed
|
|
15
|
-
* before the component upgrades, which is why hosts are tracked individually rather than assumed:
|
|
16
|
-
* whatever was missed is still declared as a blind spot, and only what was genuinely captured stops
|
|
17
|
-
* being one.
|
|
18
|
-
*/
|
|
19
|
-
/**
|
|
20
|
-
* Bookkeeping ceiling. Weak references make the ROOTS collectable; this bounds the entries pointing
|
|
21
|
-
* at them, which are not. Sized far above any real page's live component count.
|
|
22
|
-
*/
|
|
23
1
|
export declare const MAX_TRACKED_ROOTS = 4000;
|
|
24
|
-
/** Dead-entry sweeps are O(retained), so they run once per this many roots rather than per root. */
|
|
25
2
|
export declare const SWEEP_EVERY = 250;
|
|
26
|
-
/** Shadow roots captured so far. Includes closed roots when the patch was installed in time. */
|
|
27
3
|
export declare function capturedRoots(): readonly ShadowRoot[];
|
|
28
|
-
/** Whether this element's shadow root was captured — i.e. whether its contents are reachable. */
|
|
29
4
|
export declare function isCaptured(host: Element): boolean;
|
|
30
|
-
/** The captured root for a host, or null. Non-null for closed roots the patch caught in time. */
|
|
31
5
|
export declare function capturedRootOf(host: Element): ShadowRoot | null;
|
|
32
|
-
/** Run `listener` for every root captured from now on. Returns an unsubscribe. */
|
|
33
6
|
export declare function onShadowRoot(listener: (root: ShadowRoot) => void): () => void;
|
|
34
|
-
/**
|
|
35
|
-
* Patch `attachShadow` to record each root as it is created, and sweep the roots that already exist.
|
|
36
|
-
*
|
|
37
|
-
* The sweep can only find OPEN roots (there is no other way in), so a closed root attached before
|
|
38
|
-
* install stays unreachable — correctly, and it is declared as such.
|
|
39
|
-
*/
|
|
40
7
|
export declare function installShadowRegistry(): () => void;
|
|
@@ -1,63 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Every shadow root this SDK has seen created, open or closed.
|
|
3
|
-
*
|
|
4
|
-
* Two problems are solved by the same record.
|
|
5
|
-
*
|
|
6
|
-
* **Observation.** A `MutationObserver` on `documentElement` does NOT cross a shadow boundary, so a
|
|
7
|
-
* click that changes text inside a web component produced an entirely empty observe window. Measured
|
|
8
|
-
* on a console with a shadow-DOM alert badge: the click landed, the badge changed, and `observe`
|
|
9
|
-
* reported zero DOM events — indistinguishable from an app that ignored the click. The DOM observer
|
|
10
|
-
* needs a list of roots to attach to, and no API enumerates them.
|
|
11
|
-
*
|
|
12
|
-
* **Closed roots.** `element.shadowRoot` is null forever for `mode: 'closed'`, so a closed root is
|
|
13
|
-
* unreadable AFTER the fact — but `attachShadow` still RETURNS it to its caller. Patching that call
|
|
14
|
-
* captures the root at the one instant it is exposed. This only works when the SDK is installed
|
|
15
|
-
* before the component upgrades, which is why hosts are tracked individually rather than assumed:
|
|
16
|
-
* whatever was missed is still declared as a blind spot, and only what was genuinely captured stops
|
|
17
|
-
* being one.
|
|
18
|
-
*/
|
|
19
1
|
import { captureMethod } from '../patching/capture-method.js';
|
|
20
|
-
/**
|
|
21
|
-
* How many installs are live.
|
|
22
|
-
*
|
|
23
|
-
* The state below is module-level, and a dev-only SDK gets installed more than once: Vite re-runs the
|
|
24
|
-
* module on hot reload, so `connect()` can run again before the previous instance is torn down.
|
|
25
|
-
* Measured: install, subscribe, install again, tear down the SECOND one — and the FIRST instance's
|
|
26
|
-
* subscribers were gone, so the DOM observer silently stopped being told about new shadow roots for
|
|
27
|
-
* the rest of the session. Shared state must only be cleared by the last install standing.
|
|
28
|
-
*/
|
|
29
2
|
let installs = 0;
|
|
30
|
-
/**
|
|
31
|
-
* Retained shadow roots, weakly.
|
|
32
|
-
*
|
|
33
|
-
* This was a strong `Set` and it never shrank. Measured: a virtualized list that renders and removes
|
|
34
|
-
* 5,000 web-component rows left 5,000 roots retained — each pinning its whole shadow subtree, and
|
|
35
|
-
* pinning its host through `root.host`, so nothing the list discarded could ever be collected. A
|
|
36
|
-
* design system's rows ARE web components, so the most ordinary data-heavy page is the worst case,
|
|
37
|
-
* and this SDK is meant to sit in a dev session all day.
|
|
38
|
-
*
|
|
39
|
-
* Same shape `RefRegistry` already uses for element handles: hold weakly, bound the bookkeeping,
|
|
40
|
-
* sweep the dead entries periodically rather than on every write.
|
|
41
|
-
*/
|
|
42
3
|
const roots = new Set();
|
|
43
|
-
/**
|
|
44
|
-
* Membership, separately and weakly.
|
|
45
|
-
*
|
|
46
|
-
* The weak entries above are for ENUMERATION and cannot answer "have I seen this root" without
|
|
47
|
-
* dereferencing all of them — which turns every `attachShadow` into a scan of every root so far, and
|
|
48
|
-
* a 5,000-row list into 25 million comparisons. A WeakSet answers it in O(1) and retains nothing.
|
|
49
|
-
*/
|
|
50
4
|
const known = new WeakSet();
|
|
51
5
|
const hosts = new WeakSet();
|
|
52
|
-
/**
|
|
53
|
-
* Bookkeeping ceiling. Weak references make the ROOTS collectable; this bounds the entries pointing
|
|
54
|
-
* at them, which are not. Sized far above any real page's live component count.
|
|
55
|
-
*/
|
|
56
6
|
export const MAX_TRACKED_ROOTS = 4000;
|
|
57
|
-
/** Dead-entry sweeps are O(retained), so they run once per this many roots rather than per root. */
|
|
58
7
|
export const SWEEP_EVERY = 250;
|
|
59
8
|
let sinceSweep = 0;
|
|
60
|
-
/** Drop entries whose root has been collected, then any excess by age (Set keeps insertion order). */
|
|
61
9
|
function sweep() {
|
|
62
10
|
for (const ref of roots) {
|
|
63
11
|
if (ref.deref() === undefined)
|
|
@@ -70,7 +18,6 @@ function sweep() {
|
|
|
70
18
|
roots.delete(oldest.value);
|
|
71
19
|
}
|
|
72
20
|
}
|
|
73
|
-
/** The live roots behind the weak entries. */
|
|
74
21
|
function liveRoots() {
|
|
75
22
|
const out = [];
|
|
76
23
|
for (const ref of roots) {
|
|
@@ -82,10 +29,6 @@ function liveRoots() {
|
|
|
82
29
|
}
|
|
83
30
|
const listeners = new Set();
|
|
84
31
|
function record(root, host) {
|
|
85
|
-
// Idempotent. A hot reload leaves one patch wrapping another, so a single `attachShadow` travels
|
|
86
|
-
// through both and reports twice; the initial sweep can also meet a root the patch already caught.
|
|
87
|
-
// Subscribers must see each root exactly once — they attach an observer per root, and counting one
|
|
88
|
-
// twice is how a "reported once" guarantee quietly becomes "reported per install".
|
|
89
32
|
if (known.has(root))
|
|
90
33
|
return;
|
|
91
34
|
known.add(root);
|
|
@@ -99,15 +42,12 @@ function record(root, host) {
|
|
|
99
42
|
for (const listener of listeners)
|
|
100
43
|
listener(root);
|
|
101
44
|
}
|
|
102
|
-
/** Shadow roots captured so far. Includes closed roots when the patch was installed in time. */
|
|
103
45
|
export function capturedRoots() {
|
|
104
46
|
return liveRoots();
|
|
105
47
|
}
|
|
106
|
-
/** Whether this element's shadow root was captured — i.e. whether its contents are reachable. */
|
|
107
48
|
export function isCaptured(host) {
|
|
108
49
|
return hosts.has(host);
|
|
109
50
|
}
|
|
110
|
-
/** The captured root for a host, or null. Non-null for closed roots the patch caught in time. */
|
|
111
51
|
export function capturedRootOf(host) {
|
|
112
52
|
if (!hosts.has(host))
|
|
113
53
|
return null;
|
|
@@ -116,21 +56,12 @@ export function capturedRootOf(host) {
|
|
|
116
56
|
return root;
|
|
117
57
|
return null;
|
|
118
58
|
}
|
|
119
|
-
/** Run `listener` for every root captured from now on. Returns an unsubscribe. */
|
|
120
59
|
export function onShadowRoot(listener) {
|
|
121
60
|
listeners.add(listener);
|
|
122
61
|
return () => listeners.delete(listener);
|
|
123
62
|
}
|
|
124
|
-
/**
|
|
125
|
-
* Patch `attachShadow` to record each root as it is created, and sweep the roots that already exist.
|
|
126
|
-
*
|
|
127
|
-
* The sweep can only find OPEN roots (there is no other way in), so a closed root attached before
|
|
128
|
-
* install stays unreachable — correctly, and it is declared as such.
|
|
129
|
-
*/
|
|
130
63
|
export function installShadowRegistry() {
|
|
131
64
|
const proto = Element.prototype;
|
|
132
|
-
// Through captureMethod, like every other patch site here: reading a method to hold and restore it
|
|
133
|
-
// is the one thing `unbound-method` exists to flag, and the intention is named there once.
|
|
134
65
|
const original = captureMethod(proto, 'attachShadow');
|
|
135
66
|
if (typeof original !== 'function')
|
|
136
67
|
return () => undefined;
|
|
@@ -146,19 +77,18 @@ export function installShadowRegistry() {
|
|
|
146
77
|
const root = el.shadowRoot;
|
|
147
78
|
if (null === root)
|
|
148
79
|
continue;
|
|
149
|
-
record(root, el);
|
|
80
|
+
record(root, el);
|
|
150
81
|
sweep(root);
|
|
151
82
|
}
|
|
152
83
|
};
|
|
153
84
|
if (document.documentElement !== null)
|
|
154
85
|
sweep(document.documentElement);
|
|
155
86
|
return () => {
|
|
156
|
-
// Restore only if nobody patched over us; clobbering a later patch is worse than leaving ours.
|
|
157
87
|
if (captureMethod(proto, 'attachShadow') === patched)
|
|
158
88
|
proto.attachShadow = original;
|
|
159
89
|
installs -= 1;
|
|
160
90
|
if (installs > 0)
|
|
161
|
-
return;
|
|
91
|
+
return;
|
|
162
92
|
roots.clear();
|
|
163
93
|
listeners.clear();
|
|
164
94
|
};
|
package/dist/dom/snapshot.d.ts
CHANGED
|
@@ -2,12 +2,7 @@ import { SnapshotMode } from '@reticlehq/core';
|
|
|
2
2
|
interface SnapshotStatus {
|
|
3
3
|
route: string;
|
|
4
4
|
title: string;
|
|
5
|
-
/** Open dialogs, OMITTED when there are none — absence means "no dialog is up". */
|
|
6
5
|
visibleDialogs?: string[];
|
|
7
|
-
/**
|
|
8
|
-
* Present ONLY when a whole-page snapshot came back near-empty because an open overlay
|
|
9
|
-
* aria-hidden the rest of the page (#397). Absence means the page is not hidden behind an overlay.
|
|
10
|
-
*/
|
|
11
6
|
overlayHidingPage?: string;
|
|
12
7
|
}
|
|
13
8
|
export interface SnapshotResult {
|
|
@@ -15,32 +10,11 @@ export interface SnapshotResult {
|
|
|
15
10
|
status: SnapshotStatus;
|
|
16
11
|
nodes: number;
|
|
17
12
|
truncated: boolean;
|
|
18
|
-
/**
|
|
19
|
-
* True when a `scope` was given but resolved to nothing. The snapshot then covers NOTHING rather
|
|
20
|
-
* than silently falling back to the whole page — an agent snapshotting "the modal" after it closed
|
|
21
|
-
* must not receive the entire page as if it were the modal.
|
|
22
|
-
*/
|
|
23
13
|
scopeMissing?: boolean;
|
|
24
|
-
/**
|
|
25
|
-
* How many MEANINGFUL nodes the lean walk passed over, present only in a lean mode and only when
|
|
26
|
-
* non-zero. This is what stops an empty `interactive` tree reading as an empty page — the mode is
|
|
27
|
-
* a role filter, and a production app whose controls carry no roles yields nothing while being
|
|
28
|
-
* full of things to drive. It shipped untyped: the value was spread into the result and every
|
|
29
|
-
* consumer had to reach for it without a type, including the tool that builds the note from it.
|
|
30
|
-
*/
|
|
31
14
|
leanSkipped?: number;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* cut's own frontier: re-snapshot each with `{ scope: ref, includeRoot: true }` and the union is
|
|
35
|
-
* the whole tree. Without it `truncated` says only THAT the read stopped, never WHERE, so nobody
|
|
36
|
-
* could finish it — and a reader who does not finish it must not conclude anything is ABSENT.
|
|
37
|
-
*/
|
|
15
|
+
hiddenSkipped?: number;
|
|
16
|
+
domElements?: number;
|
|
38
17
|
unread?: string[];
|
|
39
|
-
/**
|
|
40
|
-
* True when the frontier itself did not fit in `MAX_UNREAD_BRANCHES`, so `unread` is a prefix of
|
|
41
|
-
* the branches that were cut. Completion is then impossible from this read, and the difference
|
|
42
|
-
* between "impossible" and "not attempted" is the whole point of saying so.
|
|
43
|
-
*/
|
|
44
18
|
unreadOverflow?: true;
|
|
45
19
|
}
|
|
46
20
|
interface SnapshotOptions {
|
|
@@ -48,23 +22,8 @@ interface SnapshotOptions {
|
|
|
48
22
|
mode?: SnapshotMode | undefined;
|
|
49
23
|
maxNodes?: number | undefined;
|
|
50
24
|
maxDepth?: number | undefined;
|
|
51
|
-
/**
|
|
52
|
-
* Emit the scope element's OWN line before its descendants. Off by default (a scoped snapshot has
|
|
53
|
-
* always been the subtree UNDER the scope); the completion re-read needs it, because the branch it
|
|
54
|
-
* is re-reading is exactly the node the truncated walk stopped before emitting.
|
|
55
|
-
*/
|
|
56
25
|
includeRoot?: boolean | undefined;
|
|
57
26
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Cap on how many cut branches one snapshot names.
|
|
60
|
-
*
|
|
61
|
-
* A hostile or merely huge DOM can cut thousands of siblings at once (a non-virtualized grid does it
|
|
62
|
-
* routinely), and a frontier that large is neither affordable to send nor useful to re-read one at a
|
|
63
|
-
* time. Fifty is well above what a real page's cut produces — the frontier is the branches at the
|
|
64
|
-
* node cap, not the nodes themselves — and small enough that the list stays a hint rather than a
|
|
65
|
-
* second snapshot. Past it the read declares `unreadOverflow` instead of quietly sending fewer.
|
|
66
|
-
*/
|
|
67
27
|
export declare const MAX_UNREAD_BRANCHES = 50;
|
|
68
|
-
/** Build the semantic accessibility snapshot of the page or a subtree. */
|
|
69
28
|
export declare function buildSnapshot(options?: SnapshotOptions): SnapshotResult;
|
|
70
29
|
export {};
|