@reticlehq/browser 2.13.1 → 3.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/dist/actions/actions-dom.d.ts +8 -20
- package/dist/actions/actions-dom.js +70 -69
- package/dist/actions/actions-press.d.ts +14 -0
- package/dist/actions/actions-press.js +123 -0
- package/dist/actions/actions.d.ts +1 -89
- package/dist/actions/actions.js +64 -365
- package/dist/actions/appeared-text.d.ts +0 -20
- package/dist/actions/appeared-text.js +6 -36
- package/dist/actions/danger-context.d.ts +3 -0
- package/dist/actions/danger-context.js +39 -0
- package/dist/actions/scroll.d.ts +8 -10
- package/dist/actions/scroll.js +28 -17
- package/dist/actions/synthetic/synthetic-input.d.ts +2 -0
- package/dist/actions/synthetic/synthetic-input.js +13 -0
- 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 +10 -70
- package/dist/connect-options.d.ts +2 -96
- package/dist/dom/a11y.d.ts +0 -29
- package/dist/dom/a11y.js +3 -90
- package/dist/dom/addressing/auto-anchor.d.ts +25 -0
- package/dist/dom/addressing/auto-anchor.js +41 -0
- package/dist/dom/addressing/refs.d.ts +14 -0
- package/dist/dom/addressing/refs.js +114 -0
- package/dist/dom/addressing/source.d.ts +9 -0
- package/dist/dom/addressing/source.js +29 -0
- 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 +1 -73
- package/dist/dom/dom-ignore.d.ts +0 -30
- package/dist/dom/dom-ignore.js +1 -48
- package/dist/dom/occlusion.d.ts +0 -7
- package/dist/dom/occlusion.js +31 -12
- package/dist/dom/paint-context.d.ts +6 -0
- package/dist/dom/paint-context.js +38 -0
- package/dist/dom/query.d.ts +0 -14
- package/dist/dom/query.js +50 -216
- package/dist/dom/realm.d.ts +0 -20
- package/dist/dom/realm.js +0 -28
- 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 +27 -152
- 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 +1 -23
- package/dist/edit-epoch.js +1 -53
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -28
- package/dist/observers/animation.d.ts +0 -1
- package/dist/observers/animation.js +1 -4
- package/dist/observers/blind-spots.d.ts +0 -26
- package/dist/observers/blind-spots.js +3 -62
- 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 +28 -52
- package/dist/observers/download.d.ts +1 -2
- package/dist/observers/download.js +39 -56
- 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 +2 -34
- 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/net-detail/network-body.d.ts +12 -0
- package/dist/observers/net-detail/network-body.js +71 -0
- package/dist/observers/net-detail/network-redact.d.ts +1 -0
- package/dist/observers/net-detail/network-redact.js +1 -0
- package/dist/observers/network-stream.d.ts +0 -21
- package/dist/observers/network-stream.js +3 -46
- package/dist/observers/network.d.ts +3 -27
- package/dist/observers/network.js +178 -295
- 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 +2 -11
- 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/chrome/layers.d.ts +2 -0
- package/dist/presenter/chrome/layers.js +2 -0
- package/dist/presenter/chrome/log-kinds.d.ts +12 -0
- package/dist/presenter/chrome/log-kinds.js +7 -0
- package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -1
- package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -1
- package/dist/presenter/chrome/presenter-brand.d.ts +3 -0
- package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -21
- package/dist/presenter/chrome/presenter-effects.d.ts +4 -0
- package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -10
- package/dist/presenter/chrome/presenter-hud-chrome.d.ts +7 -0
- package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -10
- package/dist/presenter/chrome/presenter-log.d.ts +17 -0
- package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +5 -54
- package/dist/presenter/chrome/presenter-report-copy.d.ts +51 -0
- package/dist/presenter/chrome/presenter-report-copy.js +95 -0
- package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
- package/dist/presenter/chrome/presenter-safe-html.js +16 -0
- package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -5
- package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -5
- package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -7
- package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -8
- package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -8
- package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -6
- package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -6
- package/dist/presenter/presenter-account-styles.d.ts +1 -0
- package/dist/presenter/presenter-account-styles.js +99 -0
- package/dist/presenter/presenter-account.d.ts +40 -0
- package/dist/presenter/presenter-account.js +178 -0
- package/dist/presenter/presenter-config.d.ts +10 -107
- package/dist/presenter/presenter-config.js +5 -92
- package/dist/presenter/presenter-controls.d.ts +1 -35
- package/dist/presenter/presenter-controls.js +10 -133
- package/dist/presenter/presenter-dock-layout.d.ts +0 -3
- package/dist/presenter/presenter-dock-layout.js +1 -5
- package/dist/presenter/presenter-drag.d.ts +2 -42
- package/dist/presenter/presenter-drag.js +3 -76
- package/dist/presenter/presenter-glow.d.ts +0 -21
- package/dist/presenter/presenter-glow.js +2 -28
- package/dist/presenter/presenter-hud-position.d.ts +18 -0
- package/dist/presenter/presenter-hud-position.js +50 -0
- package/dist/presenter/presenter-report-styles.d.ts +0 -6
- package/dist/presenter/presenter-report-styles.js +22 -7
- package/dist/presenter/presenter-report.d.ts +5 -7
- package/dist/presenter/presenter-report.js +44 -95
- package/dist/presenter/presenter-run-state.d.ts +1 -8
- package/dist/presenter/presenter-run-state.js +1 -6
- package/dist/presenter/presenter-settings-styles.d.ts +0 -1
- package/dist/presenter/presenter-settings-styles.js +2 -2
- package/dist/presenter/presenter-settings.d.ts +5 -23
- package/dist/presenter/presenter-settings.js +21 -33
- package/dist/presenter/presenter-shell-styles.js +23 -20
- package/dist/presenter/presenter-shell.d.ts +6 -18
- package/dist/presenter/presenter-shell.js +26 -77
- package/dist/presenter/presenter-styles.d.ts +2 -10
- package/dist/presenter/presenter-styles.js +6 -12
- package/dist/presenter/presenter-workspace.d.ts +4 -4
- package/dist/presenter/presenter-workspace.js +30 -10
- package/dist/presenter/presenter.d.ts +3 -63
- package/dist/presenter/presenter.js +29 -132
- package/dist/presenter/tour/tour-view.d.ts +33 -0
- package/dist/presenter/tour/tour-view.js +188 -0
- package/dist/presenter/tour/tour.d.ts +25 -0
- package/dist/presenter/tour/tour.js +245 -0
- 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 +6 -49
- 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/declared-channels.d.ts +2 -0
- package/dist/registry/declared-channels.js +14 -0
- package/dist/registry/declared-platform.d.ts +4 -0
- package/dist/registry/declared-platform.js +34 -0
- 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 +1 -11
- package/dist/registry/store-adapters.d.ts +0 -186
- package/dist/registry/store-adapters.js +1 -164
- package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -10
- package/dist/registry/{adapters.js → stores/adapters.js} +0 -10
- package/dist/registry/stores.d.ts +0 -33
- package/dist/registry/stores.js +1 -60
- package/dist/reticle-presenter-helpers.d.ts +0 -13
- package/dist/reticle-presenter-helpers.js +10 -20
- package/dist/reticle-singleton.d.ts +2 -0
- package/dist/reticle-singleton.js +3 -0
- package/dist/reticle.d.ts +1 -52
- package/dist/reticle.js +115 -180
- package/dist/review/annotator-styles.js +1 -8
- package/dist/review/annotator.d.ts +2 -13
- package/dist/review/annotator.js +2 -42
- package/dist/review/mark-anchor.d.ts +0 -11
- package/dist/review/mark-anchor.js +3 -11
- 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/console/native-console.js +5 -0
- package/dist/timers/native/native-timers.d.ts +11 -0
- package/dist/timers/native/native-timers.js +48 -0
- package/dist/transport/transport.d.ts +0 -51
- package/dist/transport/transport.js +5 -131
- 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 +5 -4
- package/dist/actions/synthetic-input.d.ts +0 -9
- package/dist/actions/synthetic-input.js +0 -40
- package/dist/dom/auto-anchor.d.ts +0 -65
- package/dist/dom/auto-anchor.js +0 -74
- package/dist/dom/refs.d.ts +0 -90
- package/dist/dom/refs.js +0 -245
- package/dist/dom/source.d.ts +0 -53
- package/dist/dom/source.js +0 -74
- package/dist/observers/network-body.d.ts +0 -35
- package/dist/observers/network-body.js +0 -168
- package/dist/observers/network-redact.d.ts +0 -20
- package/dist/observers/network-redact.js +0 -80
- package/dist/presenter/presenter-brand.d.ts +0 -22
- package/dist/presenter/presenter-effects.d.ts +0 -13
- package/dist/presenter/presenter-hud-chrome.d.ts +0 -17
- package/dist/presenter/presenter-log.d.ts +0 -64
- package/dist/presenter/presenter-report-copy.d.ts +0 -112
- package/dist/presenter/presenter-report-copy.js +0 -156
- package/dist/presenter/presenter-test-helpers.d.ts +0 -16
- package/dist/presenter/presenter-test-helpers.js +0 -41
- package/dist/timers/native-console.js +0 -9
- package/dist/timers/native-timers.d.ts +0 -36
- package/dist/timers/native-timers.js +0 -78
- /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
- /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
package/dist/dom/chart.d.ts
CHANGED
|
@@ -1,83 +1,22 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chart inspection — the one dashboard surface Reticle could not previously see.
|
|
3
|
-
*
|
|
4
|
-
* A chart is where the store and the screen diverge most easily and most silently. Everything else on
|
|
5
|
-
* a dashboard is text: a KPI card renders a number you can read, a table renders rows you can count,
|
|
6
|
-
* and if the store disagrees with either, a state-vs-DOM comparison catches it. A chart renders
|
|
7
|
-
* GEOMETRY — the data has been through a scale function into `points`/`d` coordinates — and geometry
|
|
8
|
-
* is not text. `series` can be perfectly correct in the store while the polyline is blank, flat, or
|
|
9
|
-
* full of NaN, and no reader of either side would notice.
|
|
10
|
-
*
|
|
11
|
-
* Two failure classes are worth detecting, and they are chosen because they are DECIDABLE from the
|
|
12
|
-
* element alone — no baseline image, no per-library scale inversion, no false positives from a chart
|
|
13
|
-
* that is merely ugly:
|
|
14
|
-
*
|
|
15
|
-
* 1. **Non-finite coordinates.** `NaN`/`Infinity` in a path reaches the DOM whenever the scale math
|
|
16
|
-
* divides by a zero range — an empty series, a single-point series, or min === max. The browser
|
|
17
|
-
* silently renders nothing. This is a genuine bug every time; there is no chart for which NaN
|
|
18
|
-
* coordinates are correct.
|
|
19
|
-
* 2. **Degenerate geometry.** A chart element that exists but encloses no area: no points at all, or
|
|
20
|
-
* every point identical. Distinguishes "the chart is missing" (already catchable — the element is
|
|
21
|
-
* absent) from "the chart is present and empty", which looks healthy to a presence check and to a
|
|
22
|
-
* screenshot diff against a baseline that was also empty.
|
|
23
|
-
*
|
|
24
|
-
* Deliberately NOT attempted: comparing plotted geometry against the source data. That needs the
|
|
25
|
-
* chart's scale functions inverted per library, is a large surface to get wrong, and `chartInstance`
|
|
26
|
-
* below obsoletes it for the libraries people actually use — Chart.js and ECharts both hang their
|
|
27
|
-
* instance off the canvas element, so their data can be read directly rather than reverse-engineered
|
|
28
|
-
* from pixels.
|
|
29
|
-
*/
|
|
30
1
|
export interface ChartFinding {
|
|
31
|
-
/** What is wrong, as a stable machine-readable kind. */
|
|
32
2
|
kind: 'non-finite-coordinates' | 'empty-geometry' | 'degenerate-geometry';
|
|
33
|
-
/** The offending element's tag, so the agent can find it. */
|
|
34
3
|
tag: string;
|
|
35
|
-
/** Which attribute carried the fault. */
|
|
36
4
|
attr: string;
|
|
37
|
-
/** A short excerpt of the offending value — bounded; the full path can be thousands of chars. */
|
|
38
5
|
sample: string;
|
|
39
6
|
}
|
|
40
7
|
export interface ChartReport {
|
|
41
|
-
/** How many data-bearing geometry elements were examined. */
|
|
42
8
|
examined: number;
|
|
43
9
|
findings: ChartFinding[];
|
|
44
|
-
/** True when a canvas is present, i.e. the geometry is NOT in the DOM and this report cannot see it. */
|
|
45
10
|
canvas: boolean;
|
|
46
11
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Every number embedded in a geometry attribute. Path data interleaves single-letter drawing
|
|
49
|
-
* commands with coordinate pairs, so this extracts numeric-looking tokens rather than parsing the
|
|
50
|
-
* path grammar — the question here is only "is any coordinate non-finite", which does not need a
|
|
51
|
-
* full parser.
|
|
52
|
-
*/
|
|
53
12
|
export declare function geometryNumbers(value: string): number[];
|
|
54
|
-
/** True when the attribute contains a coordinate the browser cannot render. */
|
|
55
13
|
export declare function hasNonFiniteCoordinate(value: string): boolean;
|
|
56
|
-
/** True when the geometry encloses no area — no coordinates, or every coordinate pair identical. */
|
|
57
14
|
export declare function isDegenerate(value: string): boolean;
|
|
58
|
-
/**
|
|
59
|
-
* Inspect a subtree for chart geometry faults. Pure over the element — no events, no globals — so it
|
|
60
|
-
* can back a tool call, an assertion, or a snapshot annotation without any of them owning it.
|
|
61
|
-
*/
|
|
62
15
|
export declare function inspectChart(root: Element): ChartReport;
|
|
63
|
-
/** The shape a canvas charting library hangs off its canvas element, once located. */
|
|
64
16
|
export interface CanvasChartData {
|
|
65
17
|
library: 'chartjs' | 'echarts';
|
|
66
|
-
/** The library's own data structure — series/datasets — read directly, not inferred from pixels. */
|
|
67
18
|
data: unknown;
|
|
68
19
|
}
|
|
69
|
-
/**
|
|
70
|
-
* Read the data behind a CANVAS chart.
|
|
71
|
-
*
|
|
72
|
-
* Canvas is the one place where "instrument from inside the app" is not merely cheaper than pixels but
|
|
73
|
-
* is the only option at all: the chart is a bitmap, so the DOM shows an empty `<canvas>` and a
|
|
74
|
-
* screenshot shows colours no assertion can interpret. Both major canvas libraries keep their live
|
|
75
|
-
* instance reachable from the element — Chart.js via its registry, ECharts via a per-DOM lookup — so
|
|
76
|
-
* the series data is READABLE rather than reverse-engineerable.
|
|
77
|
-
*
|
|
78
|
-
* Takes the globals as arguments rather than reaching for `window`, so this stays pure and testable
|
|
79
|
-
* and the SDK never depends on either library being present.
|
|
80
|
-
*/
|
|
81
20
|
export declare function canvasChartData(canvas: Element, globals: {
|
|
82
21
|
Chart?: {
|
|
83
22
|
getChart?: (el: Element) => {
|
package/dist/dom/chart.js
CHANGED
|
@@ -1,72 +1,24 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chart inspection — the one dashboard surface Reticle could not previously see.
|
|
3
|
-
*
|
|
4
|
-
* A chart is where the store and the screen diverge most easily and most silently. Everything else on
|
|
5
|
-
* a dashboard is text: a KPI card renders a number you can read, a table renders rows you can count,
|
|
6
|
-
* and if the store disagrees with either, a state-vs-DOM comparison catches it. A chart renders
|
|
7
|
-
* GEOMETRY — the data has been through a scale function into `points`/`d` coordinates — and geometry
|
|
8
|
-
* is not text. `series` can be perfectly correct in the store while the polyline is blank, flat, or
|
|
9
|
-
* full of NaN, and no reader of either side would notice.
|
|
10
|
-
*
|
|
11
|
-
* Two failure classes are worth detecting, and they are chosen because they are DECIDABLE from the
|
|
12
|
-
* element alone — no baseline image, no per-library scale inversion, no false positives from a chart
|
|
13
|
-
* that is merely ugly:
|
|
14
|
-
*
|
|
15
|
-
* 1. **Non-finite coordinates.** `NaN`/`Infinity` in a path reaches the DOM whenever the scale math
|
|
16
|
-
* divides by a zero range — an empty series, a single-point series, or min === max. The browser
|
|
17
|
-
* silently renders nothing. This is a genuine bug every time; there is no chart for which NaN
|
|
18
|
-
* coordinates are correct.
|
|
19
|
-
* 2. **Degenerate geometry.** A chart element that exists but encloses no area: no points at all, or
|
|
20
|
-
* every point identical. Distinguishes "the chart is missing" (already catchable — the element is
|
|
21
|
-
* absent) from "the chart is present and empty", which looks healthy to a presence check and to a
|
|
22
|
-
* screenshot diff against a baseline that was also empty.
|
|
23
|
-
*
|
|
24
|
-
* Deliberately NOT attempted: comparing plotted geometry against the source data. That needs the
|
|
25
|
-
* chart's scale functions inverted per library, is a large surface to get wrong, and `chartInstance`
|
|
26
|
-
* below obsoletes it for the libraries people actually use — Chart.js and ECharts both hang their
|
|
27
|
-
* instance off the canvas element, so their data can be read directly rather than reverse-engineered
|
|
28
|
-
* from pixels.
|
|
29
|
-
*/
|
|
30
|
-
/** Elements whose geometry encodes data. `line`/`circle`/`rect` are excluded: they are used for axes,
|
|
31
|
-
* gridlines and backgrounds, where a zero-extent element is normal rather than a fault. */
|
|
32
1
|
const GEOMETRY_TAGS = ['path', 'polyline', 'polygon'];
|
|
33
|
-
/** Attributes carrying that geometry, per tag. */
|
|
34
2
|
const GEOMETRY_ATTRS = ['d', 'points'];
|
|
35
3
|
const SAMPLE_MAX = 120;
|
|
36
|
-
/**
|
|
37
|
-
* Every number embedded in a geometry attribute. Path data interleaves single-letter drawing
|
|
38
|
-
* commands with coordinate pairs, so this extracts numeric-looking tokens rather than parsing the
|
|
39
|
-
* path grammar — the question here is only "is any coordinate non-finite", which does not need a
|
|
40
|
-
* full parser.
|
|
41
|
-
*/
|
|
42
4
|
export function geometryNumbers(value) {
|
|
43
5
|
const tokens = value.match(/-?\d*\.?\d+(?:e[-+]?\d+)?|NaN|Infinity|-Infinity/gi) ?? [];
|
|
44
6
|
return tokens.map((t) => Number(t));
|
|
45
7
|
}
|
|
46
|
-
/** True when the attribute contains a coordinate the browser cannot render. */
|
|
47
8
|
export function hasNonFiniteCoordinate(value) {
|
|
48
|
-
// Checked textually first: `NaN` and `Infinity` survive into the attribute as literal words, and a
|
|
49
|
-
// string scan catches them without depending on how the number extractor tokenises.
|
|
50
9
|
if (/NaN|Infinity/i.test(value))
|
|
51
10
|
return true;
|
|
52
11
|
return geometryNumbers(value).some((n) => !Number.isFinite(n));
|
|
53
12
|
}
|
|
54
|
-
/** True when the geometry encloses no area — no coordinates, or every coordinate pair identical. */
|
|
55
13
|
export function isDegenerate(value) {
|
|
56
14
|
const nums = geometryNumbers(value).filter((n) => Number.isFinite(n));
|
|
57
15
|
if (0 === nums.length)
|
|
58
16
|
return true;
|
|
59
|
-
// A polyline of one repeated point draws nothing. Compare pairs, not raw numbers, so a legitimately
|
|
60
|
-
// flat line (varying x, constant y) is NOT flagged — that is a real chart of constant data.
|
|
61
17
|
const xs = nums.filter((_, i) => 0 === i % 2);
|
|
62
18
|
const ys = nums.filter((_, i) => 1 === i % 2);
|
|
63
19
|
const allSame = (arr) => arr.every((v) => v === arr[0]);
|
|
64
20
|
return xs.length > 1 && allSame(xs) && allSame(ys);
|
|
65
21
|
}
|
|
66
|
-
/**
|
|
67
|
-
* Inspect a subtree for chart geometry faults. Pure over the element — no events, no globals — so it
|
|
68
|
-
* can back a tool call, an assertion, or a snapshot annotation without any of them owning it.
|
|
69
|
-
*/
|
|
70
22
|
export function inspectChart(root) {
|
|
71
23
|
const findings = [];
|
|
72
24
|
let examined = 0;
|
|
@@ -93,18 +45,6 @@ export function inspectChart(root) {
|
|
|
93
45
|
const canvas = root.querySelectorAll('canvas').length > 0;
|
|
94
46
|
return { examined, findings, canvas };
|
|
95
47
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Read the data behind a CANVAS chart.
|
|
98
|
-
*
|
|
99
|
-
* Canvas is the one place where "instrument from inside the app" is not merely cheaper than pixels but
|
|
100
|
-
* is the only option at all: the chart is a bitmap, so the DOM shows an empty `<canvas>` and a
|
|
101
|
-
* screenshot shows colours no assertion can interpret. Both major canvas libraries keep their live
|
|
102
|
-
* instance reachable from the element — Chart.js via its registry, ECharts via a per-DOM lookup — so
|
|
103
|
-
* the series data is READABLE rather than reverse-engineerable.
|
|
104
|
-
*
|
|
105
|
-
* Takes the globals as arguments rather than reaching for `window`, so this stays pure and testable
|
|
106
|
-
* and the SDK never depends on either library being present.
|
|
107
|
-
*/
|
|
108
48
|
export function canvasChartData(canvas, globals) {
|
|
109
49
|
const chartjs = globals.Chart?.getChart?.(canvas);
|
|
110
50
|
if (chartjs !== undefined && chartjs !== null) {
|
|
@@ -1,35 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Desktop screenshots, asked of the runtime that owns the window.
|
|
3
|
-
*
|
|
4
|
-
* A browser tab is captured through CDP. A desktop webview has no CDP endpoint, so the pixels have
|
|
5
|
-
* to come from the shell — and the shell is the only party that can produce them CORRECTLY.
|
|
6
|
-
*
|
|
7
|
-
* Capturing a screen *region* instead was tried and rejected: it photographs the glass, so an app
|
|
8
|
-
* window sitting behind the editor yields a picture of the editor, saved as a visual baseline that a
|
|
9
|
-
* later diff would trust. A screenshot tool that can silently return another window's pixels is
|
|
10
|
-
* worse than one that returns nothing — it manufactures exactly the false green Reticle exists to
|
|
11
|
-
* eliminate. Electron's `webContents.capturePage()` reads the window's own backing store instead:
|
|
12
|
-
* correct while occluded, correct while backgrounded, and needing no screen-recording permission.
|
|
13
|
-
*
|
|
14
|
-
* Both desktop runtimes can do this, each through its own shell API — `webContents.capturePage()` on
|
|
15
|
-
* Electron, and on Tauri the platform's own webview snapshot (`WKWebView.takeSnapshot`, WebView2
|
|
16
|
-
* `CapturePreview`, or WebKitGTK's snapshot). None of them reads the screen, so all stay correct with
|
|
17
|
-
* the window occluded, hidden, or running headless with nothing on screen at all.
|
|
18
|
-
*
|
|
19
|
-
* The app opts in with one line in its main process (`@reticlehq/electron/main`) or one Rust command
|
|
20
|
-
* (`reticle-tauri`). Absent that, this returns `{ ok: false }` and the tool reports no-provider
|
|
21
|
-
* rather than guessing.
|
|
22
|
-
*/
|
|
23
1
|
interface CaptureResult {
|
|
24
2
|
ok: boolean;
|
|
25
|
-
/**
|
|
26
|
-
* Filesystem path of the captured PNG, written by the desktop shell.
|
|
27
|
-
*
|
|
28
|
-
* A path, not the image bytes: the SDK's transport sanitizer caps every string at 64KB, so a
|
|
29
|
-
* base64 PNG came back SILENTLY TRUNCATED and was saved as a "successful" screenshot that no
|
|
30
|
-
* decoder could read. The daemon and the app always share a machine here (the bridge is loopback),
|
|
31
|
-
* so handing over a path keeps the image off the event wire entirely.
|
|
32
|
-
*/
|
|
33
3
|
path?: string;
|
|
34
4
|
reason?: string;
|
|
35
5
|
}
|
|
@@ -1,59 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Desktop screenshots, asked of the runtime that owns the window.
|
|
3
|
-
*
|
|
4
|
-
* A browser tab is captured through CDP. A desktop webview has no CDP endpoint, so the pixels have
|
|
5
|
-
* to come from the shell — and the shell is the only party that can produce them CORRECTLY.
|
|
6
|
-
*
|
|
7
|
-
* Capturing a screen *region* instead was tried and rejected: it photographs the glass, so an app
|
|
8
|
-
* window sitting behind the editor yields a picture of the editor, saved as a visual baseline that a
|
|
9
|
-
* later diff would trust. A screenshot tool that can silently return another window's pixels is
|
|
10
|
-
* worse than one that returns nothing — it manufactures exactly the false green Reticle exists to
|
|
11
|
-
* eliminate. Electron's `webContents.capturePage()` reads the window's own backing store instead:
|
|
12
|
-
* correct while occluded, correct while backgrounded, and needing no screen-recording permission.
|
|
13
|
-
*
|
|
14
|
-
* Both desktop runtimes can do this, each through its own shell API — `webContents.capturePage()` on
|
|
15
|
-
* Electron, and on Tauri the platform's own webview snapshot (`WKWebView.takeSnapshot`, WebView2
|
|
16
|
-
* `CapturePreview`, or WebKitGTK's snapshot). None of them reads the screen, so all stay correct with
|
|
17
|
-
* the window occluded, hidden, or running headless with nothing on screen at all.
|
|
18
|
-
*
|
|
19
|
-
* The app opts in with one line in its main process (`@reticlehq/electron/main`) or one Rust command
|
|
20
|
-
* (`reticle-tauri`). Absent that, this returns `{ ok: false }` and the tool reports no-provider
|
|
21
|
-
* rather than guessing.
|
|
22
|
-
*/
|
|
23
1
|
import { RETICLE_IPC_GLOBAL, RETICLE_TAURI_CAPTURE_COMMAND, VisualReason } from '@reticlehq/core';
|
|
24
|
-
import { nativeFrame } from '../timers/native-timers.js';
|
|
2
|
+
import { nativeFrame } from '../timers/native/native-timers.js';
|
|
25
3
|
import { RETICLE_OVERLAY } from './dom-ignore.js';
|
|
26
|
-
/**
|
|
27
|
-
* What a shell returns when it can photograph the viewport but not the whole scroll height.
|
|
28
|
-
*
|
|
29
|
-
* Only WebKitGTK can render a full document offscreen; `capturePage()` and `takeSnapshot` both give
|
|
30
|
-
* what is composited. Downgrading silently would be the worst option available: the caller asked for
|
|
31
|
-
* the content below the fold, would get an image without it, and would bank it as a baseline that a
|
|
32
|
-
* later diff reports as matching. So the shells say so, and the tool reports the reason.
|
|
33
|
-
*/
|
|
34
4
|
const FULL_PAGE_UNSUPPORTED_MARKER = VisualReason.FULL_PAGE_UNSUPPORTED;
|
|
35
|
-
/**
|
|
36
|
-
* Reasons a shell can name, in the order they are matched.
|
|
37
|
-
*
|
|
38
|
-
* The shell throws one of these rather than answering a bare null, because "nothing came back" was
|
|
39
|
-
* covering three different problems at once: a window with no composited frame yet, a webContents
|
|
40
|
-
* that had gone away, and any error at all. They need different responses from the reader, and the
|
|
41
|
-
* tool reported them identically.
|
|
42
|
-
*/
|
|
43
5
|
const RECOGNISED_MARKERS = [
|
|
44
6
|
FULL_PAGE_UNSUPPORTED_MARKER,
|
|
45
7
|
VisualReason.NOT_COMPOSITED,
|
|
46
8
|
];
|
|
47
9
|
const TAURI_INTERNALS_GLOBAL = '__TAURI_INTERNALS__';
|
|
48
|
-
/**
|
|
49
|
-
* Tauri's capture path, used when no preload-installed channel exists.
|
|
50
|
-
*
|
|
51
|
-
* Electron can install `RETICLE_IPC_GLOBAL` from its preload, which runs before app code. Tauri has
|
|
52
|
-
* no preload stage at all, so requiring the same global would mean every Tauri app hand-writing a
|
|
53
|
-
* frontend shim — a setup step that silently yields "no screenshots" when forgotten. Invoking the
|
|
54
|
-
* command directly removes the JavaScript side entirely: the app registers one Rust command and the
|
|
55
|
-
* SDK finds it. Absent that command the invoke rejects, and this reports no-provider like any other.
|
|
56
|
-
*/
|
|
57
10
|
function tauriCapture() {
|
|
58
11
|
const internals = window[TAURI_INTERNALS_GLOBAL];
|
|
59
12
|
if (typeof internals?.invoke !== 'function')
|
|
@@ -64,31 +17,9 @@ function tauriCapture() {
|
|
|
64
17
|
return 'string' === typeof path ? path : null;
|
|
65
18
|
};
|
|
66
19
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Take Reticle's own UI out of the shot, then put it back.
|
|
69
|
-
*
|
|
70
|
-
* The shell photographs the composited window, so anything Reticle mounted is IN the picture — and
|
|
71
|
-
* the presenter panel is the worst possible thing to bank into a visual baseline, because it renders
|
|
72
|
-
* live session state: an event tally, a log tail, a run badge. A baseline captured with it is a
|
|
73
|
-
* baseline of Reticle's own UI as much as the app's, and the next `reticle_visual_diff` reports a
|
|
74
|
-
* change that belongs entirely to the observer. That is measuring the instrument.
|
|
75
|
-
*
|
|
76
|
-
* Two frames, not one: the first lets the style change apply, the second lets the compositor present
|
|
77
|
-
* it — capture before that and the shell photographs the pre-hide window anyway. Both are BOUNDED
|
|
78
|
-
* frames, which is load-bearing rather than defensive: a headless Tauri window is hidden, and a
|
|
79
|
-
* hidden webview never fires `requestAnimationFrame` at all, so a raw double-rAF here deadlocked
|
|
80
|
-
* every capture until the command timed out at 8s. Restoration runs in a `finally`, so a capture
|
|
81
|
-
* that throws still leaves the panel on screen for the human watching.
|
|
82
|
-
*/
|
|
83
20
|
async function withReticleUiHidden(capture) {
|
|
84
|
-
// The SAME selector the snapshot layer already uses to keep Reticle's own DOM out of what the
|
|
85
|
-
// agent reads — the presenter panel, the cursor, the glow, and the annotator's "flag a bug"
|
|
86
|
-
// button. One definition: a root that leaked into snapshots once would otherwise leak into
|
|
87
|
-
// baselines the second time someone added one.
|
|
88
21
|
const roots = [...document.querySelectorAll(RETICLE_OVERLAY)];
|
|
89
22
|
const previous = roots.map((el) => el.style.visibility);
|
|
90
|
-
// `visibility`, not `display`: it keeps the element's box, so hiding cannot reflow the app
|
|
91
|
-
// underneath and change the very layout the screenshot is meant to record.
|
|
92
23
|
for (const el of roots)
|
|
93
24
|
el.style.visibility = 'hidden';
|
|
94
25
|
try {
|
|
@@ -118,9 +49,6 @@ export async function captureDesktopWindow(fullPage = false) {
|
|
|
118
49
|
}
|
|
119
50
|
catch (error) {
|
|
120
51
|
const reason = error instanceof Error ? error.message : String(error);
|
|
121
|
-
// Recognised markers are reported as the reason ITSELF, so the tool can branch on a closed
|
|
122
|
-
// vocabulary rather than on prose. Everything else keeps its own message: dressing an unknown
|
|
123
|
-
// failure up as a known one would send the reader to the wrong place entirely.
|
|
124
52
|
for (const marker of RECOGNISED_MARKERS) {
|
|
125
53
|
if (reason.includes(marker))
|
|
126
54
|
return { ok: false, reason: marker };
|
package/dist/dom/dom-ignore.d.ts
CHANGED
|
@@ -1,37 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Selectors for Reticle's own presenter overlay (cursor, HUD, glow) + the annotator's
|
|
3
|
-
* UI (`data-reticle-mark`) - never observed/snapshotted. The annotator mounts by DEFAULT with the
|
|
4
|
-
* presenter, so omitting its selector here leaked annotation chrome into every snapshot.
|
|
5
|
-
*/
|
|
6
1
|
export declare const RETICLE_OVERLAY: string;
|
|
7
|
-
/**
|
|
8
|
-
* Make Reticle's own presenter visible to snapshots and queries. Contributors only.
|
|
9
|
-
*
|
|
10
|
-
* Deliberately a function rather than a field on the ignore set, so the one place that decides
|
|
11
|
-
* "is this ours" also decides "may it be seen", and no caller can half-apply it.
|
|
12
|
-
*/
|
|
13
2
|
export declare function setPresenterVisible(visible: boolean): void;
|
|
14
|
-
/** Whether the hatch is open — reported in the app's capabilities so a verdict is never mistaken. */
|
|
15
3
|
export declare function isPresenterVisible(): boolean;
|
|
16
|
-
/** Let the host app add selectors to exclude from snapshots (e.g. its own dev widgets). */
|
|
17
4
|
export declare function setIgnoreSelectors(selectors: string[]): void;
|
|
18
|
-
/** True if the element is part of Reticle's own presenter overlay. */
|
|
19
5
|
export declare function isReticleOverlay(el: Element): boolean;
|
|
20
|
-
/**
|
|
21
|
-
* True iff the element is part of Reticle's OWN UI - the presenter overlay, the HUD, the synthetic
|
|
22
|
-
* cursor, the glow, or the annotator's marks - or lives inside one of them.
|
|
23
|
-
*
|
|
24
|
-
* The rule used to be "any ancestor carries a data-reticle* attribute", which is wrong twice over.
|
|
25
|
-
* `data-reticle-mark-active` sits on <html> while annotate mode is live, so the whole document
|
|
26
|
-
* answered yes; and `data-reticle-source` is stamped by the Vite/Babel plugins on every element the
|
|
27
|
-
* APP renders, so in an instrumented app - which is the only kind there is - most of the page
|
|
28
|
-
* answered yes too. Those attributes describe page content; they do not make it ours.
|
|
29
|
-
*
|
|
30
|
-
* Two things read this and both failed silently. `pageElementAt` (annotator) skipped every stamped
|
|
31
|
-
* element and anchored the note to the outermost unstamped ancestor, i.e. the app shell instead of
|
|
32
|
-
* the control under the cursor. `occlusion.ts` reads a yes as "nothing to report", so occlusion
|
|
33
|
-
* detection - a bug class Reticle advertises catching - came back clean wherever the stamp reached.
|
|
34
|
-
*/
|
|
35
6
|
export declare function isReticleUi(node: Element | null): boolean;
|
|
36
|
-
/** True if the element should be excluded from snapshots/queries (Reticle overlay or dev overlay). */
|
|
37
7
|
export declare function isIgnored(el: Element): boolean;
|
package/dist/dom/dom-ignore.js
CHANGED
|
@@ -1,70 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
* Selectors for Reticle's own presenter overlay (cursor, HUD, glow) + the annotator's
|
|
3
|
-
* UI (`data-reticle-mark`) - never observed/snapshotted. The annotator mounts by DEFAULT with the
|
|
4
|
-
* presenter, so omitting its selector here leaked annotation chrome into every snapshot.
|
|
5
|
-
*/
|
|
6
|
-
export const RETICLE_OVERLAY = '[data-reticle-overlay],[data-reticle-cursor],[data-reticle-hud],[data-reticle-glow],[data-reticle-mark],[data-reticle-blocker]';
|
|
7
|
-
/** Known third-party dev overlays to keep out of snapshots (Agentation, Next dev UI). */
|
|
1
|
+
export const RETICLE_OVERLAY = '[data-reticle-overlay],[data-reticle-cursor],[data-reticle-hud],[data-reticle-glow],[data-reticle-mark],[data-reticle-blocker],[data-reticle-tour]';
|
|
8
2
|
const DEV_OVERLAYS = '[data-agentation],#__next-build-watcher,nextjs-portal,[data-nextjs-dialog],[data-nextjs-toast]';
|
|
9
3
|
let extraIgnore = '';
|
|
10
|
-
/**
|
|
11
|
-
* Whether Reticle's OWN UI is visible to snapshots and queries.
|
|
12
|
-
*
|
|
13
|
-
* Off, always, unless an app opts in — and the opt-in exists for exactly one situation: the app
|
|
14
|
-
* under test IS Reticle. A HUD change is otherwise the only kind of change Reticle cannot be used to
|
|
15
|
-
* check, because the panel that renders it is invisible to every tool that could look at it.
|
|
16
|
-
*
|
|
17
|
-
* It stays off by default because the reason for hiding it is sound: an agent that can drive
|
|
18
|
-
* Reticle's own interface can fabricate its own impact report, and a snapshot full of Reticle chrome
|
|
19
|
-
* is noise in every other app on earth. This is a hatch for contributors, not a setting.
|
|
20
|
-
*
|
|
21
|
-
* Third-party dev overlays stay ignored either way — they are somebody else's furniture and were
|
|
22
|
-
* never the thing being verified.
|
|
23
|
-
*/
|
|
24
4
|
let presenterVisible = false;
|
|
25
|
-
/**
|
|
26
|
-
* Make Reticle's own presenter visible to snapshots and queries. Contributors only.
|
|
27
|
-
*
|
|
28
|
-
* Deliberately a function rather than a field on the ignore set, so the one place that decides
|
|
29
|
-
* "is this ours" also decides "may it be seen", and no caller can half-apply it.
|
|
30
|
-
*/
|
|
31
5
|
export function setPresenterVisible(visible) {
|
|
32
6
|
presenterVisible = visible;
|
|
33
7
|
}
|
|
34
|
-
/** Whether the hatch is open — reported in the app's capabilities so a verdict is never mistaken. */
|
|
35
8
|
export function isPresenterVisible() {
|
|
36
9
|
return presenterVisible;
|
|
37
10
|
}
|
|
38
|
-
/** Let the host app add selectors to exclude from snapshots (e.g. its own dev widgets). */
|
|
39
11
|
export function setIgnoreSelectors(selectors) {
|
|
40
12
|
extraIgnore = selectors.join(',');
|
|
41
13
|
}
|
|
42
|
-
/** True if the element is part of Reticle's own presenter overlay. */
|
|
43
14
|
export function isReticleOverlay(el) {
|
|
44
15
|
return el.closest(RETICLE_OVERLAY) !== null;
|
|
45
16
|
}
|
|
46
|
-
/**
|
|
47
|
-
* True iff the element is part of Reticle's OWN UI - the presenter overlay, the HUD, the synthetic
|
|
48
|
-
* cursor, the glow, or the annotator's marks - or lives inside one of them.
|
|
49
|
-
*
|
|
50
|
-
* The rule used to be "any ancestor carries a data-reticle* attribute", which is wrong twice over.
|
|
51
|
-
* `data-reticle-mark-active` sits on <html> while annotate mode is live, so the whole document
|
|
52
|
-
* answered yes; and `data-reticle-source` is stamped by the Vite/Babel plugins on every element the
|
|
53
|
-
* APP renders, so in an instrumented app - which is the only kind there is - most of the page
|
|
54
|
-
* answered yes too. Those attributes describe page content; they do not make it ours.
|
|
55
|
-
*
|
|
56
|
-
* Two things read this and both failed silently. `pageElementAt` (annotator) skipped every stamped
|
|
57
|
-
* element and anchored the note to the outermost unstamped ancestor, i.e. the app shell instead of
|
|
58
|
-
* the control under the cursor. `occlusion.ts` reads a yes as "nothing to report", so occlusion
|
|
59
|
-
* detection - a bug class Reticle advertises catching - came back clean wherever the stamp reached.
|
|
60
|
-
*/
|
|
61
17
|
export function isReticleUi(node) {
|
|
62
18
|
return node !== null && node.closest(RETICLE_OVERLAY) !== null;
|
|
63
19
|
}
|
|
64
|
-
/** True if the element should be excluded from snapshots/queries (Reticle overlay or dev overlay). */
|
|
65
20
|
export function isIgnored(el) {
|
|
66
|
-
// With the hatch open, Reticle's own overlay drops out of the ignore set — but the third-party
|
|
67
|
-
// dev overlays do not. They are somebody else's furniture and were never the thing being verified.
|
|
68
21
|
const ours = presenterVisible ? '' : RETICLE_OVERLAY;
|
|
69
22
|
const sel = [ours, DEV_OVERLAYS, extraIgnore].filter((part) => part.length > 0).join(',');
|
|
70
23
|
return el.closest(sel) !== null;
|
package/dist/dom/occlusion.d.ts
CHANGED
|
@@ -1,8 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Center-point hit-test, shared by click-geometry (actions) and occlusion detection (commands) so the
|
|
3
|
-
* two can't drift. Returns the top NON-Reticle element covering `el`'s center, or null when nothing
|
|
4
|
-
* foreign is on top — i.e. `el` itself, an ancestor wrapping it, or a descendant is on top (all "not
|
|
5
|
-
* occluded"). A zero-area or unlayouted box (jsdom / detached node) returns null: we cannot tell, so
|
|
6
|
-
* we never false-positive an occlusion. Reticle's own HUD is never treated as an occluder.
|
|
7
|
-
*/
|
|
8
1
|
export declare function hitTestOccluder(el: Element, rect: DOMRect): Element | null;
|
package/dist/dom/occlusion.js
CHANGED
|
@@ -1,20 +1,39 @@
|
|
|
1
1
|
import { isReticleUi } from './dom-ignore.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
const SAMPLE_POINTS = [
|
|
3
|
+
[0.5, 0.5],
|
|
4
|
+
[0.25, 0.25],
|
|
5
|
+
[0.75, 0.25],
|
|
6
|
+
[0.25, 0.75],
|
|
7
|
+
[0.75, 0.75],
|
|
8
|
+
];
|
|
9
|
+
const BLOCKED_MAJORITY = 3;
|
|
9
10
|
export function hitTestOccluder(el, rect) {
|
|
10
11
|
if (0 === rect.width || 0 === rect.height)
|
|
11
12
|
return null;
|
|
12
13
|
const doc = el.ownerDocument;
|
|
13
14
|
if (typeof doc.elementFromPoint !== 'function')
|
|
14
15
|
return null;
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const withStack = doc;
|
|
17
|
+
const topAt = (x, y) => {
|
|
18
|
+
const found = withStack.elementsFromPoint?.(x, y) ?? [doc.elementFromPoint(x, y)];
|
|
19
|
+
return found.find((c) => null !== c && !isReticleUi(c)) ?? null;
|
|
20
|
+
};
|
|
21
|
+
const blockers = new Map();
|
|
22
|
+
for (const [fx, fy] of SAMPLE_POINTS) {
|
|
23
|
+
const top = topAt(rect.left + rect.width * fx, rect.top + rect.height * fy);
|
|
24
|
+
if (null === top)
|
|
25
|
+
continue;
|
|
26
|
+
if (top === el || el.contains(top) || top.contains(el))
|
|
27
|
+
continue;
|
|
28
|
+
blockers.set(top, (blockers.get(top) ?? 0) + 1);
|
|
29
|
+
}
|
|
30
|
+
let worst = null;
|
|
31
|
+
let worstCount = 0;
|
|
32
|
+
for (const [element, count] of blockers) {
|
|
33
|
+
if (count > worstCount) {
|
|
34
|
+
worst = element;
|
|
35
|
+
worstCount = count;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return worstCount >= BLOCKED_MAJORITY ? worst : null;
|
|
20
39
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const PAINT_PROPERTIES = [
|
|
2
|
+
'filter',
|
|
3
|
+
'opacity',
|
|
4
|
+
'transform',
|
|
5
|
+
'mix-blend-mode',
|
|
6
|
+
'backdrop-filter',
|
|
7
|
+
];
|
|
8
|
+
const INERT = {
|
|
9
|
+
filter: 'none',
|
|
10
|
+
opacity: '1',
|
|
11
|
+
transform: 'none',
|
|
12
|
+
'mix-blend-mode': 'normal',
|
|
13
|
+
'backdrop-filter': 'none',
|
|
14
|
+
};
|
|
15
|
+
function describeElement(el) {
|
|
16
|
+
if (el.id.length > 0)
|
|
17
|
+
return `#${el.id}`;
|
|
18
|
+
const cls = el.getAttribute('class');
|
|
19
|
+
if (cls !== null && cls.trim().length > 0)
|
|
20
|
+
return `.${cls.trim().split(/\s+/).join('.')}`;
|
|
21
|
+
return el.tagName.toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
export function paintContextOf(el) {
|
|
24
|
+
const view = el.ownerDocument.defaultView;
|
|
25
|
+
if (null === view)
|
|
26
|
+
return [];
|
|
27
|
+
const found = [];
|
|
28
|
+
for (let node = el; node !== null && node !== el.ownerDocument.documentElement; node = node.parentElement) {
|
|
29
|
+
const style = view.getComputedStyle(node);
|
|
30
|
+
for (const property of PAINT_PROPERTIES) {
|
|
31
|
+
const value = style.getPropertyValue(property);
|
|
32
|
+
if (value.length > 0 && value !== INERT[property]) {
|
|
33
|
+
found.push({ on: describeElement(node), property, value });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return found;
|
|
38
|
+
}
|
package/dist/dom/query.d.ts
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
1
|
import { ElementState, type ElementQuery, type MatchResult, type QueryResult } from '@reticlehq/core';
|
|
2
|
-
/** Longest attribute value returned; one enormous href must not blow the response budget. */
|
|
3
2
|
export declare const ATTR_VALUE_MAX = 512;
|
|
4
|
-
/**
|
|
5
|
-
* Match an element predicate against the live DOM.
|
|
6
|
-
*
|
|
7
|
-
* `count` is every match; `elements` is the described prefix. Keeping those separate is what lets
|
|
8
|
-
* "how many?" stay exact while the answer stays affordable.
|
|
9
|
-
*/
|
|
10
3
|
export declare function matchQuery(query: ElementQuery, state?: ElementState, limit?: number): MatchResult;
|
|
11
|
-
/**
|
|
12
|
-
* Resolve a query to descriptors for the `query` MCP tool.
|
|
13
|
-
*
|
|
14
|
-
* `count` is carried through deliberately: the server reports match totals from it rather than from
|
|
15
|
-
* the array, because the array is capped in transit. Dropping it here - which this function used to
|
|
16
|
-
* do - silently put the server back to counting survivors and calling that the answer.
|
|
17
|
-
*/
|
|
18
4
|
export declare function runQuery(query: ElementQuery, limit?: number): QueryResult;
|