framewatch-mcp-server 0.1.0 → 0.2.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.
Files changed (133) hide show
  1. package/README.md +895 -15
  2. package/dist/constants.d.ts +274 -0
  3. package/dist/constants.js +279 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/engine/browser.d.ts +20 -4
  6. package/dist/engine/browser.js +26 -9
  7. package/dist/engine/browser.js.map +1 -1
  8. package/dist/engine/clicks.d.ts +221 -0
  9. package/dist/engine/clicks.js +801 -0
  10. package/dist/engine/clicks.js.map +1 -0
  11. package/dist/engine/forms.d.ts +137 -0
  12. package/dist/engine/forms.js +474 -0
  13. package/dist/engine/forms.js.map +1 -0
  14. package/dist/engine/hmr.d.ts +41 -0
  15. package/dist/engine/hmr.js +91 -0
  16. package/dist/engine/hmr.js.map +1 -0
  17. package/dist/engine/inspect.d.ts +31 -0
  18. package/dist/engine/inspect.js +383 -0
  19. package/dist/engine/inspect.js.map +1 -0
  20. package/dist/engine/interaction.d.ts +12 -7
  21. package/dist/engine/interaction.js +110 -18
  22. package/dist/engine/interaction.js.map +1 -1
  23. package/dist/engine/links.d.ts +134 -0
  24. package/dist/engine/links.js +384 -0
  25. package/dist/engine/links.js.map +1 -0
  26. package/dist/engine/mocks.d.ts +53 -0
  27. package/dist/engine/mocks.js +148 -0
  28. package/dist/engine/mocks.js.map +1 -0
  29. package/dist/engine/rtl.d.ts +129 -0
  30. package/dist/engine/rtl.js +540 -0
  31. package/dist/engine/rtl.js.map +1 -0
  32. package/dist/engine/seo.d.ts +189 -0
  33. package/dist/engine/seo.js +398 -0
  34. package/dist/engine/seo.js.map +1 -0
  35. package/dist/engine/snapshot.d.ts +29 -0
  36. package/dist/engine/snapshot.js +10 -0
  37. package/dist/engine/snapshot.js.map +1 -0
  38. package/dist/engine/vue.d.ts +54 -0
  39. package/dist/engine/vue.js +419 -0
  40. package/dist/engine/vue.js.map +1 -0
  41. package/dist/index.js +45 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/tools/accessibility.d.ts +4 -0
  44. package/dist/tools/accessibility.js +9 -2
  45. package/dist/tools/accessibility.js.map +1 -1
  46. package/dist/tools/api-mock.d.ts +405 -0
  47. package/dist/tools/api-mock.js +186 -0
  48. package/dist/tools/api-mock.js.map +1 -0
  49. package/dist/tools/capture.d.ts +90 -26
  50. package/dist/tools/capture.js +109 -58
  51. package/dist/tools/capture.js.map +1 -1
  52. package/dist/tools/compare.d.ts +4 -0
  53. package/dist/tools/compare.js +16 -5
  54. package/dist/tools/compare.js.map +1 -1
  55. package/dist/tools/dead-clicks.d.ts +128 -0
  56. package/dist/tools/dead-clicks.js +570 -0
  57. package/dist/tools/dead-clicks.js.map +1 -0
  58. package/dist/tools/form-test.d.ts +112 -0
  59. package/dist/tools/form-test.js +477 -0
  60. package/dist/tools/form-test.js.map +1 -0
  61. package/dist/tools/index.d.ts +17 -1
  62. package/dist/tools/index.js +45 -1
  63. package/dist/tools/index.js.map +1 -1
  64. package/dist/tools/inspect.d.ts +78 -0
  65. package/dist/tools/inspect.js +136 -0
  66. package/dist/tools/inspect.js.map +1 -0
  67. package/dist/tools/interact.d.ts +37 -18
  68. package/dist/tools/interact.js +113 -13
  69. package/dist/tools/interact.js.map +1 -1
  70. package/dist/tools/links.d.ts +129 -0
  71. package/dist/tools/links.js +640 -0
  72. package/dist/tools/links.js.map +1 -0
  73. package/dist/tools/responsive.d.ts +10 -6
  74. package/dist/tools/responsive.js +21 -4
  75. package/dist/tools/responsive.js.map +1 -1
  76. package/dist/tools/rtl.d.ts +241 -0
  77. package/dist/tools/rtl.js +410 -0
  78. package/dist/tools/rtl.js.map +1 -0
  79. package/dist/tools/save-auth.d.ts +263 -0
  80. package/dist/tools/save-auth.js +253 -0
  81. package/dist/tools/save-auth.js.map +1 -0
  82. package/dist/tools/screenshot.d.ts +4 -0
  83. package/dist/tools/screenshot.js +15 -4
  84. package/dist/tools/screenshot.js.map +1 -1
  85. package/dist/tools/seo.d.ts +113 -0
  86. package/dist/tools/seo.js +281 -0
  87. package/dist/tools/seo.js.map +1 -0
  88. package/dist/tools/snapshot.d.ts +122 -0
  89. package/dist/tools/snapshot.js +183 -0
  90. package/dist/tools/snapshot.js.map +1 -0
  91. package/dist/tools/wait-for.d.ts +107 -0
  92. package/dist/tools/wait-for.js +167 -0
  93. package/dist/tools/wait-for.js.map +1 -0
  94. package/dist/utils/arabic-text.d.ts +14 -0
  95. package/dist/utils/arabic-text.js +193 -0
  96. package/dist/utils/arabic-text.js.map +1 -0
  97. package/dist/utils/budget.d.ts +41 -0
  98. package/dist/utils/budget.js +182 -0
  99. package/dist/utils/budget.js.map +1 -0
  100. package/dist/utils/format.d.ts +11 -1
  101. package/dist/utils/format.js +27 -4
  102. package/dist/utils/format.js.map +1 -1
  103. package/dist/utils/highlight.d.ts +69 -0
  104. package/dist/utils/highlight.js +181 -0
  105. package/dist/utils/highlight.js.map +1 -0
  106. package/dist/utils/link-rules.d.ts +100 -0
  107. package/dist/utils/link-rules.js +284 -0
  108. package/dist/utils/link-rules.js.map +1 -0
  109. package/dist/utils/mock-rules.d.ts +144 -0
  110. package/dist/utils/mock-rules.js +224 -0
  111. package/dist/utils/mock-rules.js.map +1 -0
  112. package/dist/utils/rtl-rules.d.ts +142 -0
  113. package/dist/utils/rtl-rules.js +296 -0
  114. package/dist/utils/rtl-rules.js.map +1 -0
  115. package/dist/utils/seo-rules.d.ts +129 -0
  116. package/dist/utils/seo-rules.js +726 -0
  117. package/dist/utils/seo-rules.js.map +1 -0
  118. package/dist/utils/snapshot-rules.d.ts +33 -0
  119. package/dist/utils/snapshot-rules.js +111 -0
  120. package/dist/utils/snapshot-rules.js.map +1 -0
  121. package/dist/utils/storage-state.d.ts +76 -0
  122. package/dist/utils/storage-state.js +195 -0
  123. package/dist/utils/storage-state.js.map +1 -0
  124. package/dist/utils/style-rules.d.ts +107 -0
  125. package/dist/utils/style-rules.js +223 -0
  126. package/dist/utils/style-rules.js.map +1 -0
  127. package/dist/utils/test-data.d.ts +75 -0
  128. package/dist/utils/test-data.js +294 -0
  129. package/dist/utils/test-data.js.map +1 -0
  130. package/dist/utils/vue-rules.d.ts +72 -0
  131. package/dist/utils/vue-rules.js +108 -0
  132. package/dist/utils/vue-rules.js.map +1 -0
  133. package/package.json +6 -4
@@ -0,0 +1,91 @@
1
+ import { MAX_HMR_EVENTS } from "../constants.js";
2
+ import { parseViteLine } from "../utils/vue-rules.js";
3
+ export class HmrWatcher {
4
+ #page;
5
+ #events = [];
6
+ #waiters = new Set();
7
+ #connected = false;
8
+ #lastSeen = Date.now();
9
+ constructor(page) {
10
+ this.#page = page;
11
+ page.on("console", this.#onConsole);
12
+ }
13
+ /** Vite's client said hello, or has sent an update: this is a dev-server page. */
14
+ get connected() {
15
+ return this.#connected || this.#events.length > 0;
16
+ }
17
+ /** When a tool last looked at this page. */
18
+ get lastSeen() {
19
+ return this.#lastSeen;
20
+ }
21
+ markSeen() {
22
+ this.#lastSeen = Date.now();
23
+ }
24
+ /** The most recent event, whenever it happened. */
25
+ get latest() {
26
+ return this.#events[this.#events.length - 1];
27
+ }
28
+ /**
29
+ * The first event newer than `since`, waiting up to `timeoutMs` for one to
30
+ * arrive. Resolves immediately when one already has.
31
+ */
32
+ waitForEvent(since, timeoutMs) {
33
+ const already = this.#events.find((event) => event.t > since);
34
+ if (already)
35
+ return Promise.resolve(already);
36
+ return new Promise((resolve) => {
37
+ const done = (event) => {
38
+ clearTimeout(timer);
39
+ this.#waiters.delete(onEvent);
40
+ resolve(event);
41
+ };
42
+ const onEvent = (event) => {
43
+ if (event.t > since)
44
+ done(event);
45
+ };
46
+ const timer = setTimeout(() => done(null), Math.max(0, timeoutMs));
47
+ this.#waiters.add(onEvent);
48
+ });
49
+ }
50
+ detach() {
51
+ this.#page.off("console", this.#onConsole);
52
+ }
53
+ #onConsole = (message) => {
54
+ const event = parseViteLine(message.text());
55
+ if (!event)
56
+ return;
57
+ if (event.kind === "connected") {
58
+ this.#connected = true;
59
+ return;
60
+ }
61
+ const record = { t: Date.now(), kind: event.kind, path: event.path };
62
+ this.#events.push(record);
63
+ if (this.#events.length > MAX_HMR_EVENTS)
64
+ this.#events.shift();
65
+ for (const waiter of [...this.#waiters])
66
+ waiter(record);
67
+ };
68
+ }
69
+ const byPage = new WeakMap();
70
+ /** The watcher for `page`, created (and attached) on first use. */
71
+ export function hmrFor(page) {
72
+ const existing = byPage.get(page);
73
+ if (existing)
74
+ return existing;
75
+ const created = new HmrWatcher(page);
76
+ byPage.set(page, created);
77
+ return created;
78
+ }
79
+ /** Whether the page loaded Vite's client script — a dev-server page even before it logged anything. */
80
+ export async function hasViteClient(page) {
81
+ try {
82
+ return await page.evaluate(() => {
83
+ const doc = globalThis.document;
84
+ return !!(doc && doc.querySelector('script[src*="/@vite/client"]'));
85
+ });
86
+ }
87
+ catch {
88
+ return false;
89
+ }
90
+ }
91
+ //# sourceMappingURL=hmr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hmr.js","sourceRoot":"","sources":["../../src/engine/hmr.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConsoleMessage, Page } from \"playwright\";\nimport { MAX_HMR_EVENTS } from \"../constants.js\";\nimport { parseViteLine } from \"../utils/vue-rules.js\";\n\n/**\n * What Vite did to the session page while nobody was looking.\n *\n * Vite's client announces every hot update in the console, so a watcher on\n * the page can tell the difference between \"the page has not changed since\n * you saved\" and \"it changed 300ms ago\" without reloading anything. The\n * watcher belongs to the session page and lives as long as it does: it is\n * attached when the page is created (see `getSessionPage`), and every tool\n * that touches the page stamps `markSeen` on its way out. \"Since the last\n * look\" is then exactly the window `framewatch_wait_for` cares about — an\n * update that landed between the agent's save and its next call still\n * counts, and one from an earlier edit does not.\n */\n\nexport interface HmrEvent {\n /** Epoch ms. */\n t: number;\n kind: \"update\" | \"css\" | \"reload\";\n path: string;\n}\n\nexport class HmrWatcher {\n readonly #page: Page;\n readonly #events: HmrEvent[] = [];\n readonly #waiters = new Set<(event: HmrEvent) => void>();\n #connected = false;\n #lastSeen: number = Date.now();\n\n constructor(page: Page) {\n this.#page = page;\n page.on(\"console\", this.#onConsole);\n }\n\n /** Vite's client said hello, or has sent an update: this is a dev-server page. */\n get connected(): boolean {\n return this.#connected || this.#events.length > 0;\n }\n\n /** When a tool last looked at this page. */\n get lastSeen(): number {\n return this.#lastSeen;\n }\n\n markSeen(): void {\n this.#lastSeen = Date.now();\n }\n\n /** The most recent event, whenever it happened. */\n get latest(): HmrEvent | undefined {\n return this.#events[this.#events.length - 1];\n }\n\n /**\n * The first event newer than `since`, waiting up to `timeoutMs` for one to\n * arrive. Resolves immediately when one already has.\n */\n waitForEvent(since: number, timeoutMs: number): Promise<HmrEvent | null> {\n const already = this.#events.find((event) => event.t > since);\n if (already) return Promise.resolve(already);\n return new Promise((resolve) => {\n const done = (event: HmrEvent | null): void => {\n clearTimeout(timer);\n this.#waiters.delete(onEvent);\n resolve(event);\n };\n const onEvent = (event: HmrEvent): void => {\n if (event.t > since) done(event);\n };\n const timer = setTimeout(() => done(null), Math.max(0, timeoutMs));\n this.#waiters.add(onEvent);\n });\n }\n\n detach(): void {\n this.#page.off(\"console\", this.#onConsole);\n }\n\n readonly #onConsole = (message: ConsoleMessage): void => {\n const event = parseViteLine(message.text());\n if (!event) return;\n if (event.kind === \"connected\") {\n this.#connected = true;\n return;\n }\n const record: HmrEvent = { t: Date.now(), kind: event.kind, path: event.path };\n this.#events.push(record);\n if (this.#events.length > MAX_HMR_EVENTS) this.#events.shift();\n for (const waiter of [...this.#waiters]) waiter(record);\n };\n}\n\nconst byPage = new WeakMap<Page, HmrWatcher>();\n\n/** The watcher for `page`, created (and attached) on first use. */\nexport function hmrFor(page: Page): HmrWatcher {\n const existing = byPage.get(page);\n if (existing) return existing;\n const created = new HmrWatcher(page);\n byPage.set(page, created);\n return created;\n}\n\n/** Whether the page loaded Vite's client script — a dev-server page even before it logged anything. */\nexport async function hasViteClient(page: Page): Promise<boolean> {\n try {\n return await page.evaluate(() => {\n const doc = (globalThis as any).document;\n return !!(doc && doc.querySelector('script[src*=\"/@vite/client\"]'));\n });\n } catch {\n return false;\n }\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import type { Page } from "playwright";
2
+ import { type ElementMeasurement, type PageInventory } from "../utils/style-rules.js";
3
+ import type { ComponentInfo, ComponentUnavailable } from "../utils/vue-rules.js";
4
+ /**
5
+ * How an element is built, measured inside the page.
6
+ *
7
+ * A screenshot shows what a button looks like; this says what it *is*: the
8
+ * box in viewport pixels, the font, the colours it actually renders in (a
9
+ * transparent background resolved through its ancestors), the spacing, and
10
+ * where it sits against its parent and the sibling before it. That is what an
11
+ * agent needs to check its own work against the design it meant to build.
12
+ *
13
+ * Every number is read in one `evaluate` per element and judged in
14
+ * `style-rules.ts`, which never sees a browser.
15
+ */
16
+ export interface InspectResult {
17
+ target: string;
18
+ measurement?: ElementMeasurement;
19
+ /** Why there is no measurement: the target resolved to nothing. */
20
+ error?: string;
21
+ /** The Vue component behind the element, when asked for and present. */
22
+ component?: ComponentInfo | ComponentUnavailable;
23
+ }
24
+ export interface MeasureOptions {
25
+ /** Also read the Vue component that rendered each element. */
26
+ components?: boolean;
27
+ }
28
+ /** Measure each target — a snapshot ref (`e8`) or a CSS selector — in order. */
29
+ export declare function measureElements(page: Page, targets: readonly string[], options?: MeasureOptions): Promise<InspectResult[]>;
30
+ /** Every font, size, weight, colour, spacing value and radius the page (or `selector`) uses. */
31
+ export declare function inventoryPage(page: Page, selector?: string): Promise<PageInventory>;
@@ -0,0 +1,383 @@
1
+ import { MAX_INSPECT_TEXT_LENGTH, MAX_INVENTORY_ELEMENTS, MAX_INVENTORY_VALUES, SELECTOR_TIMEOUT_MS, } from "../constants.js";
2
+ import { isRef, refSelector } from "../utils/snapshot-rules.js";
3
+ import { parseColour, toHex } from "../utils/style-rules.js";
4
+ import { componentOf } from "./vue.js";
5
+ /** Measure each target — a snapshot ref (`e8`) or a CSS selector — in order. */
6
+ export async function measureElements(page, targets, options = {}) {
7
+ const results = [];
8
+ for (const target of targets) {
9
+ const result = await measureOne(page, target);
10
+ if (options.components && result.measurement) {
11
+ const component = await componentOf(resolve(page, target));
12
+ if (component)
13
+ result.component = component;
14
+ }
15
+ results.push(result);
16
+ }
17
+ return results;
18
+ }
19
+ async function measureOne(page, target) {
20
+ const locator = resolve(page, target);
21
+ let count;
22
+ try {
23
+ count = await locator.count();
24
+ }
25
+ catch (error) {
26
+ return { target, error: `could not be resolved: ${firstLine(error)}` };
27
+ }
28
+ if (count === 0) {
29
+ return {
30
+ target,
31
+ error: isRef(target)
32
+ ? "did not resolve — the page has changed since that snapshot, or none was taken; run framewatch_snapshot again"
33
+ : "matched nothing",
34
+ };
35
+ }
36
+ try {
37
+ const raw = await locator.evaluate(measureInPage, { max_text: MAX_INSPECT_TEXT_LENGTH }, { timeout: SELECTOR_TIMEOUT_MS });
38
+ return { target, measurement: { target, ...raw } };
39
+ }
40
+ catch (error) {
41
+ return { target, error: `could not be measured: ${firstLine(error)}` };
42
+ }
43
+ }
44
+ function resolve(page, target) {
45
+ return isRef(target) ? page.locator(refSelector(target)) : page.locator(target).first();
46
+ }
47
+ function firstLine(error) {
48
+ return (error instanceof Error ? error.message : String(error)).split("\n")[0];
49
+ }
50
+ /* ── Inventory ────────────────────────────────────────────────────────── */
51
+ /** Every font, size, weight, colour, spacing value and radius the page (or `selector`) uses. */
52
+ export async function inventoryPage(page, selector) {
53
+ const raw = await page.evaluate(inventoryInPage, { selector: selector ?? null, max_elements: MAX_INVENTORY_ELEMENTS });
54
+ const tally = (map, normalise = (v) => v) => {
55
+ const merged = new Map();
56
+ for (const [value, count] of Object.entries(map)) {
57
+ const key = normalise(value);
58
+ if (key === null)
59
+ continue;
60
+ merged.set(key, (merged.get(key) ?? 0) + count);
61
+ }
62
+ return [...merged.entries()]
63
+ .map(([value, count]) => ({ value, count }))
64
+ .sort((a, b) => b.count - a.count || a.value.localeCompare(b.value))
65
+ .slice(0, MAX_INVENTORY_VALUES);
66
+ };
67
+ const hex = (value) => {
68
+ const colour = parseColour(value);
69
+ if (!colour || colour.a === 0)
70
+ return null;
71
+ return toHex(colour);
72
+ };
73
+ return {
74
+ elements: raw.elements,
75
+ text_elements: raw.text_elements,
76
+ fonts: tally(raw.fonts),
77
+ font_sizes: tally(raw.font_sizes),
78
+ font_weights: tally(raw.font_weights),
79
+ text_colours: tally(raw.text_colours, hex),
80
+ backgrounds: tally(raw.backgrounds, hex),
81
+ spacing: tally(raw.spacing),
82
+ radii: tally(raw.radii),
83
+ };
84
+ }
85
+ function measureInPage(element, config) {
86
+ const g = globalThis;
87
+ const win = g.window;
88
+ const doc = g.document;
89
+ const px = (value) => {
90
+ const n = parseFloat(value);
91
+ return Number.isFinite(n) ? Math.round(n * 100) / 100 : 0;
92
+ };
93
+ const rect = (node) => {
94
+ const r = node.getBoundingClientRect();
95
+ return { x: Math.round(r.left), y: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) };
96
+ };
97
+ const style = win.getComputedStyle(element);
98
+ const box = rect(element);
99
+ // Effective background: composite this element's own over each ancestor's
100
+ // until one is opaque. Done here as a list of layers, blended in Node.
101
+ const layers = [];
102
+ let node = element;
103
+ while (node && node.nodeType === 1) {
104
+ const bg = win.getComputedStyle(node).backgroundColor;
105
+ layers.push(bg);
106
+ if (/^rgb\(/.test(bg))
107
+ break;
108
+ node = node.parentElement;
109
+ }
110
+ const background = blend(layers);
111
+ // Clipped by an ancestor whose overflow is not visible.
112
+ let clipped = false;
113
+ let ancestor = element.parentElement;
114
+ while (ancestor && ancestor !== doc.documentElement) {
115
+ const s = win.getComputedStyle(ancestor);
116
+ if (s.overflow !== "visible" || s.overflowX !== "visible" || s.overflowY !== "visible") {
117
+ const a = ancestor.getBoundingClientRect();
118
+ const r = element.getBoundingClientRect();
119
+ if (r.left < a.left - 0.5 || r.right > a.right + 0.5 || r.top < a.top - 0.5 || r.bottom > a.bottom + 0.5) {
120
+ clipped = true;
121
+ break;
122
+ }
123
+ }
124
+ ancestor = ancestor.parentElement;
125
+ }
126
+ const vw = win.innerWidth;
127
+ const vh = win.innerHeight;
128
+ const r = element.getBoundingClientRect();
129
+ const inside = r.left >= 0 && r.top >= 0 && r.right <= vw && r.bottom <= vh;
130
+ const outside = r.right <= 0 || r.bottom <= 0 || r.left >= vw || r.top >= vh;
131
+ const in_viewport = inside ? "fully" : outside ? "none" : "partially";
132
+ const opacity = px(style.opacity);
133
+ const visible = style.display !== "none" && style.visibility !== "hidden" && opacity > 0 && r.width > 0 && r.height > 0;
134
+ const text_overflows = element.scrollWidth > element.clientWidth + 1 || element.scrollHeight > element.clientHeight + 1;
135
+ const ownText = () => {
136
+ let out = "";
137
+ const children = element.childNodes;
138
+ for (let i = 0; i < children.length; i++) {
139
+ if (children[i].nodeType === 3)
140
+ out += children[i].nodeValue;
141
+ }
142
+ out = out.replace(/\s+/g, " ").trim();
143
+ if (out === "" && typeof element.value === "string")
144
+ out = element.value;
145
+ if (out === "")
146
+ out = String(element.textContent || "").replace(/\s+/g, " ").trim();
147
+ return out.length > config.max_text ? out.slice(0, config.max_text) + "…" : out;
148
+ };
149
+ const accessibleName = () => {
150
+ const label = element.getAttribute("aria-label");
151
+ if (label)
152
+ return String(label).trim();
153
+ const labelledBy = element.getAttribute("aria-labelledby");
154
+ if (labelledBy) {
155
+ const names = String(labelledBy)
156
+ .split(/\s+/)
157
+ .map((id) => doc.getElementById(id))
158
+ .filter(Boolean)
159
+ .map((n) => String(n.textContent || "").replace(/\s+/g, " ").trim());
160
+ if (names.length > 0)
161
+ return names.join(" ");
162
+ }
163
+ if (element.labels && element.labels.length > 0) {
164
+ return String(element.labels[0].textContent || "").replace(/\s+/g, " ").trim();
165
+ }
166
+ const alt = element.getAttribute("alt");
167
+ if (alt)
168
+ return String(alt).trim();
169
+ const title = element.getAttribute("title");
170
+ if (title)
171
+ return String(title).trim();
172
+ const tag = String(element.tagName).toLowerCase();
173
+ if (tag === "button" || tag === "a" || /^h[1-6]$/.test(tag) || element.getAttribute("role")) {
174
+ const text = String(element.textContent || "").replace(/\s+/g, " ").trim();
175
+ return text === "" ? undefined : text.length > config.max_text ? text.slice(0, config.max_text) + "…" : text;
176
+ }
177
+ return undefined;
178
+ };
179
+ const implicitRole = () => {
180
+ const explicit = element.getAttribute("role");
181
+ if (explicit)
182
+ return String(explicit);
183
+ const tag = String(element.tagName).toLowerCase();
184
+ const type = String(element.getAttribute("type") || "").toLowerCase();
185
+ if (tag === "button")
186
+ return "button";
187
+ if (tag === "a" && element.hasAttribute("href"))
188
+ return "link";
189
+ if (/^h[1-6]$/.test(tag))
190
+ return "heading";
191
+ if (tag === "input") {
192
+ if (type === "checkbox")
193
+ return "checkbox";
194
+ if (type === "radio")
195
+ return "radio";
196
+ if (type === "button" || type === "submit" || type === "reset")
197
+ return "button";
198
+ if (type === "range")
199
+ return "slider";
200
+ if (type === "number")
201
+ return "spinbutton";
202
+ return "textbox";
203
+ }
204
+ if (tag === "textarea")
205
+ return "textbox";
206
+ if (tag === "select")
207
+ return "combobox";
208
+ if (tag === "img")
209
+ return "img";
210
+ if (tag === "nav")
211
+ return "navigation";
212
+ if (tag === "main")
213
+ return "main";
214
+ if (tag === "p")
215
+ return "paragraph";
216
+ if (tag === "ul" || tag === "ol")
217
+ return "list";
218
+ if (tag === "li")
219
+ return "listitem";
220
+ return undefined;
221
+ };
222
+ const neighbour = (node) => node && node.nodeType === 1 ? { tag: String(node.tagName).toLowerCase(), box: rect(node) } : undefined;
223
+ const lineHeight = style.lineHeight === "normal" ? null : px(style.lineHeight);
224
+ const gap = style.display.indexOf("flex") !== -1 || style.display.indexOf("grid") !== -1 ? px(style.rowGap === "normal" ? "0" : style.rowGap) : null;
225
+ const name = accessibleName();
226
+ const classes = [];
227
+ if (element.classList)
228
+ for (let i = 0; i < element.classList.length && i < 4; i++)
229
+ classes.push(String(element.classList[i]));
230
+ const out = {
231
+ tag: String(element.tagName).toLowerCase(),
232
+ classes,
233
+ box,
234
+ visible,
235
+ in_viewport,
236
+ clipped,
237
+ text_overflows,
238
+ display: String(style.display),
239
+ position: String(style.position),
240
+ opacity,
241
+ font: {
242
+ family: String(style.fontFamily).split(",")[0].replace(/["']/g, "").trim(),
243
+ size: px(style.fontSize),
244
+ weight: px(style.fontWeight),
245
+ line_height: lineHeight,
246
+ style: String(style.fontStyle),
247
+ },
248
+ color: String(style.color),
249
+ background,
250
+ padding: [px(style.paddingTop), px(style.paddingRight), px(style.paddingBottom), px(style.paddingLeft)],
251
+ margin: [px(style.marginTop), px(style.marginRight), px(style.marginBottom), px(style.marginLeft)],
252
+ gap,
253
+ border: `${style.borderTopWidth} ${style.borderTopStyle} ${style.borderTopColor}`,
254
+ radius: String(style.borderTopLeftRadius),
255
+ };
256
+ if (element.id)
257
+ out.id = String(element.id);
258
+ const role = implicitRole();
259
+ if (role !== undefined)
260
+ out.role = role;
261
+ if (name !== undefined)
262
+ out.name = name;
263
+ const text = ownText();
264
+ if (text !== "")
265
+ out.text = text;
266
+ const parent = neighbour(element.parentElement);
267
+ if (parent)
268
+ out.parent = parent;
269
+ const previous = neighbour(element.previousElementSibling);
270
+ if (previous)
271
+ out.previous = previous;
272
+ return out;
273
+ /** Composite `layers[0]` over `layers[1]` over … ; returns an rgb()/rgba() string. */
274
+ function blend(list) {
275
+ const parse = (value) => {
276
+ const m = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)$/.exec(value);
277
+ if (!m)
278
+ return value === "transparent" ? [0, 0, 0, 0] : null;
279
+ return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] === undefined ? 1 : Number(m[4])];
280
+ };
281
+ // Start from the deepest (last) layer and paint each one above it on top.
282
+ let acc = null;
283
+ for (let i = list.length - 1; i >= 0; i--) {
284
+ const top = parse(list[i]);
285
+ if (!top)
286
+ continue;
287
+ if (!acc || top[3] >= 1) {
288
+ acc = top;
289
+ continue;
290
+ }
291
+ if (top[3] <= 0)
292
+ continue;
293
+ const below = acc;
294
+ const a = top[3] + below[3] * (1 - top[3]);
295
+ const mix = (t, b) => Math.round((t * top[3] + b * below[3] * (1 - top[3])) / a);
296
+ acc = [mix(top[0], below[0]), mix(top[1], below[1]), mix(top[2], below[2]), a];
297
+ }
298
+ if (!acc)
299
+ return "rgba(0, 0, 0, 0)";
300
+ // The page's canvas is white under everything that is still translucent.
301
+ if (acc[3] < 1) {
302
+ const a = acc[3];
303
+ acc = [Math.round(acc[0] * a + 255 * (1 - a)), Math.round(acc[1] * a + 255 * (1 - a)), Math.round(acc[2] * a + 255 * (1 - a)), 1];
304
+ }
305
+ return `rgb(${acc[0]}, ${acc[1]}, ${acc[2]})`;
306
+ }
307
+ }
308
+ function inventoryInPage(config) {
309
+ const g = globalThis;
310
+ const win = g.window;
311
+ const doc = g.document;
312
+ const out = {
313
+ elements: 0,
314
+ text_elements: 0,
315
+ fonts: {},
316
+ font_sizes: {},
317
+ font_weights: {},
318
+ text_colours: {},
319
+ backgrounds: {},
320
+ spacing: {},
321
+ radii: {},
322
+ };
323
+ const bump = (map, value) => {
324
+ map[value] = (map[value] || 0) + 1;
325
+ };
326
+ const SKIP = ["script", "style", "link", "meta", "title", "head", "noscript", "template", "br", "svg", "path"];
327
+ const root = config.selector ? doc.querySelector(config.selector) : doc.body;
328
+ if (!root)
329
+ return out;
330
+ const all = [root].concat(Array.prototype.slice.call(root.querySelectorAll("*")));
331
+ for (const el of all) {
332
+ if (out.elements >= config.max_elements)
333
+ break;
334
+ const tag = String(el.tagName || "").toLowerCase();
335
+ if (SKIP.indexOf(tag) !== -1)
336
+ continue;
337
+ const s = win.getComputedStyle(el);
338
+ if (s.display === "none")
339
+ continue;
340
+ out.elements++;
341
+ let ownText = "";
342
+ for (let i = 0; i < el.childNodes.length; i++) {
343
+ if (el.childNodes[i].nodeType === 3)
344
+ ownText += el.childNodes[i].nodeValue;
345
+ }
346
+ const hasText = ownText.trim() !== "" || (typeof el.value === "string" && el.value !== "" && tag !== "option");
347
+ if (hasText) {
348
+ out.text_elements++;
349
+ bump(out.fonts, String(s.fontFamily).split(",")[0].replace(/["']/g, "").trim());
350
+ bump(out.font_sizes, s.fontSize);
351
+ bump(out.font_weights, s.fontWeight);
352
+ bump(out.text_colours, s.color);
353
+ }
354
+ if (s.backgroundColor && s.backgroundColor !== "rgba(0, 0, 0, 0)" && s.backgroundColor !== "transparent") {
355
+ bump(out.backgrounds, s.backgroundColor);
356
+ }
357
+ const spacing = [
358
+ s.paddingTop,
359
+ s.paddingRight,
360
+ s.paddingBottom,
361
+ s.paddingLeft,
362
+ s.marginTop,
363
+ s.marginRight,
364
+ s.marginBottom,
365
+ s.marginLeft,
366
+ ];
367
+ if (s.display.indexOf("flex") !== -1 || s.display.indexOf("grid") !== -1) {
368
+ if (s.rowGap !== "normal")
369
+ spacing.push(s.rowGap);
370
+ if (s.columnGap !== "normal" && s.columnGap !== s.rowGap)
371
+ spacing.push(s.columnGap);
372
+ }
373
+ for (const value of spacing) {
374
+ if (value && value !== "0px" && parseFloat(value) > 0)
375
+ bump(out.spacing, value);
376
+ }
377
+ const radius = s.borderTopLeftRadius;
378
+ if (radius && radius !== "0px")
379
+ bump(out.radii, radius);
380
+ }
381
+ return out;
382
+ }
383
+ //# sourceMappingURL=inspect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../src/engine/inspect.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Locator, Page } from \"playwright\";\nimport {\n MAX_INSPECT_TEXT_LENGTH,\n MAX_INVENTORY_ELEMENTS,\n MAX_INVENTORY_VALUES,\n SELECTOR_TIMEOUT_MS,\n} from \"../constants.js\";\nimport { isRef, refSelector } from \"../utils/snapshot-rules.js\";\nimport { parseColour, toHex, type ElementMeasurement, type PageInventory, type Tally } from \"../utils/style-rules.js\";\nimport type { ComponentInfo, ComponentUnavailable } from \"../utils/vue-rules.js\";\nimport { componentOf } from \"./vue.js\";\n\n/**\n * How an element is built, measured inside the page.\n *\n * A screenshot shows what a button looks like; this says what it *is*: the\n * box in viewport pixels, the font, the colours it actually renders in (a\n * transparent background resolved through its ancestors), the spacing, and\n * where it sits against its parent and the sibling before it. That is what an\n * agent needs to check its own work against the design it meant to build.\n *\n * Every number is read in one `evaluate` per element and judged in\n * `style-rules.ts`, which never sees a browser.\n */\n\nexport interface InspectResult {\n target: string;\n measurement?: ElementMeasurement;\n /** Why there is no measurement: the target resolved to nothing. */\n error?: string;\n /** The Vue component behind the element, when asked for and present. */\n component?: ComponentInfo | ComponentUnavailable;\n}\n\nexport interface MeasureOptions {\n /** Also read the Vue component that rendered each element. */\n components?: boolean;\n}\n\n/** Measure each target — a snapshot ref (`e8`) or a CSS selector — in order. */\nexport async function measureElements(page: Page, targets: readonly string[], options: MeasureOptions = {}): Promise<InspectResult[]> {\n const results: InspectResult[] = [];\n for (const target of targets) {\n const result = await measureOne(page, target);\n if (options.components && result.measurement) {\n const component = await componentOf(resolve(page, target));\n if (component) result.component = component;\n }\n results.push(result);\n }\n return results;\n}\n\nasync function measureOne(page: Page, target: string): Promise<InspectResult> {\n const locator = resolve(page, target);\n let count: number;\n try {\n count = await locator.count();\n } catch (error) {\n return { target, error: `could not be resolved: ${firstLine(error)}` };\n }\n if (count === 0) {\n return {\n target,\n error: isRef(target)\n ? \"did not resolve — the page has changed since that snapshot, or none was taken; run framewatch_snapshot again\"\n : \"matched nothing\",\n };\n }\n try {\n const raw = await locator.evaluate(measureInPage, { max_text: MAX_INSPECT_TEXT_LENGTH }, { timeout: SELECTOR_TIMEOUT_MS });\n return { target, measurement: { target, ...raw } };\n } catch (error) {\n return { target, error: `could not be measured: ${firstLine(error)}` };\n }\n}\n\nfunction resolve(page: Page, target: string): Locator {\n return isRef(target) ? page.locator(refSelector(target)) : page.locator(target).first();\n}\n\nfunction firstLine(error: unknown): string {\n return (error instanceof Error ? error.message : String(error)).split(\"\\n\")[0];\n}\n\n/* ── Inventory ────────────────────────────────────────────────────────── */\n\n/** Every font, size, weight, colour, spacing value and radius the page (or `selector`) uses. */\nexport async function inventoryPage(page: Page, selector?: string): Promise<PageInventory> {\n const raw = await page.evaluate(inventoryInPage, { selector: selector ?? null, max_elements: MAX_INVENTORY_ELEMENTS });\n const tally = (map: Record<string, number>, normalise: (value: string) => string | null = (v) => v): Tally[] => {\n const merged = new Map<string, number>();\n for (const [value, count] of Object.entries(map)) {\n const key = normalise(value);\n if (key === null) continue;\n merged.set(key, (merged.get(key) ?? 0) + count);\n }\n return [...merged.entries()]\n .map(([value, count]) => ({ value, count }))\n .sort((a, b) => b.count - a.count || a.value.localeCompare(b.value))\n .slice(0, MAX_INVENTORY_VALUES);\n };\n const hex = (value: string): string | null => {\n const colour = parseColour(value);\n if (!colour || colour.a === 0) return null;\n return toHex(colour);\n };\n return {\n elements: raw.elements,\n text_elements: raw.text_elements,\n fonts: tally(raw.fonts),\n font_sizes: tally(raw.font_sizes),\n font_weights: tally(raw.font_weights),\n text_colours: tally(raw.text_colours, hex),\n backgrounds: tally(raw.backgrounds, hex),\n spacing: tally(raw.spacing),\n radii: tally(raw.radii),\n };\n}\n\n/* ── In-page ──────────────────────────────────────────────────────────────\n * Everything below runs inside Chromium. Written against `globalThis` and\n * untyped nodes because this package is compiled with the Node lib only —\n * and nothing here may close over a module value: the function body is\n * serialised and run where the module does not exist (CLAUDE.md, decision 50).\n */\n\ntype RawMeasurement = Omit<ElementMeasurement, \"target\">;\n\nfunction measureInPage(element: any, config: { max_text: number }): RawMeasurement {\n const g = globalThis as any;\n const win = g.window;\n const doc = g.document;\n\n const px = (value: string): number => {\n const n = parseFloat(value);\n return Number.isFinite(n) ? Math.round(n * 100) / 100 : 0;\n };\n const rect = (node: any): { x: number; y: number; width: number; height: number } => {\n const r = node.getBoundingClientRect();\n return { x: Math.round(r.left), y: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) };\n };\n\n const style = win.getComputedStyle(element);\n const box = rect(element);\n\n // Effective background: composite this element's own over each ancestor's\n // until one is opaque. Done here as a list of layers, blended in Node.\n const layers: string[] = [];\n let node: any = element;\n while (node && node.nodeType === 1) {\n const bg = win.getComputedStyle(node).backgroundColor;\n layers.push(bg);\n if (/^rgb\\(/.test(bg)) break;\n node = node.parentElement;\n }\n const background = blend(layers);\n\n // Clipped by an ancestor whose overflow is not visible.\n let clipped = false;\n let ancestor: any = element.parentElement;\n while (ancestor && ancestor !== doc.documentElement) {\n const s = win.getComputedStyle(ancestor);\n if (s.overflow !== \"visible\" || s.overflowX !== \"visible\" || s.overflowY !== \"visible\") {\n const a = ancestor.getBoundingClientRect();\n const r = element.getBoundingClientRect();\n if (r.left < a.left - 0.5 || r.right > a.right + 0.5 || r.top < a.top - 0.5 || r.bottom > a.bottom + 0.5) {\n clipped = true;\n break;\n }\n }\n ancestor = ancestor.parentElement;\n }\n\n const vw = win.innerWidth;\n const vh = win.innerHeight;\n const r = element.getBoundingClientRect();\n const inside = r.left >= 0 && r.top >= 0 && r.right <= vw && r.bottom <= vh;\n const outside = r.right <= 0 || r.bottom <= 0 || r.left >= vw || r.top >= vh;\n const in_viewport: \"fully\" | \"partially\" | \"none\" = inside ? \"fully\" : outside ? \"none\" : \"partially\";\n\n const opacity = px(style.opacity);\n const visible =\n style.display !== \"none\" && style.visibility !== \"hidden\" && opacity > 0 && r.width > 0 && r.height > 0;\n\n const text_overflows = element.scrollWidth > element.clientWidth + 1 || element.scrollHeight > element.clientHeight + 1;\n\n const ownText = (): string => {\n let out = \"\";\n const children = element.childNodes;\n for (let i = 0; i < children.length; i++) {\n if (children[i].nodeType === 3) out += children[i].nodeValue;\n }\n out = out.replace(/\\s+/g, \" \").trim();\n if (out === \"\" && typeof element.value === \"string\") out = element.value;\n if (out === \"\") out = String(element.textContent || \"\").replace(/\\s+/g, \" \").trim();\n return out.length > config.max_text ? out.slice(0, config.max_text) + \"…\" : out;\n };\n\n const accessibleName = (): string | undefined => {\n const label = element.getAttribute(\"aria-label\");\n if (label) return String(label).trim();\n const labelledBy = element.getAttribute(\"aria-labelledby\");\n if (labelledBy) {\n const names = String(labelledBy)\n .split(/\\s+/)\n .map((id) => doc.getElementById(id))\n .filter(Boolean)\n .map((n: any) => String(n.textContent || \"\").replace(/\\s+/g, \" \").trim());\n if (names.length > 0) return names.join(\" \");\n }\n if (element.labels && element.labels.length > 0) {\n return String(element.labels[0].textContent || \"\").replace(/\\s+/g, \" \").trim();\n }\n const alt = element.getAttribute(\"alt\");\n if (alt) return String(alt).trim();\n const title = element.getAttribute(\"title\");\n if (title) return String(title).trim();\n const tag = String(element.tagName).toLowerCase();\n if (tag === \"button\" || tag === \"a\" || /^h[1-6]$/.test(tag) || element.getAttribute(\"role\")) {\n const text = String(element.textContent || \"\").replace(/\\s+/g, \" \").trim();\n return text === \"\" ? undefined : text.length > config.max_text ? text.slice(0, config.max_text) + \"…\" : text;\n }\n return undefined;\n };\n\n const implicitRole = (): string | undefined => {\n const explicit = element.getAttribute(\"role\");\n if (explicit) return String(explicit);\n const tag = String(element.tagName).toLowerCase();\n const type = String(element.getAttribute(\"type\") || \"\").toLowerCase();\n if (tag === \"button\") return \"button\";\n if (tag === \"a\" && element.hasAttribute(\"href\")) return \"link\";\n if (/^h[1-6]$/.test(tag)) return \"heading\";\n if (tag === \"input\") {\n if (type === \"checkbox\") return \"checkbox\";\n if (type === \"radio\") return \"radio\";\n if (type === \"button\" || type === \"submit\" || type === \"reset\") return \"button\";\n if (type === \"range\") return \"slider\";\n if (type === \"number\") return \"spinbutton\";\n return \"textbox\";\n }\n if (tag === \"textarea\") return \"textbox\";\n if (tag === \"select\") return \"combobox\";\n if (tag === \"img\") return \"img\";\n if (tag === \"nav\") return \"navigation\";\n if (tag === \"main\") return \"main\";\n if (tag === \"p\") return \"paragraph\";\n if (tag === \"ul\" || tag === \"ol\") return \"list\";\n if (tag === \"li\") return \"listitem\";\n return undefined;\n };\n\n const neighbour = (node: any): { tag: string; box: { x: number; y: number; width: number; height: number } } | undefined =>\n node && node.nodeType === 1 ? { tag: String(node.tagName).toLowerCase(), box: rect(node) } : undefined;\n\n const lineHeight = style.lineHeight === \"normal\" ? null : px(style.lineHeight);\n const gap = style.display.indexOf(\"flex\") !== -1 || style.display.indexOf(\"grid\") !== -1 ? px(style.rowGap === \"normal\" ? \"0\" : style.rowGap) : null;\n\n const name = accessibleName();\n const classes: string[] = [];\n if (element.classList) for (let i = 0; i < element.classList.length && i < 4; i++) classes.push(String(element.classList[i]));\n\n const out: RawMeasurement = {\n tag: String(element.tagName).toLowerCase(),\n classes,\n box,\n visible,\n in_viewport,\n clipped,\n text_overflows,\n display: String(style.display),\n position: String(style.position),\n opacity,\n font: {\n family: String(style.fontFamily).split(\",\")[0].replace(/[\"']/g, \"\").trim(),\n size: px(style.fontSize),\n weight: px(style.fontWeight),\n line_height: lineHeight,\n style: String(style.fontStyle),\n },\n color: String(style.color),\n background,\n padding: [px(style.paddingTop), px(style.paddingRight), px(style.paddingBottom), px(style.paddingLeft)],\n margin: [px(style.marginTop), px(style.marginRight), px(style.marginBottom), px(style.marginLeft)],\n gap,\n border: `${style.borderTopWidth} ${style.borderTopStyle} ${style.borderTopColor}`,\n radius: String(style.borderTopLeftRadius),\n };\n if (element.id) out.id = String(element.id);\n const role = implicitRole();\n if (role !== undefined) out.role = role;\n if (name !== undefined) out.name = name;\n const text = ownText();\n if (text !== \"\") out.text = text;\n const parent = neighbour(element.parentElement);\n if (parent) out.parent = parent;\n const previous = neighbour(element.previousElementSibling);\n if (previous) out.previous = previous;\n return out;\n\n /** Composite `layers[0]` over `layers[1]` over … ; returns an rgb()/rgba() string. */\n function blend(list: string[]): string {\n const parse = (value: string): [number, number, number, number] | null => {\n const m = /^rgba?\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*(?:,\\s*([\\d.]+)\\s*)?\\)$/.exec(value);\n if (!m) return value === \"transparent\" ? [0, 0, 0, 0] : null;\n return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] === undefined ? 1 : Number(m[4])];\n };\n // Start from the deepest (last) layer and paint each one above it on top.\n let acc: [number, number, number, number] | null = null;\n for (let i = list.length - 1; i >= 0; i--) {\n const top = parse(list[i]);\n if (!top) continue;\n if (!acc || top[3] >= 1) {\n acc = top;\n continue;\n }\n if (top[3] <= 0) continue;\n const below: [number, number, number, number] = acc;\n const a: number = top[3] + below[3] * (1 - top[3]);\n const mix = (t: number, b: number): number => Math.round((t * top[3] + b * below[3] * (1 - top[3])) / a);\n acc = [mix(top[0], below[0]), mix(top[1], below[1]), mix(top[2], below[2]), a];\n }\n if (!acc) return \"rgba(0, 0, 0, 0)\";\n // The page's canvas is white under everything that is still translucent.\n if (acc[3] < 1) {\n const a = acc[3];\n acc = [Math.round(acc[0] * a + 255 * (1 - a)), Math.round(acc[1] * a + 255 * (1 - a)), Math.round(acc[2] * a + 255 * (1 - a)), 1];\n }\n return `rgb(${acc[0]}, ${acc[1]}, ${acc[2]})`;\n }\n}\n\ninterface RawInventory {\n elements: number;\n text_elements: number;\n fonts: Record<string, number>;\n font_sizes: Record<string, number>;\n font_weights: Record<string, number>;\n text_colours: Record<string, number>;\n backgrounds: Record<string, number>;\n spacing: Record<string, number>;\n radii: Record<string, number>;\n}\n\nfunction inventoryInPage(config: { selector: string | null; max_elements: number }): RawInventory {\n const g = globalThis as any;\n const win = g.window;\n const doc = g.document;\n const out: RawInventory = {\n elements: 0,\n text_elements: 0,\n fonts: {},\n font_sizes: {},\n font_weights: {},\n text_colours: {},\n backgrounds: {},\n spacing: {},\n radii: {},\n };\n const bump = (map: Record<string, number>, value: string): void => {\n map[value] = (map[value] || 0) + 1;\n };\n const SKIP = [\"script\", \"style\", \"link\", \"meta\", \"title\", \"head\", \"noscript\", \"template\", \"br\", \"svg\", \"path\"];\n\n const root = config.selector ? doc.querySelector(config.selector) : doc.body;\n if (!root) return out;\n const all = [root].concat(Array.prototype.slice.call(root.querySelectorAll(\"*\")));\n\n for (const el of all) {\n if (out.elements >= config.max_elements) break;\n const tag = String(el.tagName || \"\").toLowerCase();\n if (SKIP.indexOf(tag) !== -1) continue;\n const s = win.getComputedStyle(el);\n if (s.display === \"none\") continue;\n out.elements++;\n\n let ownText = \"\";\n for (let i = 0; i < el.childNodes.length; i++) {\n if (el.childNodes[i].nodeType === 3) ownText += el.childNodes[i].nodeValue;\n }\n const hasText = ownText.trim() !== \"\" || (typeof el.value === \"string\" && el.value !== \"\" && tag !== \"option\");\n if (hasText) {\n out.text_elements++;\n bump(out.fonts, String(s.fontFamily).split(\",\")[0].replace(/[\"']/g, \"\").trim());\n bump(out.font_sizes, s.fontSize);\n bump(out.font_weights, s.fontWeight);\n bump(out.text_colours, s.color);\n }\n if (s.backgroundColor && s.backgroundColor !== \"rgba(0, 0, 0, 0)\" && s.backgroundColor !== \"transparent\") {\n bump(out.backgrounds, s.backgroundColor);\n }\n const spacing = [\n s.paddingTop,\n s.paddingRight,\n s.paddingBottom,\n s.paddingLeft,\n s.marginTop,\n s.marginRight,\n s.marginBottom,\n s.marginLeft,\n ];\n if (s.display.indexOf(\"flex\") !== -1 || s.display.indexOf(\"grid\") !== -1) {\n if (s.rowGap !== \"normal\") spacing.push(s.rowGap);\n if (s.columnGap !== \"normal\" && s.columnGap !== s.rowGap) spacing.push(s.columnGap);\n }\n for (const value of spacing) {\n if (value && value !== \"0px\" && parseFloat(value) > 0) bump(out.spacing, value);\n }\n const radius = s.borderTopLeftRadius;\n if (radius && radius !== \"0px\") bump(out.radii, radius);\n }\n return out;\n}\n"]}
@@ -9,16 +9,16 @@ import type { Page } from "playwright";
9
9
  * a single actionable line naming the step that failed — a recording is a bad
10
10
  * place to surface a 30-line call log.
11
11
  */
12
- /** Actions `framewatch_capture` can replay during a recording. */
13
- export declare const CAPTURE_ACTIONS: readonly ["click", "tap", "type", "scroll", "swipe", "wait", "navigate"];
14
- /** Actions `framewatch_interact` can perform as a one-off. */
15
- export declare const INTERACT_ACTIONS: readonly ["click", "tap", "type", "scroll", "swipe", "navigate", "select", "hover"];
12
+ /** Actions a replayable script (`framewatch_capture`, `framewatch_save_auth`) can perform. */
13
+ export declare const CAPTURE_ACTIONS: readonly ["click", "tap", "type", "key", "scroll", "swipe", "hover", "select", "wait", "navigate"];
14
+ /** Actions `framewatch_interact` can perform as a one-off — everything but `wait`, which needs a script to sit in. */
15
+ export declare const INTERACT_ACTIONS: readonly ["click", "tap", "type", "key", "scroll", "swipe", "navigate", "select", "hover"];
16
16
  export type InteractionAction = (typeof CAPTURE_ACTIONS)[number] | (typeof INTERACT_ACTIONS)[number];
17
17
  export interface Interaction {
18
18
  action: InteractionAction;
19
- /** CSS selector for click/tap/type/select/hover, or the scroll container. */
19
+ /** CSS selector for click/tap/type/select/hover, the field to focus for `key`, or the scroll container. */
20
20
  selector?: string;
21
- /** Text to type, option value to select, or URL to navigate to. */
21
+ /** Text to type, key to press, option value to select, or URL to navigate to. */
22
22
  value?: string;
23
23
  x?: number;
24
24
  y?: number;
@@ -31,6 +31,11 @@ export interface ExecuteOptions {
31
31
  /** Timeout for selector-based actions. Default SELECTOR_TIMEOUT_MS. */
32
32
  timeout_ms?: number;
33
33
  }
34
+ /** What a step has to add to its own description once it has run. */
35
+ export interface InteractionOutcome {
36
+ /** Appended to `describeInteraction(step)`, e.g. ` (vue-router)`. */
37
+ note?: string;
38
+ }
34
39
  /** True when any step needs a touch-enabled browser context. */
35
40
  export declare function needsTouch(interactions: readonly Interaction[]): boolean;
36
41
  /**
@@ -54,7 +59,7 @@ export declare function describeInteraction(interaction: Interaction): string;
54
59
  * Throws a single-line Error naming the step on any failure; the page is never
55
60
  * touched when the step is invalid.
56
61
  */
57
- export declare function executeInteraction(page: Page, interaction: Interaction, options?: ExecuteOptions): Promise<void>;
62
+ export declare function executeInteraction(page: Page, interaction: Interaction, options?: ExecuteOptions): Promise<InteractionOutcome>;
58
63
  /**
59
64
  * The shared zod fields every interaction step accepts. Both tools build their
60
65
  * own object around these (the action enums differ, and `framewatch_capture`