explorbot 0.4.0 → 0.4.2

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 (177) hide show
  1. package/bin/explorbot-cli.ts +13 -6
  2. package/boat/api-tester/src/ai/chief.ts +7 -1
  3. package/boat/api-tester/src/ai/curler-tools.ts +1 -1
  4. package/boat/api-tester/src/ai/curler.ts +7 -1
  5. package/boat/api-tester/src/apibot.ts +10 -4
  6. package/boat/api-tester/src/cli.ts +15 -2
  7. package/boat/api-tester/src/config.ts +28 -8
  8. package/boat/doc-collector/bin/doc-collector-cli.ts +3 -2
  9. package/boat/doc-collector/src/ai/documentarian.ts +57 -5
  10. package/boat/doc-collector/src/ai/tools.ts +1 -1
  11. package/boat/doc-collector/src/cli.ts +21 -3
  12. package/boat/doc-collector/src/config.ts +3 -0
  13. package/boat/doc-collector/src/docbot.ts +57 -12
  14. package/boat/doc-collector/src/docs-renderer.ts +79 -55
  15. package/boat/doc-collector/src/interaction-screenshots.ts +19 -10
  16. package/boat/doc-collector/src/screenshots.ts +106 -3
  17. package/boat/doc-collector/src/template-dedup.ts +31 -0
  18. package/boat/prima/bin/prima-cli.ts +2 -0
  19. package/boat/prima/src/cli.ts +5 -0
  20. package/boat/prima/src/prima.ts +3 -4
  21. package/dist/bin/explorbot-cli.js +14 -6
  22. package/dist/boat/api-tester/bin/apibot-cli.js +3 -2
  23. package/dist/boat/api-tester/src/ai/chief.js +6 -1
  24. package/dist/boat/api-tester/src/ai/curler-tools.js +1 -1
  25. package/dist/boat/api-tester/src/ai/curler.js +6 -1
  26. package/dist/boat/api-tester/src/apibot.js +7 -3
  27. package/dist/boat/api-tester/src/cli.js +14 -2
  28. package/dist/boat/api-tester/src/config.js +31 -8
  29. package/dist/boat/doc-collector/bin/doc-collector-cli.js +3 -2
  30. package/dist/boat/doc-collector/src/ai/documentarian.js +62 -4
  31. package/dist/boat/doc-collector/src/ai/tools.js +1 -1
  32. package/dist/boat/doc-collector/src/cli.js +18 -3
  33. package/dist/boat/doc-collector/src/config.js +1 -0
  34. package/dist/boat/doc-collector/src/docbot.js +53 -11
  35. package/dist/boat/doc-collector/src/docs-renderer.js +69 -46
  36. package/dist/boat/doc-collector/src/interaction-screenshots.js +18 -11
  37. package/dist/boat/doc-collector/src/screenshots.js +104 -1
  38. package/dist/boat/doc-collector/src/template-dedup.js +27 -0
  39. package/dist/boat/prima/bin/prima-cli.js +2 -0
  40. package/dist/boat/prima/src/cli.js +5 -0
  41. package/dist/boat/prima/src/prima.js +3 -4
  42. package/dist/package.json +1 -1
  43. package/dist/rules/planner/styles/normal.md +1 -1
  44. package/dist/src/action-result.d.ts +2 -2
  45. package/dist/src/action-result.js +15 -17
  46. package/dist/src/action.d.ts +1 -1
  47. package/dist/src/action.js +15 -15
  48. package/dist/src/ai/captain.js +1 -1
  49. package/dist/src/ai/fisherman/request-haul.d.ts +11 -0
  50. package/dist/src/ai/fisherman/request-haul.js +28 -0
  51. package/dist/src/ai/fisherman-tools.d.ts +10 -1
  52. package/dist/src/ai/fisherman-tools.js +35 -31
  53. package/dist/src/ai/fisherman.d.ts +3 -2
  54. package/dist/src/ai/fisherman.js +8 -6
  55. package/dist/src/ai/navigator.js +1 -1
  56. package/dist/src/ai/pilot.js +21 -16
  57. package/dist/src/ai/planner.js +9 -7
  58. package/dist/src/ai/provider.js +8 -14
  59. package/dist/src/ai/researcher/coordinates.d.ts +2 -0
  60. package/dist/src/ai/researcher/coordinates.js +13 -1
  61. package/dist/src/ai/researcher/deep-analysis.js +9 -9
  62. package/dist/src/ai/researcher/fingerprint-worker.js +1 -11
  63. package/dist/src/ai/researcher.js +3 -1
  64. package/dist/src/ai/rules.js +11 -3
  65. package/dist/src/ai/tester.js +20 -20
  66. package/dist/src/ai/tools.d.ts +1 -1
  67. package/dist/src/ai/tools.js +18 -74
  68. package/dist/src/api/request-store.d.ts +1 -5
  69. package/dist/src/api/request-store.js +12 -22
  70. package/dist/src/api/spec-reader.js +1 -1
  71. package/dist/src/commands/config-command.js +3 -10
  72. package/dist/src/commands/drill-command.js +1 -1
  73. package/dist/src/commands/explore-command.js +12 -1
  74. package/dist/src/commands/index.js +2 -0
  75. package/dist/src/commands/init-command.js +3 -7
  76. package/dist/src/commands/options/base-option.d.ts +8 -0
  77. package/dist/src/commands/options/base-option.js +12 -0
  78. package/dist/src/commands/options/index.d.ts +5 -0
  79. package/dist/src/commands/options/index.js +5 -0
  80. package/dist/src/commands/options/knowledge-option.d.ts +7 -0
  81. package/dist/src/commands/options/knowledge-option.js +12 -0
  82. package/dist/src/commands/options/ws-option.d.ts +7 -0
  83. package/dist/src/commands/options/ws-option.js +21 -0
  84. package/dist/src/commands/plans-command.d.ts +4 -1
  85. package/dist/src/commands/plans-command.js +4 -4
  86. package/dist/src/commands/recommended-models-command.d.ts +9 -0
  87. package/dist/src/commands/recommended-models-command.js +85 -0
  88. package/dist/src/config.d.ts +3 -0
  89. package/dist/src/config.js +18 -2
  90. package/dist/src/experience-tracker.js +1 -1
  91. package/dist/src/explorbot.js +4 -4
  92. package/dist/src/global-config.d.ts +2 -0
  93. package/dist/src/global-config.js +6 -0
  94. package/dist/src/knowledge-tracker.d.ts +20 -7
  95. package/dist/src/knowledge-tracker.js +69 -31
  96. package/dist/src/remote.d.ts +0 -3
  97. package/dist/src/remote.js +0 -18
  98. package/dist/src/state-manager.d.ts +3 -3
  99. package/dist/src/state-manager.js +8 -8
  100. package/dist/src/test-plan.d.ts +1 -0
  101. package/dist/src/test-plan.js +13 -9
  102. package/dist/src/utils/aria.d.ts +1 -1
  103. package/dist/src/utils/aria.js +23 -2
  104. package/dist/src/utils/cli-section.d.ts +1 -0
  105. package/dist/src/utils/cli-section.js +12 -0
  106. package/dist/src/utils/html-diff.d.ts +1 -0
  107. package/dist/src/utils/html-diff.js +5 -5
  108. package/dist/src/utils/html.d.ts +2 -0
  109. package/dist/src/utils/html.js +5 -4
  110. package/dist/src/utils/overlay.d.ts +19 -55
  111. package/dist/src/utils/overlay.js +94 -148
  112. package/dist/src/utils/region.d.ts +41 -0
  113. package/dist/src/utils/region.js +99 -0
  114. package/dist/src/utils/similarity.d.ts +1 -0
  115. package/dist/src/utils/similarity.js +11 -0
  116. package/dist/src/utils/url-matcher.d.ts +1 -1
  117. package/dist/src/utils/url-matcher.js +4 -4
  118. package/docs/api-testing/basics.md +15 -0
  119. package/docs/api-testing/planning.md +10 -1
  120. package/docs/doc-collection/basics.md +11 -5
  121. package/docs/doc-collection/crawling.md +21 -1
  122. package/docs/reference/commands.md +27 -4
  123. package/docs/superpowers/plans/2026-09-03-model-resolves-click-ambiguity.md +509 -0
  124. package/docs/workflow/agentic-usage.md +11 -2
  125. package/docs/workflow/application-spec.md +4 -0
  126. package/docs/workflow/knowledge.md +46 -2
  127. package/package.json +1 -1
  128. package/rules/planner/styles/normal.md +1 -1
  129. package/src/action-result.ts +15 -16
  130. package/src/action.ts +16 -15
  131. package/src/ai/captain.ts +1 -1
  132. package/src/ai/fisherman/request-haul.ts +32 -0
  133. package/src/ai/fisherman-tools.ts +38 -32
  134. package/src/ai/fisherman.ts +8 -7
  135. package/src/ai/navigator.ts +1 -1
  136. package/src/ai/pilot.ts +19 -14
  137. package/src/ai/planner.ts +9 -8
  138. package/src/ai/provider.ts +8 -12
  139. package/src/ai/researcher/coordinates.ts +13 -1
  140. package/src/ai/researcher/deep-analysis.ts +9 -9
  141. package/src/ai/researcher/fingerprint-worker.ts +1 -10
  142. package/src/ai/researcher.ts +2 -1
  143. package/src/ai/rules.ts +11 -3
  144. package/src/ai/tester.ts +18 -18
  145. package/src/ai/tools.ts +18 -86
  146. package/src/api/request-store.ts +13 -27
  147. package/src/api/spec-reader.ts +1 -1
  148. package/src/commands/config-command.ts +3 -8
  149. package/src/commands/drill-command.ts +1 -1
  150. package/src/commands/explore-command.ts +12 -1
  151. package/src/commands/index.ts +2 -0
  152. package/src/commands/init-command.ts +4 -11
  153. package/src/commands/options/base-option.ts +18 -0
  154. package/src/commands/options/index.ts +7 -0
  155. package/src/commands/options/knowledge-option.ts +14 -0
  156. package/src/commands/options/ws-option.ts +24 -0
  157. package/src/commands/plans-command.ts +5 -5
  158. package/src/commands/recommended-models-command.ts +92 -0
  159. package/src/config.ts +20 -2
  160. package/src/experience-tracker.ts +1 -1
  161. package/src/explorbot.ts +4 -4
  162. package/src/global-config.ts +8 -0
  163. package/src/knowledge-tracker.ts +94 -36
  164. package/src/remote.ts +0 -20
  165. package/src/state-manager.ts +10 -9
  166. package/src/test-plan.ts +12 -10
  167. package/src/utils/aria.ts +26 -3
  168. package/src/utils/cli-section.ts +12 -0
  169. package/src/utils/html-diff.ts +6 -6
  170. package/src/utils/html.ts +6 -4
  171. package/src/utils/overlay.ts +105 -192
  172. package/src/utils/region.ts +132 -0
  173. package/src/utils/similarity.ts +9 -0
  174. package/src/utils/url-matcher.ts +3 -3
  175. package/dist/src/utils/request-map.d.ts +0 -7
  176. package/dist/src/utils/request-map.js +0 -16
  177. package/src/utils/request-map.ts +0 -19
@@ -26,15 +26,6 @@ export class Remote {
26
26
  asks = new Map();
27
27
  askCounter = 0;
28
28
  lastActivity = null;
29
- registerOption(program) {
30
- program.option('--ws <url>', 'Stream this run to a remote UI over WebSocket');
31
- program.hook('preAction', (_thisCommand, actionCommand) => {
32
- const url = actionCommand.optsWithGlobals().ws || process.env.EXPLORBOT_WS_URL;
33
- if (!url)
34
- return;
35
- this.attach(String(url), this.commandPath(actionCommand));
36
- });
37
- }
38
29
  attach(url, command) {
39
30
  if (this.url)
40
31
  return;
@@ -222,14 +213,5 @@ export class Remote {
222
213
  return failure.message;
223
214
  return undefined;
224
215
  }
225
- commandPath(command) {
226
- const parts = [];
227
- let node = command;
228
- while (node) {
229
- parts.unshift(node.name());
230
- node = node.parent;
231
- }
232
- return parts.slice(1).join(' ') || parts.join(' ');
233
- }
234
216
  }
235
217
  export const remote = new Remote();
@@ -1,7 +1,7 @@
1
1
  import { ActionResult, type FocusedElement } from './action-result.js';
2
2
  import type { ExperienceTracker } from './experience-tracker.js';
3
3
  import type { Knowledge, KnowledgeTracker } from './knowledge-tracker.js';
4
- import { Overlay } from './utils/overlay.js';
4
+ import type { Region } from './utils/region.js';
5
5
  export interface Link {
6
6
  title: string;
7
7
  url: string;
@@ -41,7 +41,7 @@ export interface WebPageState {
41
41
  focusedElement?: FocusedElement | null;
42
42
  links?: Link[];
43
43
  verifications?: Record<string, boolean>;
44
- overlay?: Overlay;
44
+ overlay?: Region;
45
45
  /** Region name, the persisted scalar form of overlay.name used in experience frontmatter */
46
46
  region?: string;
47
47
  /** Region root selector, the persisted scalar form of overlay.root used in experience frontmatter */
@@ -95,7 +95,7 @@ export declare class StateManager {
95
95
  * Update state from basic data (for navigation events)
96
96
  */
97
97
  updateStateFromBasic(url: string, title?: string, trigger?: 'manual' | 'navigation' | 'automatic'): WebPageState;
98
- hasRegionAppeared(previousState: WebPageState | null, newState: WebPageState): boolean;
98
+ regionOpened(previousState: WebPageState | null, newState: WebPageState): boolean;
99
99
  /**
100
100
  * Generate a basic hash for state comparison
101
101
  */
@@ -42,7 +42,7 @@ export class StateManager {
42
42
  emitStateChange(event) {
43
43
  const state = event.toState;
44
44
  const payload = { url: state.fullUrl || state.url, path: state.url, title: state.title, h1: state.h1 };
45
- if (state.overlay?.present)
45
+ if (state.overlay?.isOpen)
46
46
  payload.region = state.overlay.name || state.overlay.type;
47
47
  tag('data').log('state', payload);
48
48
  this.stateChangeListeners.forEach((listener) => {
@@ -66,8 +66,8 @@ export class StateManager {
66
66
  if (newState.url)
67
67
  this.allVisitedUrls.add(normalizeUrl(newState.url));
68
68
  const hashChanged = actionResult.hash !== previousHash;
69
- const regionAppeared = !hashChanged && this.hasRegionAppeared(previousState, newState);
70
- if (hashChanged || regionAppeared) {
69
+ const regionOpened = !hashChanged && this.regionOpened(previousState, newState);
70
+ if (hashChanged || regionOpened) {
71
71
  const transition = {
72
72
  fromState: previousState,
73
73
  toState: newState,
@@ -77,7 +77,7 @@ export class StateManager {
77
77
  };
78
78
  this.stateHistory.push(transition);
79
79
  this.emitStateChange(transition);
80
- if (regionAppeared) {
80
+ if (regionOpened) {
81
81
  debugLog('State change detected: region of interest appeared');
82
82
  }
83
83
  }
@@ -116,10 +116,10 @@ export class StateManager {
116
116
  debugLog(`State updated from navigation: ${this.currentState.url} (${this.currentState.hash})`);
117
117
  return newState;
118
118
  }
119
- hasRegionAppeared(previousState, newState) {
120
- const prevFocus = previousState?.overlay ?? Overlay.fromAria(previousState?.ariaSnapshot ?? null);
121
- const newFocus = newState.overlay ?? Overlay.fromAria(newState.ariaSnapshot ?? null);
122
- return !prevFocus.present && newFocus.present;
119
+ regionOpened(previousState, newState) {
120
+ const previous = Overlay.resolve(previousState ?? {});
121
+ const current = Overlay.resolve(newState);
122
+ return !previous.isOpen && current.isOpen;
123
123
  }
124
124
  /**
125
125
  * Generate a basic hash for state comparison
@@ -147,6 +147,7 @@ export declare class Plan {
147
147
  get allSuccessful(): boolean;
148
148
  get allFailed(): boolean;
149
149
  updateStatus(): void;
150
+ static resolveFile(file: string, plansDir?: string): string | null;
150
151
  static loadFromFile(file: string, plansDir?: string): Plan | null;
151
152
  static fromMarkdown(filePath: string): Plan;
152
153
  saveToMarkdown(filePath: string): void;
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { existsSync } from 'node:fs';
3
3
  import path from 'node:path';
4
- import { listSites } from "./global-config.js";
4
+ import { listSitePlanDirs } from "./global-config.js";
5
5
  import { tag } from "./utils/logger.js";
6
6
  import { parsePlanFromMarkdown, planToAiContext, savePlanToMarkdown, savePlansToMarkdown } from "./utils/test-plan-markdown.js";
7
7
  import { uniqSessionName } from "./utils/unique-names.js";
@@ -323,7 +323,6 @@ export class Test extends Task {
323
323
  .sort((a, b) => a.timestamp - b.timestamp);
324
324
  }
325
325
  }
326
- const SITE_PLANS_DIR = ['output', 'plans'];
327
326
  export class Plan {
328
327
  title;
329
328
  tests = [];
@@ -398,7 +397,7 @@ export class Plan {
398
397
  return this.tests.length > 0 && this.tests.every((test) => test.hasFailed);
399
398
  }
400
399
  updateStatus() { }
401
- static loadFromFile(file, plansDir) {
400
+ static resolveFile(file, plansDir) {
402
401
  const names = [file];
403
402
  if (!file.endsWith('.md'))
404
403
  names.push(`${file}.md`);
@@ -406,17 +405,22 @@ export class Plan {
406
405
  if (plansDir)
407
406
  dirs.push(plansDir);
408
407
  if (!plansDir)
409
- dirs.push(...listSites().map((site) => path.join(site.dir, ...SITE_PLANS_DIR)));
408
+ dirs.push(...listSitePlanDirs());
410
409
  for (const dir of dirs) {
411
410
  const filePath = names.map((name) => path.resolve(dir, name)).find(existsSync);
412
- if (!filePath)
413
- continue;
414
- const loaded = parsePlanFromMarkdown(filePath);
415
- loaded.filePath = filePath;
416
- return loaded;
411
+ if (filePath)
412
+ return filePath;
417
413
  }
418
414
  return null;
419
415
  }
416
+ static loadFromFile(file, plansDir) {
417
+ const filePath = Plan.resolveFile(file, plansDir);
418
+ if (!filePath)
419
+ return null;
420
+ const loaded = parsePlanFromMarkdown(filePath);
421
+ loaded.filePath = filePath;
422
+ return loaded;
423
+ }
420
424
  static fromMarkdown(filePath) {
421
425
  return parsePlanFromMarkdown(filePath);
422
426
  }
@@ -1,5 +1,4 @@
1
1
  export interface FocusAreaResult {
2
- detected: boolean;
3
2
  type: 'modal' | null;
4
3
  name: string | null;
5
4
  }
@@ -8,6 +7,7 @@ export declare const compactAriaSnapshot: (snapshot: string | null, keepNamed?:
8
7
  export declare const diffAriaSnapshots: (previous: string | null, current: string | null) => AriaDiff;
9
8
  export declare const detectFocusArea: (snapshot: string | null) => FocusAreaResult;
10
9
  export declare const collectInteractiveNodes: (snapshot: string | null) => Array<Record<string, unknown>>;
10
+ export declare const ariaTemplateSignature: (snapshot: string | null) => Set<string>;
11
11
  export declare function parseAriaLocator(ariaStr: string): {
12
12
  role: string;
13
13
  text: string;
@@ -473,7 +473,7 @@ const findDialogOrModal = (nodes) => {
473
473
  let topmost = null;
474
474
  for (const node of nodes) {
475
475
  if (node.role === 'dialog' || node.role === 'alertdialog' || node.attributes.modal === true || node.attributes.modal === 'true') {
476
- topmost = { detected: true, type: 'modal', name: node.name || null };
476
+ topmost = { type: 'modal', name: node.name || null };
477
477
  }
478
478
  const child = findDialogOrModal(node.children);
479
479
  if (child)
@@ -517,7 +517,7 @@ export const detectFocusArea = (snapshot) => {
517
517
  let tree = parseSnapshot(snapshot);
518
518
  tree = unwrapIgnored(tree);
519
519
  tree = dropEmpty(tree, { keepNamed: true });
520
- return findDialogOrModal(tree) ?? { detected: false, type: null, name: null };
520
+ return findDialogOrModal(tree) ?? { type: null, name: null };
521
521
  };
522
522
  export const collectInteractiveNodes = (snapshot) => {
523
523
  let tree = parseSnapshot(snapshot);
@@ -526,6 +526,27 @@ export const collectInteractiveNodes = (snapshot) => {
526
526
  tree = dropEmpty(tree);
527
527
  return flatten(tree).map((e) => e.entry);
528
528
  };
529
+ const TEMPLATE_CHROME_ROLES = new Set(['banner', 'navigation', 'contentinfo', 'complementary']);
530
+ const TEMPLATE_PROSE_ROLE = 'text';
531
+ const dropChrome = (nodes) => nodes.flatMap((node) => {
532
+ if (TEMPLATE_CHROME_ROLES.has(node.role))
533
+ return [];
534
+ return [{ ...node, children: dropChrome(node.children) }];
535
+ });
536
+ const collectRolePaths = (nodes, prefix, into) => {
537
+ for (const node of nodes) {
538
+ if (node.role === TEMPLATE_PROSE_ROLE)
539
+ continue;
540
+ const path = prefix ? `${prefix}/${node.role}` : node.role;
541
+ into.add(path);
542
+ collectRolePaths(node.children, path, into);
543
+ }
544
+ };
545
+ export const ariaTemplateSignature = (snapshot) => {
546
+ const paths = new Set();
547
+ collectRolePaths(dropChrome(parseSnapshot(snapshot)), '', paths);
548
+ return paths;
549
+ };
529
550
  // ─────────────────────────────────────────────────────────────────
530
551
  // Standalone helpers (regex on raw strings — not part of the pipeline)
531
552
  // ─────────────────────────────────────────────────────────────────
@@ -0,0 +1 @@
1
+ export declare function renderSection(title: string, entries: [string, string][], indent?: number): string[];
@@ -0,0 +1,12 @@
1
+ import chalk from 'chalk';
2
+ export function renderSection(title, entries, indent = 0) {
3
+ if (!entries.length)
4
+ return [];
5
+ const pad = ' '.repeat(indent);
6
+ const width = Math.max(...entries.map(([label]) => label.length));
7
+ const lines = [`${pad}${chalk.bold(title)}`];
8
+ for (const [label, value] of entries)
9
+ lines.push(`${pad} ${chalk.dim(label.padEnd(width))} ${value}`);
10
+ lines.push('');
11
+ return lines;
12
+ }
@@ -6,6 +6,7 @@ export interface HtmlDiffPart {
6
6
  added: string[];
7
7
  removed: string[];
8
8
  appearedSelector?: string;
9
+ containerWasEmpty?: boolean;
9
10
  }
10
11
  export interface HtmlDiffResult {
11
12
  parts: HtmlDiffPart[];
@@ -1,5 +1,5 @@
1
1
  import { parse, serialize } from 'parse5';
2
- import { TAILWIND_CLASS_PATTERNS, TRASH_HTML_CLASSES, minifyHtml } from "./html.js";
2
+ import { isTailwindClass, isTrashClass, minifyHtml } from "./html.js";
3
3
  import { isDynamicId, isGenericClass } from "./xpath.js";
4
4
  const IGNORED_PATHS = new Set(['html[1]', 'html[1]/head[1]', 'html[1]/body[1]']);
5
5
  const SHELL_RATIO = 0.8;
@@ -364,14 +364,13 @@ const directTextDiffer = (current, previous) => {
364
364
  }
365
365
  return false;
366
366
  };
367
- const trashHtmlClasses = TRASH_HTML_CLASSES;
368
367
  function filterContainerClasses(classes) {
369
368
  return classes
370
369
  .filter((cls) => !/\d/.test(cls))
371
370
  .filter((cls) => !isGenericClass(cls))
372
- .filter((cls) => !trashHtmlClasses.test(cls))
371
+ .filter((cls) => !isTrashClass(cls))
373
372
  .filter((cls) => !/(:|__)/.test(cls))
374
- .filter((cls) => !TAILWIND_CLASS_PATTERNS.some((pattern) => pattern.test(cls)));
373
+ .filter((cls) => !isTailwindClass(cls));
375
374
  }
376
375
  function buildContainerSelector(element, allElements) {
377
376
  const tag = element.tagName?.toLowerCase();
@@ -529,7 +528,8 @@ async function buildDiffParts(originalMap, modifiedMap, pageSize) {
529
528
  appearedPaths.sort((a, b) => sizeOf(b) - sizeOf(a));
530
529
  const addedLines = appearedPaths.map((p) => `ELEMENT:${p}`);
531
530
  const removedLines = [];
532
- const part = { container: selector, subtree, rawSize: serialized.length, added: addedLines, removed: removedLines };
531
+ const heldContentBefore = (originalMap.get(containerPath)?.childNodes ?? []).some((child) => 'tagName' in child && !!child.tagName);
532
+ const part = { container: selector, subtree, rawSize: serialized.length, added: addedLines, removed: removedLines, containerWasEmpty: !heldContentBefore };
533
533
  const appearedPath = appearedPaths[0];
534
534
  if (appearedPath) {
535
535
  const appearedElement = modifiedMap.get(appearedPath);
@@ -76,6 +76,8 @@ export declare function extractComponentScopeHtml(eidx: string, config: Componen
76
76
  export declare function getComponentScopeHtmlExtractorSource(): string;
77
77
  export declare const TRASH_HTML_CLASSES: RegExp;
78
78
  export declare const TAILWIND_CLASS_PATTERNS: RegExp[];
79
+ export declare const isTailwindClass: (cls: string) => boolean;
80
+ export declare const isTrashClass: (cls: string) => boolean;
79
81
  export declare function sanitizeHtmlDocument(html: string, htmlConfig?: HtmlConfig): parse5TreeAdapter.Document;
80
82
  export declare function sanitizeHtmlString(html: string, htmlConfig?: HtmlConfig): string;
81
83
  /**
@@ -509,6 +509,8 @@ export const TAILWIND_CLASS_PATTERNS = [
509
509
  /^aspect-/i,
510
510
  /^prose$/i,
511
511
  ];
512
+ export const isTailwindClass = (cls) => TAILWIND_CLASS_PATTERNS.some((pattern) => pattern.test(cls));
513
+ export const isTrashClass = (cls) => TRASH_HTML_CLASSES.test(cls);
512
514
  const NON_SEMANTIC_TAGS = new Set([
513
515
  'style',
514
516
  'script',
@@ -688,7 +690,6 @@ export function sanitizeHtmlString(html, htmlConfig) {
688
690
  */
689
691
  export function htmlMinimalUISnapshot(html, htmlConfig) {
690
692
  const document = parse(html);
691
- const trashHtmlClasses = TRASH_HTML_CLASSES;
692
693
  const removeElements = ['path', 'script'];
693
694
  function isFilteredOut(node) {
694
695
  // Check exclude selectors first
@@ -790,9 +791,9 @@ export function htmlMinimalUISnapshot(html, htmlConfig) {
790
791
  attr.value = value
791
792
  .split(' ')
792
793
  .filter((className) => !/\d/.test(className))
793
- .filter((className) => !className.match(trashHtmlClasses))
794
+ .filter((className) => !isTrashClass(className))
794
795
  .filter((className) => !className.match(/(:|__)/))
795
- .filter((className) => !TAILWIND_CLASS_PATTERNS.some((pattern) => pattern.test(className)))
796
+ .filter((className) => !isTailwindClass(className))
796
797
  .join(' ');
797
798
  if (attr.value === '')
798
799
  return false;
@@ -1312,7 +1313,7 @@ function cleanElement(element) {
1312
1313
  .filter(Boolean)
1313
1314
  .filter((className) => !/\d/.test(className))
1314
1315
  .filter((className) => !className.includes(':'))
1315
- .filter((className) => !TAILWIND_CLASS_PATTERNS.some((pattern) => pattern.test(className)))
1316
+ .filter((className) => !isTailwindClass(className))
1316
1317
  .join(' ');
1317
1318
  if (!attr.value) {
1318
1319
  element.attrs = element.attrs.filter((a) => a.name !== 'class');
@@ -1,31 +1,13 @@
1
- import type { HtmlDiffPart } from './html-diff.js';
2
- export type OverlayType = 'modal' | 'region';
3
- export type OverlayData = {
4
- type?: OverlayType | null;
5
- name?: string | null;
6
- root?: string | null;
7
- html?: string | null;
8
- xpath?: string | null;
9
- parent?: OverlayData | null;
10
- };
11
- export declare class Overlay {
12
- readonly type: OverlayType | null;
13
- readonly name: string | null;
14
- readonly root: string | null;
15
- readonly html: string | null;
16
- readonly xpath: string | null;
17
- readonly parent: OverlayData | null;
18
- constructor(data?: OverlayData);
19
- get detected(): boolean;
20
- get present(): boolean;
21
- describe(): string;
22
- withGeometry(geometry: Overlay): Overlay;
23
- withParent(parent: Overlay): Overlay;
24
- static fromAria(snapshot: string | null): Overlay;
1
+ import { Region, type RegionData, type RegionDiff } from './region.js';
2
+ export declare class Overlay extends Region {
3
+ constructor(data?: RegionData);
4
+ get isModal(): boolean;
5
+ withGeometry(geometry: Region): Overlay;
6
+ static fromAria(snapshot: string | null): Region;
25
7
  static resolve(data: {
26
- overlay?: OverlayData | null;
8
+ overlay?: RegionData | null;
27
9
  ariaSnapshot?: string | null;
28
- }): Overlay;
10
+ }): Region;
29
11
  }
30
12
  export declare class OverlayPage {
31
13
  page: {
@@ -34,36 +16,18 @@ export declare class OverlayPage {
34
16
  constructor(page: {
35
17
  evaluate(fn: any, arg: any): Promise<any>;
36
18
  } | null);
37
- detectRegion(diff: RegionDiff): Promise<Overlay | null>;
38
- isStillOpen(overlay: Overlay): Promise<boolean>;
39
- appearedSubRoots(diff: RegionDiff): AppearedSubRoot[];
40
- probe(xpath: string): Promise<RegionProbe | null>;
41
- toOverlay(subRoot: AppearedSubRoot, probe: RegionProbe | null, previousHtml: string): Overlay;
42
- nameFrom(html: string, previousHtml: string): string | null;
43
- freshHeading(html: string, previousHtml: string): string | null;
44
- headingsOf(html: string): string[];
19
+ detectRegion(diff: RegionDiff): Promise<Region | null>;
20
+ isStillOpen(region: Region): Promise<boolean>;
21
+ measure(xpath: string): Promise<RegionLayout | null>;
45
22
  }
46
- export interface RegionDiff {
47
- parts: HtmlDiffPart[];
48
- pageSize: number;
49
- similarity: number;
50
- sameUrl: boolean;
51
- previousHtml: string;
52
- }
53
- interface AppearedSubRoot {
54
- container: string;
55
- elementXPath: string;
56
- subtree: string;
57
- size: number;
58
- rawSize: number;
59
- appearedSelector?: string;
60
- fresh: boolean;
61
- }
62
- interface RegionProbe {
63
- found: boolean;
64
- onScreen: boolean;
65
- floating: boolean;
23
+ interface RegionLayout {
24
+ rendered: boolean;
25
+ inViewport: boolean;
26
+ outOfFlow: boolean;
66
27
  centerBelongs: boolean;
67
- coveredByFloating: boolean;
28
+ coveredOutOfFlow: boolean;
29
+ holdsViewportCenter: boolean;
30
+ viewportCoverage: number;
31
+ controls: number;
68
32
  }
69
33
  export {};