@treelocator/runtime 0.5.2 → 0.6.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 (163) hide show
  1. package/.eslintignore +1 -0
  2. package/dist/_generated_styles.d.ts +1 -1
  3. package/dist/_generated_styles.js +20 -0
  4. package/dist/_generated_tree_icon.d.ts +1 -1
  5. package/dist/adapters/HtmlElementTreeNode.d.ts +2 -2
  6. package/dist/adapters/HtmlElementTreeNode.js +4 -6
  7. package/dist/adapters/createTreeNode.js +17 -44
  8. package/dist/adapters/detectFramework.d.ts +8 -0
  9. package/dist/adapters/detectFramework.js +25 -0
  10. package/dist/adapters/detectFramework.test.d.ts +1 -0
  11. package/dist/adapters/detectFramework.test.js +60 -0
  12. package/dist/adapters/jsx/jsxAdapter.js +54 -89
  13. package/dist/adapters/jsx/jsxAdapter.test.d.ts +1 -0
  14. package/dist/adapters/jsx/jsxAdapter.test.js +273 -0
  15. package/dist/adapters/nextjs/parseNextjsDataAttributes.js +1 -1
  16. package/dist/adapters/nextjs/parseNextjsDataAttributes.test.d.ts +1 -0
  17. package/dist/adapters/nextjs/parseNextjsDataAttributes.test.js +158 -0
  18. package/dist/adapters/react/findFiberByHtmlElement.d.ts +1 -1
  19. package/dist/adapters/react/findFiberByHtmlElement.js +1 -1
  20. package/dist/adapters/react/getAllParentsElementsAndRootComponent.js +4 -0
  21. package/dist/adapters/resolveAdapter.d.ts +1 -1
  22. package/dist/adapters/resolveAdapter.js +4 -8
  23. package/dist/adapters/svelte/svelteAdapter.test.d.ts +1 -0
  24. package/dist/adapters/svelte/svelteAdapter.test.js +280 -0
  25. package/dist/adapters/vue/vueAdapter.test.d.ts +1 -0
  26. package/dist/adapters/vue/vueAdapter.test.js +222 -0
  27. package/dist/browserApi.d.ts +148 -0
  28. package/dist/browserApi.js +146 -5
  29. package/dist/browserApi.test.d.ts +1 -0
  30. package/dist/browserApi.test.js +287 -0
  31. package/dist/components/RecordingPillButton.d.ts +11 -0
  32. package/dist/components/RecordingPillButton.js +202 -0
  33. package/dist/components/RecordingResults.d.ts +2 -0
  34. package/dist/components/RecordingResults.js +213 -78
  35. package/dist/components/Runtime.js +161 -554
  36. package/dist/components/SettingsPanel.d.ts +5 -0
  37. package/dist/components/SettingsPanel.js +312 -0
  38. package/dist/consoleCapture.d.ts +9 -0
  39. package/dist/consoleCapture.js +95 -0
  40. package/dist/functions/cssRuleInspector.d.ts +83 -0
  41. package/dist/functions/cssRuleInspector.js +608 -0
  42. package/dist/functions/cssRuleInspector.test.d.ts +1 -0
  43. package/dist/functions/cssRuleInspector.test.js +439 -0
  44. package/dist/functions/deduplicateLabels.test.d.ts +1 -0
  45. package/dist/functions/deduplicateLabels.test.js +178 -0
  46. package/dist/functions/enrichAncestrySourceMaps.js +0 -1
  47. package/dist/functions/extractComputedStyles.d.ts +51 -0
  48. package/dist/functions/extractComputedStyles.js +447 -0
  49. package/dist/functions/extractComputedStyles.test.d.ts +1 -0
  50. package/dist/functions/extractComputedStyles.test.js +549 -0
  51. package/dist/functions/formatAncestryChain.d.ts +8 -0
  52. package/dist/functions/formatAncestryChain.js +21 -1
  53. package/dist/functions/formatAncestryChain.test.js +18 -0
  54. package/dist/functions/getUsableName.test.d.ts +1 -0
  55. package/dist/functions/getUsableName.test.js +219 -0
  56. package/dist/functions/isCombinationModifiersPressed.test.d.ts +1 -0
  57. package/dist/functions/isCombinationModifiersPressed.test.js +192 -0
  58. package/dist/functions/mergeRects.test.js +210 -1
  59. package/dist/functions/namedSnapshots.d.ts +52 -0
  60. package/dist/functions/namedSnapshots.js +161 -0
  61. package/dist/functions/namedSnapshots.test.d.ts +1 -0
  62. package/dist/functions/namedSnapshots.test.js +85 -0
  63. package/dist/functions/normalizeFilePath.test.d.ts +1 -0
  64. package/dist/functions/normalizeFilePath.test.js +66 -0
  65. package/dist/functions/parseDataId.test.d.ts +1 -0
  66. package/dist/functions/parseDataId.test.js +101 -0
  67. package/dist/hooks/getStorage.d.ts +3 -0
  68. package/dist/hooks/getStorage.js +17 -0
  69. package/dist/hooks/useEventListeners.d.ts +15 -0
  70. package/dist/hooks/useEventListeners.js +56 -0
  71. package/dist/hooks/useLocatorStorage.d.ts +18 -0
  72. package/dist/hooks/useLocatorStorage.js +41 -0
  73. package/dist/hooks/useLocatorStorage.test.d.ts +1 -0
  74. package/dist/hooks/useLocatorStorage.test.js +124 -0
  75. package/dist/hooks/useRecordingState.d.ts +43 -0
  76. package/dist/hooks/useRecordingState.js +387 -0
  77. package/dist/hooks/useSettings.d.ts +13 -0
  78. package/dist/hooks/useSettings.js +66 -0
  79. package/dist/index.d.ts +5 -2
  80. package/dist/index.js +4 -2
  81. package/dist/initRuntime.d.ts +3 -1
  82. package/dist/initRuntime.js +4 -1
  83. package/dist/mcpBridge.d.ts +61 -0
  84. package/dist/mcpBridge.js +534 -0
  85. package/dist/mcpBridge.test.d.ts +1 -0
  86. package/dist/mcpBridge.test.js +248 -0
  87. package/dist/output.css +20 -0
  88. package/dist/visualDiff/diff.d.ts +9 -0
  89. package/dist/visualDiff/diff.js +209 -0
  90. package/dist/visualDiff/diff.test.d.ts +1 -0
  91. package/dist/visualDiff/diff.test.js +253 -0
  92. package/dist/visualDiff/settle.d.ts +3 -0
  93. package/dist/visualDiff/settle.js +50 -0
  94. package/dist/visualDiff/settle.test.d.ts +1 -0
  95. package/dist/visualDiff/settle.test.js +65 -0
  96. package/dist/visualDiff/snapshot.d.ts +4 -0
  97. package/dist/visualDiff/snapshot.js +84 -0
  98. package/dist/visualDiff/snapshot.test.d.ts +1 -0
  99. package/dist/visualDiff/snapshot.test.js +245 -0
  100. package/dist/visualDiff/types.d.ts +37 -0
  101. package/dist/visualDiff/types.js +1 -0
  102. package/package.json +2 -2
  103. package/scripts/wrapCSS.js +1 -1
  104. package/scripts/wrapImage.js +1 -1
  105. package/src/_generated_styles.ts +21 -1
  106. package/src/_generated_tree_icon.ts +1 -1
  107. package/src/adapters/HtmlElementTreeNode.ts +10 -7
  108. package/src/adapters/createTreeNode.ts +12 -51
  109. package/src/adapters/detectFramework.test.ts +73 -0
  110. package/src/adapters/detectFramework.ts +28 -0
  111. package/src/adapters/jsx/jsxAdapter.test.ts +240 -0
  112. package/src/adapters/jsx/jsxAdapter.ts +53 -106
  113. package/src/adapters/nextjs/parseNextjsDataAttributes.test.ts +212 -0
  114. package/src/adapters/nextjs/parseNextjsDataAttributes.ts +1 -1
  115. package/src/adapters/react/findDebugSource.ts +5 -6
  116. package/src/adapters/react/findFiberByHtmlElement.ts +3 -3
  117. package/src/adapters/react/getAllParentsElementsAndRootComponent.ts +3 -0
  118. package/src/adapters/react/reactAdapter.ts +1 -2
  119. package/src/adapters/resolveAdapter.ts +4 -14
  120. package/src/adapters/svelte/svelteAdapter.test.ts +334 -0
  121. package/src/adapters/vue/vueAdapter.test.ts +259 -0
  122. package/src/browserApi.test.ts +329 -0
  123. package/src/browserApi.ts +351 -4
  124. package/src/components/RecordingPillButton.tsx +301 -0
  125. package/src/components/RecordingResults.tsx +114 -13
  126. package/src/components/Runtime.tsx +176 -621
  127. package/src/components/SettingsPanel.tsx +339 -0
  128. package/src/consoleCapture.ts +113 -0
  129. package/src/functions/cssRuleInspector.test.ts +517 -0
  130. package/src/functions/cssRuleInspector.ts +708 -0
  131. package/src/functions/deduplicateLabels.test.ts +115 -0
  132. package/src/functions/enrichAncestrySourceMaps.ts +6 -3
  133. package/src/functions/extractComputedStyles.test.ts +681 -0
  134. package/src/functions/extractComputedStyles.ts +768 -0
  135. package/src/functions/formatAncestryChain.test.ts +23 -1
  136. package/src/functions/formatAncestryChain.ts +22 -1
  137. package/src/functions/getUsableName.test.ts +242 -0
  138. package/src/functions/isCombinationModifiersPressed.test.ts +156 -0
  139. package/src/functions/mergeRects.test.ts +111 -1
  140. package/src/functions/namedSnapshots.test.ts +106 -0
  141. package/src/functions/namedSnapshots.ts +232 -0
  142. package/src/functions/normalizeFilePath.test.ts +80 -0
  143. package/src/functions/parseDataId.test.ts +125 -0
  144. package/src/hooks/getStorage.ts +26 -0
  145. package/src/hooks/useEventListeners.ts +97 -0
  146. package/src/hooks/useLocatorStorage.test.ts +127 -0
  147. package/src/hooks/useLocatorStorage.ts +60 -0
  148. package/src/hooks/useRecordingState.ts +516 -0
  149. package/src/hooks/useSettings.ts +83 -0
  150. package/src/index.ts +10 -5
  151. package/src/initRuntime.ts +5 -0
  152. package/src/mcpBridge.test.ts +260 -0
  153. package/src/mcpBridge.ts +677 -0
  154. package/src/visualDiff/diff.test.ts +167 -0
  155. package/src/visualDiff/diff.ts +242 -0
  156. package/src/visualDiff/settle.test.ts +77 -0
  157. package/src/visualDiff/settle.ts +62 -0
  158. package/src/visualDiff/snapshot.test.ts +200 -0
  159. package/src/visualDiff/snapshot.ts +119 -0
  160. package/src/visualDiff/types.ts +40 -0
  161. package/tsconfig.json +3 -1
  162. package/vitest.config.ts +18 -0
  163. package/jest.config.ts +0 -195
package/src/browserApi.ts CHANGED
@@ -3,11 +3,33 @@ import { createTreeNode } from "./adapters/createTreeNode";
3
3
  import {
4
4
  collectAncestry,
5
5
  formatAncestryChain,
6
+ getElementLabel,
6
7
  AncestryItem,
7
8
  } from "./functions/formatAncestryChain";
8
9
  import { enrichAncestryWithSourceMaps } from "./functions/enrichAncestrySourceMaps";
10
+ import {
11
+ inspectCSSRules,
12
+ formatCSSInspection,
13
+ CSSInspectionResult,
14
+ } from "./functions/cssRuleInspector";
15
+ import {
16
+ extractComputedStyles,
17
+ ComputedStylesResult,
18
+ ExtractOptions,
19
+ } from "./functions/extractComputedStyles";
20
+ import {
21
+ takeNamedSnapshot,
22
+ getNamedSnapshotDiff,
23
+ clearNamedSnapshot,
24
+ TakeSnapshotResult,
25
+ SnapshotDiffResult,
26
+ } from "./functions/namedSnapshots";
9
27
  import type { DejitterFinding, DejitterSummary } from "./dejitter/recorder";
10
28
  import type { InteractionEvent } from "./components/RecordingResults";
29
+ import { takeSnapshot } from "./visualDiff/snapshot";
30
+ import { computeDiff, formatReport } from "./visualDiff/diff";
31
+ import { waitForSettle } from "./visualDiff/settle";
32
+ import type { DeltaReport, ElementSnapshot } from "./visualDiff/types";
11
33
 
12
34
  export interface LocatorJSAPI {
13
35
  /**
@@ -93,6 +115,40 @@ export interface LocatorJSAPI {
93
115
  elementOrSelector: HTMLElement | string
94
116
  ): Promise<{ path: string; ancestry: AncestryItem[] } | null>;
95
117
 
118
+ /**
119
+ * Get computed styles for an element, formatted for AI consumption.
120
+ * Extracts layout, visual, typography, and interaction styles filtered against browser defaults.
121
+ * Clicking the same element twice within 30s returns a diff of changed properties.
122
+ *
123
+ * @param elementOrSelector - HTMLElement or CSS selector string
124
+ * @param options - Optional flags like { includeDefaults: true } for a fuller dump
125
+ * @returns Object with formatted string and raw snapshot, or null if element not found
126
+ *
127
+ * @example
128
+ * // Get formatted computed styles
129
+ * const result = window.__treelocator__.getStyles('button.submit');
130
+ * console.log(result.formatted);
131
+ * // [ComputedStyles] Button at src/Button.tsx:23
132
+ * // ─────────────────────────────────────────
133
+ * // Layout
134
+ * // display: flex
135
+ * // padding: 8px 16px
136
+ * // ...
137
+ *
138
+ * @example
139
+ * // In Playwright
140
+ * const styles = await page.evaluate(() => {
141
+ * return window.__treelocator__.getStyles('.my-element', {
142
+ * includeDefaults: true,
143
+ * });
144
+ * });
145
+ * console.log(styles?.formatted);
146
+ */
147
+ getStyles(
148
+ elementOrSelector: HTMLElement | string,
149
+ options?: ExtractOptions
150
+ ): ComputedStylesResult | null;
151
+
96
152
  /**
97
153
  * Display help information about the LocatorJS API.
98
154
  * Shows usage examples and method descriptions for browser automation tools.
@@ -110,6 +166,98 @@ export interface LocatorJSAPI {
110
166
  */
111
167
  help(): string;
112
168
 
169
+ /**
170
+ * Inspect all CSS rules matching an element, grouped by property.
171
+ * Shows which rule wins for each property with specificity, source, and !important info.
172
+ * Returns structured data for programmatic use.
173
+ *
174
+ * @param elementOrSelector - HTMLElement or CSS selector string
175
+ * @returns Structured CSS inspection result, or null if element not found
176
+ *
177
+ * @example
178
+ * // Get structured CSS data
179
+ * const result = window.__treelocator__.getCSSRules('button.primary');
180
+ * result.properties.forEach(p => {
181
+ * console.log(`${p.property}: ${p.value}`);
182
+ * p.rules.forEach(r => console.log(` ${r.winning ? '✓' : '✗'} ${r.selector}`));
183
+ * });
184
+ *
185
+ * @example
186
+ * // In Playwright - debug why a style isn't applying
187
+ * const css = await page.evaluate(() =>
188
+ * window.__treelocator__.getCSSRules('.my-button')
189
+ * );
190
+ * const colorRules = css?.properties.find(p => p.property === 'color');
191
+ * console.log(colorRules);
192
+ */
193
+ getCSSRules(
194
+ elementOrSelector: HTMLElement | string
195
+ ): CSSInspectionResult | null;
196
+
197
+ /**
198
+ * Get a formatted human-readable report of all CSS rules matching an element.
199
+ * Shows winning/losing rules per property with specificity and source info.
200
+ * Ideal for pasting into AI chat or logging.
201
+ *
202
+ * @param elementOrSelector - HTMLElement or CSS selector string
203
+ * @param options - Optional filter: { properties?: string[] } to limit output to specific properties
204
+ * @returns Formatted string report, or null if element not found
205
+ *
206
+ * @example
207
+ * // Get full CSS report
208
+ * console.log(window.__treelocator__.getCSSReport('button.primary'));
209
+ * // Output:
210
+ * // CSS Rules for button.primary
211
+ * // ════════════════════════════
212
+ * //
213
+ * // color: #333
214
+ * // ✓ .button.primary (0,2,0) — components.css
215
+ * // ✗ .button (0,1,0) — base.css
216
+ * // ✗ button (0,0,1) — reset.css
217
+ *
218
+ * @example
219
+ * // Filter to specific properties
220
+ * console.log(window.__treelocator__.getCSSReport('.card', { properties: ['color', 'background'] }));
221
+ *
222
+ * @example
223
+ * // In Playwright
224
+ * const report = await page.evaluate(() =>
225
+ * window.__treelocator__.getCSSReport('.error-message')
226
+ * );
227
+ * console.log(report);
228
+ */
229
+ getCSSReport(
230
+ elementOrSelector: HTMLElement | string,
231
+ options?: { properties?: string[] }
232
+ ): string | null;
233
+
234
+ /**
235
+ * Capture a baseline snapshot of an element's computed styles and persist
236
+ * it in localStorage under `snapshotId`. The baseline survives page reloads
237
+ * and is never mutated until you call `takeSnapshot` again with the same id.
238
+ *
239
+ * @param selector - CSS selector for the element to snapshot
240
+ * @param snapshotId - caller-chosen id used to retrieve the diff later
241
+ * @param options - optional `{ index, label }` (index picks among matches)
242
+ */
243
+ takeSnapshot(
244
+ selector: string,
245
+ snapshotId: string,
246
+ options?: { index?: number; label?: string }
247
+ ): TakeSnapshotResult;
248
+
249
+ /**
250
+ * Diff the current computed styles of the element against the baseline
251
+ * stored under `snapshotId`. Does not overwrite the baseline — safe to call
252
+ * repeatedly while iterating on a change.
253
+ */
254
+ getSnapshotDiff(snapshotId: string): SnapshotDiffResult;
255
+
256
+ /**
257
+ * Remove a stored baseline snapshot.
258
+ */
259
+ clearSnapshot(snapshotId: string): void;
260
+
113
261
  /**
114
262
  * Replay the last recorded interaction sequence.
115
263
  * Dispatches the recorded clicks at the original positions and timing.
@@ -155,14 +303,59 @@ export interface LocatorJSAPI {
155
303
  summary: DejitterSummary | null;
156
304
  data: any;
157
305
  interactions: InteractionEvent[];
306
+ visualDiff: DeltaReport | null;
158
307
  } | null>;
308
+
309
+ /**
310
+ * Visual diff engine — snapshot page state before/after an action and return
311
+ * a compact delta report.
312
+ *
313
+ * @example
314
+ * // In browser console or Playwright
315
+ * const report = await window.__treelocator__.diff.captureDiff(() => {
316
+ * document.querySelector('button.submit')?.click();
317
+ * });
318
+ * console.log(report.text);
319
+ */
320
+ diff: {
321
+ /**
322
+ * Capture a snapshot of all visible viewport elements right now.
323
+ * Pure — no side effects on the page.
324
+ */
325
+ snapshot(): ElementSnapshot[];
326
+
327
+ /**
328
+ * Compute the delta between two snapshots.
329
+ */
330
+ computeDiff(
331
+ before: ElementSnapshot[],
332
+ after: ElementSnapshot[]
333
+ ): DeltaReport;
334
+
335
+ /**
336
+ * Take a before-snapshot, run the action, wait for the page to settle
337
+ * (animations idle + mutations silent for 150ms), take an after-snapshot,
338
+ * and return the computed delta.
339
+ */
340
+ captureDiff(
341
+ action: () => void | Promise<void>,
342
+ opts?: { settleTimeoutMs?: number }
343
+ ): Promise<DeltaReport>;
344
+ };
159
345
  }
160
346
 
161
347
  function resolveElement(
162
348
  elementOrSelector: HTMLElement | string
163
349
  ): HTMLElement | null {
164
350
  if (typeof elementOrSelector === "string") {
165
- const element = document.querySelector(elementOrSelector);
351
+ // querySelector throws DOMException for invalid selector strings
352
+ // (e.g. "!!!") — return null instead of crashing the API call.
353
+ let element: Element | null = null;
354
+ try {
355
+ element = document.querySelector(elementOrSelector);
356
+ } catch {
357
+ return null;
358
+ }
166
359
  return element instanceof HTMLElement ? element : null;
167
360
  }
168
361
  return elementOrSelector;
@@ -227,13 +420,52 @@ METHODS:
227
420
  console.log(data.path) // formatted string
228
421
  console.log(data.ancestry) // structured array
229
422
 
230
- 4. replay()
423
+ 4. getStyles(elementOrSelector, options?)
424
+ Returns computed styles for an element, optimized for AI consumption.
425
+ Filters out browser defaults and groups by category (Layout, Visual, Typography).
426
+ Pass { includeDefaults: true } for a fuller dump closer to DevTools.
427
+ Calling twice on the same element within 30s returns a diff of changes.
428
+
429
+ Usage:
430
+ const result = window.__treelocator__.getStyles('button.submit')
431
+ console.log(result.formatted) // formatted styles string
432
+ console.log(result.snapshot) // raw property values + bounding rect
433
+ const full = window.__treelocator__.getStyles('h1', { includeDefaults: true })
434
+
435
+ 5. getCSSRules(elementOrSelector)
436
+ Returns structured CSS rule data for the element.
437
+ Shows all matching rules grouped by property with specificity and source.
438
+
439
+ Usage:
440
+ const result = window.__treelocator__.getCSSRules('button.primary')
441
+ result.properties.forEach(p => {
442
+ console.log(p.property + ': ' + p.value)
443
+ p.rules.forEach(r => console.log(' ' + (r.winning ? 'WIN' : ' ') + ' ' + r.selector))
444
+ })
445
+
446
+ 6. getCSSReport(elementOrSelector, options?)
447
+ Returns a formatted string showing all CSS rules and which wins per property.
448
+ Pass { properties: ['color', 'font-size'] } to filter to specific properties.
449
+
450
+ Usage:
451
+ console.log(window.__treelocator__.getCSSReport('button.primary'))
452
+ console.log(window.__treelocator__.getCSSReport('.card', { properties: ['color'] }))
453
+
454
+ Returns:
455
+ "CSS Rules for button.primary
456
+ ════════════════════════════
457
+ color: #333
458
+ ✓ .button.primary (0,2,0) — components.css
459
+ ✗ .button (0,1,0) — base.css
460
+ ✗ button (0,0,1) — reset.css"
461
+
462
+ 7. replay()
231
463
  Replays the last recorded interaction sequence as a macro.
232
464
 
233
465
  Usage:
234
466
  window.__treelocator__.replay()
235
467
 
236
- 5. replayWithRecord(elementOrSelector)
468
+ 8. replayWithRecord(elementOrSelector)
237
469
  Replays stored interactions while recording element changes.
238
470
  Returns dejitter analysis when replay completes.
239
471
 
@@ -242,7 +474,17 @@ METHODS:
242
474
  console.log(results.findings) // anomaly analysis
243
475
  console.log(results.path) // component ancestry
244
476
 
245
- 6. help()
477
+ 9. diff.snapshot() / diff.computeDiff(before, after) / diff.captureDiff(action)
478
+ Visual diff engine. Captures viewport element state and returns a compact
479
+ delta showing what appeared, disappeared, moved, or changed.
480
+
481
+ Usage:
482
+ const report = await window.__treelocator__.diff.captureDiff(() => {
483
+ document.querySelector('button.submit').click();
484
+ });
485
+ console.log(report.text);
486
+
487
+ 10. help()
246
488
  Displays this help message.
247
489
 
248
490
  PLAYWRIGHT EXAMPLES:
@@ -267,6 +509,19 @@ async function getComponentPath(page, selector) {
267
509
  }, selector);
268
510
  }
269
511
 
512
+ // Debug CSS specificity conflicts
513
+ const report = await page.evaluate(() => {
514
+ return window.__treelocator__.getCSSReport('.my-button', { properties: ['color', 'background'] });
515
+ });
516
+ console.log(report);
517
+
518
+ // Get structured CSS data for assertions
519
+ const css = await page.evaluate(() => {
520
+ return window.__treelocator__.getCSSRules('.my-button');
521
+ });
522
+ const colorRules = css?.properties.find(p => p.property === 'color');
523
+ console.log('Winning rule:', colorRules?.rules.find(r => r.winning));
524
+
270
525
  PUPPETEER EXAMPLES:
271
526
  ------------------
272
527
 
@@ -338,6 +593,74 @@ export function createBrowserAPI(
338
593
  );
339
594
  },
340
595
 
596
+ getStyles(
597
+ elementOrSelector: HTMLElement | string,
598
+ options?: ExtractOptions
599
+ ): ComputedStylesResult | null {
600
+ const element = resolveElement(elementOrSelector);
601
+ if (!element) return null;
602
+
603
+ const ancestry = getAncestryForElement(element, adapterId);
604
+ const label = ancestry ? getElementLabel(ancestry) : undefined;
605
+
606
+ return extractComputedStyles(element, label || undefined, options);
607
+ },
608
+
609
+ getCSSRules(
610
+ elementOrSelector: HTMLElement | string
611
+ ): CSSInspectionResult | null {
612
+ let element: HTMLElement | null = null;
613
+ try {
614
+ element = resolveElement(elementOrSelector);
615
+ } catch {
616
+ return null;
617
+ }
618
+ if (!element) return null;
619
+ return inspectCSSRules(element);
620
+ },
621
+
622
+ getCSSReport(
623
+ elementOrSelector: HTMLElement | string,
624
+ options?: { properties?: string[] }
625
+ ): string | null {
626
+ let element: HTMLElement | null = null;
627
+ try {
628
+ element = resolveElement(elementOrSelector);
629
+ } catch {
630
+ return null;
631
+ }
632
+ if (!element) return null;
633
+ const result = inspectCSSRules(element);
634
+
635
+ // Filter to requested properties if specified
636
+ if (options?.properties && options.properties.length > 0) {
637
+ const filterSet = new Set(
638
+ options.properties.map((p) => p.toLowerCase())
639
+ );
640
+ result.properties = result.properties.filter((p) =>
641
+ filterSet.has(p.property.toLowerCase())
642
+ );
643
+ }
644
+
645
+ return formatCSSInspection(result);
646
+ },
647
+
648
+ takeSnapshot(
649
+ selector: string,
650
+ snapshotId: string,
651
+ options?: { index?: number; label?: string }
652
+ ): TakeSnapshotResult {
653
+ return takeNamedSnapshot(selector, snapshotId, options);
654
+ },
655
+
656
+ getSnapshotDiff(snapshotId: string): SnapshotDiffResult {
657
+ return getNamedSnapshotDiff(snapshotId);
658
+ },
659
+
660
+ clearSnapshot(snapshotId: string): void {
661
+ clearNamedSnapshot(snapshotId);
662
+ },
663
+
341
664
  help(): string {
342
665
  return HELP_TEXT;
343
666
  },
@@ -350,6 +673,30 @@ export function createBrowserAPI(
350
673
  // Replaced by Runtime component once mounted
351
674
  return Promise.resolve(null);
352
675
  },
676
+
677
+ diff: {
678
+ snapshot() {
679
+ return takeSnapshot();
680
+ },
681
+ computeDiff(before, after) {
682
+ return computeDiff(before, after);
683
+ },
684
+ async captureDiff(action, opts) {
685
+ const started = performance.now();
686
+ const before = takeSnapshot();
687
+ await action();
688
+ const settle = await waitForSettle(opts?.settleTimeoutMs);
689
+ const after = takeSnapshot();
690
+ const report = computeDiff(before, after);
691
+ report.elapsedMs = performance.now() - started;
692
+ report.settle = settle;
693
+ report.text = formatReport(report.entries, {
694
+ elapsedMs: report.elapsedMs,
695
+ settle,
696
+ });
697
+ return report;
698
+ },
699
+ },
353
700
  };
354
701
  }
355
702