@canaryai/cli 0.2.8 → 0.2.12

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 (58) hide show
  1. package/README.md +77 -92
  2. package/dist/chunk-CEW4BDXD.js +186 -0
  3. package/dist/chunk-CEW4BDXD.js.map +1 -0
  4. package/dist/chunk-ERSNYLMZ.js +229 -0
  5. package/dist/chunk-ERSNYLMZ.js.map +1 -0
  6. package/dist/{chunk-FK3EZADZ.js → chunk-MSMC6UXW.js} +2021 -873
  7. package/dist/chunk-MSMC6UXW.js.map +1 -0
  8. package/dist/{chunk-K2OB72B6.js → chunk-Q7WFBG5C.js} +2 -2
  9. package/dist/{debug-workflow-55G4Y6YT.js → debug-workflow-53ULOFJC.js} +57 -36
  10. package/dist/debug-workflow-53ULOFJC.js.map +1 -0
  11. package/dist/{docs-RPFT7ZJB.js → docs-BEE3LOCO.js} +2 -2
  12. package/dist/{feature-flag-2FDSKOVX.js → feature-flag-CYTDV4ZB.js} +3 -2
  13. package/dist/{feature-flag-2FDSKOVX.js.map → feature-flag-CYTDV4ZB.js.map} +1 -1
  14. package/dist/index.js +72 -137
  15. package/dist/index.js.map +1 -1
  16. package/dist/init-M6I3MG3D.js +146 -0
  17. package/dist/init-M6I3MG3D.js.map +1 -0
  18. package/dist/{issues-6ZDNDSD6.js → issues-NLM72HLU.js} +3 -2
  19. package/dist/{issues-6ZDNDSD6.js.map → issues-NLM72HLU.js.map} +1 -1
  20. package/dist/{knobs-MZRTYS3P.js → knobs-O35GAU5M.js} +3 -2
  21. package/dist/{knobs-MZRTYS3P.js.map → knobs-O35GAU5M.js.map} +1 -1
  22. package/dist/list-4K4EIGAT.js +57 -0
  23. package/dist/list-4K4EIGAT.js.map +1 -0
  24. package/dist/local-NHXXPHZ3.js +63 -0
  25. package/dist/local-NHXXPHZ3.js.map +1 -0
  26. package/dist/{local-browser-X7J27IGS.js → local-browser-VAZORCO3.js} +3 -3
  27. package/dist/login-ZLP64YQP.js +130 -0
  28. package/dist/login-ZLP64YQP.js.map +1 -0
  29. package/dist/mcp-ZF5G5DCB.js +377 -0
  30. package/dist/mcp-ZF5G5DCB.js.map +1 -0
  31. package/dist/{record-4OX7HXWQ.js → record-V6QKFFH3.js} +133 -72
  32. package/dist/record-V6QKFFH3.js.map +1 -0
  33. package/dist/{release-L4IXOHDF.js → release-7TI7EIGD.js} +8 -4
  34. package/dist/release-7TI7EIGD.js.map +1 -0
  35. package/dist/session-UGNJXRUW.js +819 -0
  36. package/dist/session-UGNJXRUW.js.map +1 -0
  37. package/dist/skill-ORWAPBDW.js +424 -0
  38. package/dist/skill-ORWAPBDW.js.map +1 -0
  39. package/dist/{src-I4EXB5OD.js → src-4VIDSK4A.js} +18 -2
  40. package/dist/start-E532F3BU.js +112 -0
  41. package/dist/start-E532F3BU.js.map +1 -0
  42. package/dist/workflow-HXIUXRFI.js +613 -0
  43. package/dist/workflow-HXIUXRFI.js.map +1 -0
  44. package/package.json +1 -1
  45. package/dist/chunk-6WWHXWCS.js +0 -65
  46. package/dist/chunk-6WWHXWCS.js.map +0 -1
  47. package/dist/chunk-DXIAHB72.js +0 -340
  48. package/dist/chunk-DXIAHB72.js.map +0 -1
  49. package/dist/chunk-FK3EZADZ.js.map +0 -1
  50. package/dist/debug-workflow-55G4Y6YT.js.map +0 -1
  51. package/dist/mcp-4JVLADZL.js +0 -688
  52. package/dist/mcp-4JVLADZL.js.map +0 -1
  53. package/dist/record-4OX7HXWQ.js.map +0 -1
  54. package/dist/release-L4IXOHDF.js.map +0 -1
  55. /package/dist/{chunk-K2OB72B6.js.map → chunk-Q7WFBG5C.js.map} +0 -0
  56. /package/dist/{docs-RPFT7ZJB.js.map → docs-BEE3LOCO.js.map} +0 -0
  57. /package/dist/{local-browser-X7J27IGS.js.map → local-browser-VAZORCO3.js.map} +0 -0
  58. /package/dist/{src-I4EXB5OD.js.map → src-4VIDSK4A.js.map} +0 -0
@@ -4383,7 +4383,7 @@ var require_src = __commonJS({
4383
4383
  var require_encoder = __commonJS({
4384
4384
  "../../node_modules/.bun/jpeg-js@0.4.4/node_modules/jpeg-js/lib/encoder.js"(exports2, module2) {
4385
4385
  "use strict";
4386
- var btoa = btoa || function(buf) {
4386
+ var btoa2 = btoa2 || function(buf) {
4387
4387
  return Buffer.from(buf).toString("base64");
4388
4388
  };
4389
4389
  function JPEGEncoder(quality2) {
@@ -5404,7 +5404,7 @@ var require_encoder = __commonJS({
5404
5404
  writeWord(65497);
5405
5405
  if (typeof module2 === "undefined") return new Uint8Array(byteout);
5406
5406
  return Buffer.from(byteout);
5407
- var jpegDataUri = "data:image/jpeg;base64," + btoa(byteout.join(""));
5407
+ var jpegDataUri = "data:image/jpeg;base64," + btoa2(byteout.join(""));
5408
5408
  byteout = [];
5409
5409
  var duration = (/* @__PURE__ */ new Date()).getTime() - time_start;
5410
5410
  return jpegDataUri;
@@ -37998,6 +37998,201 @@ var CdpScreencastManager = class {
37998
37998
  }
37999
37999
  };
38000
38000
 
38001
+ // ../browser-core/src/indexeddb-helpers.ts
38002
+ var MAX_TOTAL_SIZE_BYTES = 10 * 1024 * 1024;
38003
+ async function extractIndexedDB(page) {
38004
+ try {
38005
+ const data = await page.evaluate(async () => {
38006
+ if (!indexedDB.databases) return null;
38007
+ const dbInfos = await indexedDB.databases();
38008
+ if (!dbInfos || dbInfos.length === 0) return null;
38009
+ function serializeValue(value) {
38010
+ if (value instanceof ArrayBuffer) {
38011
+ return {
38012
+ __type: "ArrayBuffer",
38013
+ __data: btoa(String.fromCharCode(...new Uint8Array(value)))
38014
+ };
38015
+ }
38016
+ if (ArrayBuffer.isView(value)) {
38017
+ const buf = value.buffer instanceof ArrayBuffer ? value.buffer : value.buffer;
38018
+ return {
38019
+ __type: "TypedArray",
38020
+ __arrayType: value.constructor.name,
38021
+ __data: btoa(String.fromCharCode(...new Uint8Array(buf, value.byteOffset, value.byteLength)))
38022
+ };
38023
+ }
38024
+ if (value instanceof Blob) {
38025
+ return { __type: "Blob", __skipped: true };
38026
+ }
38027
+ if (value instanceof Date) {
38028
+ return { __type: "Date", __data: value.toISOString() };
38029
+ }
38030
+ if (Array.isArray(value)) {
38031
+ return value.map(serializeValue);
38032
+ }
38033
+ if (value !== null && typeof value === "object") {
38034
+ const result = {};
38035
+ for (const [k, v] of Object.entries(value)) {
38036
+ result[k] = serializeValue(v);
38037
+ }
38038
+ return result;
38039
+ }
38040
+ return value;
38041
+ }
38042
+ const databases = [];
38043
+ for (const dbInfo of dbInfos) {
38044
+ if (!dbInfo.name) continue;
38045
+ try {
38046
+ const db = await new Promise((resolve2, reject2) => {
38047
+ const request2 = indexedDB.open(dbInfo.name, dbInfo.version);
38048
+ request2.onsuccess = () => resolve2(request2.result);
38049
+ request2.onerror = () => reject2(request2.error);
38050
+ request2.onupgradeneeded = () => {
38051
+ request2.transaction?.abort();
38052
+ reject2(new Error("version_mismatch"));
38053
+ };
38054
+ });
38055
+ const objectStores = [];
38056
+ for (const storeName of Array.from(db.objectStoreNames)) {
38057
+ try {
38058
+ const tx = db.transaction(storeName, "readonly");
38059
+ const store = tx.objectStore(storeName);
38060
+ const entries = await new Promise(
38061
+ (resolve2, reject2) => {
38062
+ const items = [];
38063
+ const cursorReq = store.openCursor();
38064
+ cursorReq.onsuccess = () => {
38065
+ const cursor = cursorReq.result;
38066
+ if (cursor) {
38067
+ items.push({
38068
+ key: cursor.key,
38069
+ value: serializeValue(cursor.value)
38070
+ });
38071
+ cursor.continue();
38072
+ } else {
38073
+ resolve2(items);
38074
+ }
38075
+ };
38076
+ cursorReq.onerror = () => reject2(cursorReq.error);
38077
+ }
38078
+ );
38079
+ objectStores.push({
38080
+ name: storeName,
38081
+ keyPath: store.keyPath,
38082
+ autoIncrement: store.autoIncrement,
38083
+ entries
38084
+ });
38085
+ } catch {
38086
+ }
38087
+ }
38088
+ db.close();
38089
+ databases.push({
38090
+ name: dbInfo.name,
38091
+ version: dbInfo.version ?? 1,
38092
+ objectStores
38093
+ });
38094
+ } catch {
38095
+ }
38096
+ }
38097
+ return databases.length > 0 ? { databases } : null;
38098
+ });
38099
+ if (!data) return null;
38100
+ const serialized = JSON.stringify(data);
38101
+ if (serialized.length > MAX_TOTAL_SIZE_BYTES) {
38102
+ return null;
38103
+ }
38104
+ return data;
38105
+ } catch {
38106
+ return null;
38107
+ }
38108
+ }
38109
+ async function restoreIndexedDB(page, data) {
38110
+ if (!data.databases || data.databases.length === 0) return;
38111
+ await page.evaluate(async (databases) => {
38112
+ function deserializeValue(value) {
38113
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
38114
+ const obj = value;
38115
+ if (obj.__type === "ArrayBuffer" && typeof obj.__data === "string") {
38116
+ const binary = atob(obj.__data);
38117
+ const bytes = new Uint8Array(binary.length);
38118
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
38119
+ return bytes.buffer;
38120
+ }
38121
+ if (obj.__type === "TypedArray" && typeof obj.__data === "string") {
38122
+ const binary = atob(obj.__data);
38123
+ const bytes = new Uint8Array(binary.length);
38124
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
38125
+ return bytes.buffer;
38126
+ }
38127
+ if (obj.__type === "Date" && typeof obj.__data === "string") {
38128
+ return new Date(obj.__data);
38129
+ }
38130
+ if (obj.__type === "Blob" && obj.__skipped) {
38131
+ return null;
38132
+ }
38133
+ const result = {};
38134
+ for (const [k, v] of Object.entries(obj)) {
38135
+ result[k] = deserializeValue(v);
38136
+ }
38137
+ return result;
38138
+ }
38139
+ if (Array.isArray(value)) {
38140
+ return value.map(deserializeValue);
38141
+ }
38142
+ return value;
38143
+ }
38144
+ for (const dbData of databases) {
38145
+ try {
38146
+ await new Promise((resolve2, reject2) => {
38147
+ const req = indexedDB.deleteDatabase(dbData.name);
38148
+ req.onsuccess = () => resolve2();
38149
+ req.onerror = () => reject2(req.error);
38150
+ req.onblocked = () => resolve2();
38151
+ });
38152
+ const db = await new Promise((resolve2, reject2) => {
38153
+ const request2 = indexedDB.open(dbData.name, dbData.version);
38154
+ request2.onupgradeneeded = () => {
38155
+ const db2 = request2.result;
38156
+ for (const storeData of dbData.objectStores) {
38157
+ if (!db2.objectStoreNames.contains(storeData.name)) {
38158
+ db2.createObjectStore(storeData.name, {
38159
+ keyPath: storeData.keyPath ?? void 0,
38160
+ autoIncrement: storeData.autoIncrement
38161
+ });
38162
+ }
38163
+ }
38164
+ };
38165
+ request2.onsuccess = () => resolve2(request2.result);
38166
+ request2.onerror = () => reject2(request2.error);
38167
+ });
38168
+ for (const storeData of dbData.objectStores) {
38169
+ if (storeData.entries.length === 0) continue;
38170
+ if (!db.objectStoreNames.contains(storeData.name)) continue;
38171
+ try {
38172
+ const tx = db.transaction(storeData.name, "readwrite");
38173
+ const store = tx.objectStore(storeData.name);
38174
+ for (const entry of storeData.entries) {
38175
+ const value = deserializeValue(entry.value);
38176
+ if (storeData.keyPath) {
38177
+ store.put(value);
38178
+ } else {
38179
+ store.put(value, entry.key);
38180
+ }
38181
+ }
38182
+ await new Promise((resolve2, reject2) => {
38183
+ tx.oncomplete = () => resolve2();
38184
+ tx.onerror = () => reject2(tx.error);
38185
+ });
38186
+ } catch {
38187
+ }
38188
+ }
38189
+ db.close();
38190
+ } catch {
38191
+ }
38192
+ }
38193
+ }, data.databases);
38194
+ }
38195
+
38001
38196
  // ../browser-core/src/snapshot-analyzer.ts
38002
38197
  var logger = consoleLogger;
38003
38198
  function setSnapshotAnalyzerLogger(l) {
@@ -38342,8 +38537,20 @@ function normalizeIconLabelText(role, text) {
38342
38537
  }
38343
38538
  return ICON_ONLY_ROLES.has(role) ? "(icon)" : "";
38344
38539
  }
38540
+ var STYLED_WRAPPER_RE = /^Styled\(\w+\)$/;
38541
+ var MUI_CLASS_RE = /^Mui\w+-[\w-]+$/;
38542
+ var HAS_ASCII_ALNUM_RE = /[A-Za-z0-9]/;
38543
+ function isNoisyLabel(text) {
38544
+ const trimmed = text.trim();
38545
+ if (!trimmed) return false;
38546
+ if (STYLED_WRAPPER_RE.test(trimmed)) return true;
38547
+ if (MUI_CLASS_RE.test(trimmed)) return true;
38548
+ if (HAS_PRIVATE_USE_GLYPH_RE.test(trimmed)) return false;
38549
+ if (!HAS_ASCII_ALNUM_RE.test(trimmed)) return true;
38550
+ return false;
38551
+ }
38345
38552
 
38346
- // ../browser-core/src/snapshot-formatter.ts
38553
+ // ../browser-core/src/snapshot-formatter-shared.ts
38347
38554
  var MAX_SEARCH_MATCHES = 10;
38348
38555
  var INTERACTIVE_LEAF_ROLES = /* @__PURE__ */ new Set([
38349
38556
  "button",
@@ -38378,134 +38585,6 @@ var STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
38378
38585
  "section",
38379
38586
  "iframe"
38380
38587
  ]);
38381
- var VISUAL_HEADING_MAX_LENGTH = 60;
38382
- function hasInteractiveDescendants(element) {
38383
- if (element.ref && (INTERACTIVE_ROLES.has(element.role) || isClickableByAttribute(element))) return true;
38384
- for (const child of element.children) {
38385
- if (hasInteractiveDescendants(child)) return true;
38386
- }
38387
- return false;
38388
- }
38389
- function isVisualHeading(element, siblings, siblingIndex) {
38390
- if (element.role !== "generic" && element.role !== "paragraph") return false;
38391
- if (isClickableByAttribute(element)) return false;
38392
- const text = getVisualHeadingText(element);
38393
- if (!text || text.length < 3 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
38394
- if (hasInteractiveDescendants(element)) return false;
38395
- let interactiveCount = 0;
38396
- for (let i = siblingIndex + 1; i < siblings.length; i++) {
38397
- const sib = siblings[i];
38398
- if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) break;
38399
- if (sib.role === "generic" || sib.role === "paragraph") {
38400
- if (!isClickableByAttribute(sib) && !hasInteractiveDescendants(sib)) {
38401
- const sibText = getVisualHeadingText(sib);
38402
- if (sibText && sibText.length >= 3 && sibText.length <= VISUAL_HEADING_MAX_LENGTH) {
38403
- break;
38404
- }
38405
- }
38406
- }
38407
- if (hasInteractiveDescendants(sib) || isClickableByAttribute(sib)) {
38408
- interactiveCount++;
38409
- }
38410
- }
38411
- return interactiveCount >= 3;
38412
- }
38413
- function getVisualHeadingText(element) {
38414
- const direct = element.text;
38415
- if (direct) return direct;
38416
- for (const child of element.children) {
38417
- if (TEXT_CARRYING_ROLES.has(child.role) && child.text) return child.text;
38418
- }
38419
- return void 0;
38420
- }
38421
- function findNextSectionBoundary(siblings, startIndex) {
38422
- for (let i = startIndex; i < siblings.length; i++) {
38423
- const sib = siblings[i];
38424
- if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) return i;
38425
- if (isVisualHeading(sib, siblings, i)) return i;
38426
- }
38427
- return siblings.length;
38428
- }
38429
- function isFieldLabelCandidate(element) {
38430
- if (element.role !== "generic" && element.role !== "paragraph" && element.role !== "text" && element.role !== "label" && element.role !== "strong" && element.role !== "emphasis") {
38431
- return false;
38432
- }
38433
- if (isClickableByAttribute(element)) return false;
38434
- if (hasInteractiveDescendants(element)) return false;
38435
- const text = getFieldLabelText(element);
38436
- if (!text || text.length < 1 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
38437
- return true;
38438
- }
38439
- var FIELD_LABEL_TEXT_ROLES = /* @__PURE__ */ new Set([
38440
- "generic",
38441
- "paragraph",
38442
- "text",
38443
- "strong",
38444
- "emphasis",
38445
- "label",
38446
- "heading"
38447
- ]);
38448
- function getFieldLabelText(element) {
38449
- if (element.text) return element.text.trim();
38450
- if (element.inputValue && !INTERACTIVE_ROLES.has(element.role)) return element.inputValue.trim();
38451
- for (const child of element.children) {
38452
- if (FIELD_LABEL_TEXT_ROLES.has(child.role)) {
38453
- if (child.text) return child.text.trim();
38454
- if (child.inputValue && !INTERACTIVE_ROLES.has(child.role)) return child.inputValue.trim();
38455
- }
38456
- }
38457
- return void 0;
38458
- }
38459
- function findFieldLabelFromSiblings(siblings, interactiveIndex) {
38460
- for (let i = interactiveIndex - 1; i >= 0; i--) {
38461
- const sib = siblings[i];
38462
- if (INTERACTIVE_ROLES.has(sib.role) || isClickableByAttribute(sib) || STRUCTURAL_ROLES.has(sib.role) || sib.role === "heading") {
38463
- break;
38464
- }
38465
- if (isFieldLabelCandidate(sib)) {
38466
- return getFieldLabelText(sib);
38467
- }
38468
- if (sib.children.length >= 1 && sib.children.length <= 4) {
38469
- const containerLabel = resolveContainerFieldLabel(sib);
38470
- if (containerLabel) return containerLabel;
38471
- }
38472
- if (sib.children.length > 4) break;
38473
- }
38474
- return void 0;
38475
- }
38476
- function findFieldLabelInContext(_element, siblings, siblingIndex) {
38477
- return findFieldLabelFromSiblings(siblings, siblingIndex);
38478
- }
38479
- function resolveContainerFieldLabel(container) {
38480
- const kids = container.children;
38481
- if (kids.length < 2 || kids.length > 4) return void 0;
38482
- const firstChild = kids[0];
38483
- if (isFieldLabelCandidate(firstChild)) {
38484
- return getFieldLabelText(firstChild);
38485
- }
38486
- return void 0;
38487
- }
38488
- function shouldDropInheritedFieldLabel(element, inheritedFieldLabel) {
38489
- if (!inheritedFieldLabel) return false;
38490
- if (INTERACTIVE_LEAF_ROLES.has(element.role)) return false;
38491
- if (!isClickableByAttribute(element)) return false;
38492
- const directText = getElementText(element);
38493
- if (directText) {
38494
- return directText.toLowerCase() !== inheritedFieldLabel.toLowerCase();
38495
- }
38496
- const childTexts = [];
38497
- for (const child of element.children) {
38498
- if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38499
- const text = getElementText(child);
38500
- if (text && TEXT_CARRYING_ROLES.has(child.role)) {
38501
- childTexts.push(text);
38502
- }
38503
- }
38504
- }
38505
- const composedText = childTexts.join(" ");
38506
- if (!composedText) return false;
38507
- return composedText.toLowerCase() !== inheritedFieldLabel.toLowerCase();
38508
- }
38509
38588
  var TEXT_CARRYING_ROLES = /* @__PURE__ */ new Set([
38510
38589
  "generic",
38511
38590
  "paragraph",
@@ -38516,12 +38595,11 @@ var TEXT_CARRYING_ROLES = /* @__PURE__ */ new Set([
38516
38595
  "blockquote",
38517
38596
  "caption"
38518
38597
  ]);
38598
+ var VISUAL_HEADING_MAX_LENGTH = 60;
38519
38599
  var STATUS_WORDS = /* @__PURE__ */ new Set(["favorable", "unfavorable", "success", "error", "warning", "pending"]);
38520
38600
  var BLANK_PATTERNS = [
38521
38601
  /\s*\(Blank\)\s*/gi,
38522
- // "(Blank)" in any case
38523
38602
  /\s*\(blank\)\s*/gi
38524
- // Redundant but explicit
38525
38603
  ];
38526
38604
  var MENU_TRIGGER_PATTERNS = [
38527
38605
  /^Open menu for /i,
@@ -38536,21 +38614,102 @@ var SECTION_BOUNDARY_ROLES = /* @__PURE__ */ new Set([
38536
38614
  "article",
38537
38615
  "section",
38538
38616
  "complementary"
38539
- // sidebars
38540
38617
  ]);
38618
+ var FRAMEWORK_NOISE_RE = /^(ng-binding|ng-scope|ng-pristine|ng-dirty|ng-valid|ng-invalid|ng-untouched|ng-touched|v-enter|v-leave)$/;
38541
38619
  function normalizeSearchText(text) {
38542
38620
  return text.toLowerCase().replace(/[-_.,;:!?'"()\[\]{}\/\\@#$%^&*+=<>~`|]/g, "").replace(/\s+/g, " ").trim();
38543
38621
  }
38544
- function isMenuTriggerNoise(element) {
38545
- if (isActiveElement(element)) return false;
38546
- if (element.role !== "button") return false;
38547
- const text = element.text || "";
38548
- if (!text.trim()) return false;
38549
- for (const pattern of MENU_TRIGGER_PATTERNS) {
38550
- if (pattern.test(text)) return true;
38622
+ function isActiveElement(element) {
38623
+ return element.attributes["active"] !== void 0 || element.rawLine.includes("[active]");
38624
+ }
38625
+ function isDisabledElement(element) {
38626
+ return element.attributes["disabled"] !== void 0 || element.rawLine.includes("[disabled]");
38627
+ }
38628
+ function isClickableByAttribute(element) {
38629
+ return element.attributes["cursor"] === "pointer";
38630
+ }
38631
+ function isActionableClickableGeneric(element) {
38632
+ return isClickableByAttribute(element) && !INTERACTIVE_ROLES.has(element.role);
38633
+ }
38634
+ function isSearchInteractiveElement(element) {
38635
+ return INTERACTIVE_LEAF_ROLES.has(element.role) || isActionableClickableGeneric(element);
38636
+ }
38637
+ function getElementText(el) {
38638
+ if (el.text && el.inputValue && FRAMEWORK_NOISE_RE.test(el.text.trim())) {
38639
+ return el.inputValue;
38640
+ }
38641
+ return el.text || el.inputValue;
38642
+ }
38643
+ function composeLabel(element) {
38644
+ const directText = getElementText(element);
38645
+ if (directText) return directText;
38646
+ const textParts = [];
38647
+ function collectText(el, depth) {
38648
+ if (depth > 3) return;
38649
+ const text = getElementText(el);
38650
+ if (text && TEXT_CARRYING_ROLES.has(el.role)) {
38651
+ textParts.push(text);
38652
+ }
38653
+ for (const child of el.children) {
38654
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38655
+ collectText(child, depth + 1);
38656
+ }
38657
+ }
38658
+ }
38659
+ for (const child of element.children) {
38660
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38661
+ collectText(child, 0);
38662
+ }
38663
+ }
38664
+ return textParts.join(" | ") || element.role;
38665
+ }
38666
+ function composeChildLabel(element) {
38667
+ const textParts = [];
38668
+ function collectText(el, depth) {
38669
+ if (depth > 3) return;
38670
+ const text = getElementText(el);
38671
+ if (text && TEXT_CARRYING_ROLES.has(el.role)) {
38672
+ textParts.push(text);
38673
+ }
38674
+ for (const child of el.children) {
38675
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38676
+ collectText(child, depth + 1);
38677
+ }
38678
+ }
38679
+ }
38680
+ for (const child of element.children) {
38681
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38682
+ collectText(child, 0);
38683
+ }
38684
+ }
38685
+ return textParts.length > 0 ? textParts.join(" | ") : null;
38686
+ }
38687
+ function getDisplayText(element, options) {
38688
+ const isClickable = isActionableClickableGeneric(element);
38689
+ let rawTextSource = options?.overrideLabel ?? (isClickable ? composeLabel(element) : element.text ?? "");
38690
+ if (rawTextSource && isNoisyLabel(rawTextSource) && INTERACTIVE_LEAF_ROLES.has(element.role)) {
38691
+ const childText = composeChildLabel(element);
38692
+ rawTextSource = childText ?? "";
38693
+ }
38694
+ return normalizeIconLabelText(element.role, rawTextSource);
38695
+ }
38696
+ function hasInteractiveDescendants(element) {
38697
+ if (element.ref && (INTERACTIVE_ROLES.has(element.role) || isClickableByAttribute(element))) return true;
38698
+ for (const child of element.children) {
38699
+ if (hasInteractiveDescendants(child)) return true;
38551
38700
  }
38552
38701
  return false;
38553
38702
  }
38703
+ function subtreeContainsActive(element) {
38704
+ if (isActiveElement(element)) return true;
38705
+ for (const child of element.children) {
38706
+ if (subtreeContainsActive(child)) return true;
38707
+ }
38708
+ return false;
38709
+ }
38710
+ function cleanLabel(text) {
38711
+ return text.replace(/\s+/g, " ").replace(/…+/g, "...").trim();
38712
+ }
38554
38713
  function isBlankValue(value) {
38555
38714
  if (!value) return true;
38556
38715
  const trimmed = value.trim().toLowerCase();
@@ -38590,160 +38749,15 @@ function extractDataValue(text) {
38590
38749
  }
38591
38750
  return { label: cleanLabel(cleaned), status };
38592
38751
  }
38593
- function findRelevanceScope(elements, activeElementRef) {
38594
- if (!activeElementRef) return null;
38595
- const path5 = [];
38596
- function findPath(el) {
38597
- if (el.ref) {
38598
- path5.push({ ref: el.ref, role: el.role });
38599
- }
38600
- if (el.ref === activeElementRef) {
38601
- return true;
38602
- }
38603
- for (const child of el.children) {
38604
- if (findPath(child)) return true;
38605
- }
38606
- if (el.ref) path5.pop();
38607
- return false;
38608
- }
38609
- for (const el of elements) {
38610
- if (findPath(el)) break;
38611
- }
38612
- for (let i = path5.length - 1; i >= 0; i--) {
38613
- if (SECTION_BOUNDARY_ROLES.has(path5[i].role)) {
38614
- return path5[i].ref;
38615
- }
38616
- }
38617
- return null;
38618
- }
38619
- function searchElements(elements, searchTerms, scopeRef = null) {
38620
- const allMatches = [];
38621
- const normalizedTerms = searchTerms.map((t) => normalizeSearchText(t)).filter(Boolean);
38622
- if (normalizedTerms.length === 0) return { matches: [], totalFound: 0 };
38623
- function searchElement(el, inScope) {
38624
- const nowInScope = inScope || el.ref === scopeRef || scopeRef === null;
38625
- if (el.ref && nowInScope) {
38626
- const searchText = [el.text, el.inputValue].filter(Boolean).join(" ");
38627
- const normalizedSearchText = normalizeSearchText(searchText);
38628
- for (const term of normalizedTerms) {
38629
- if (normalizedSearchText.includes(term)) {
38630
- const normalizedLabel = normalizeIconLabelText(el.role, el.text || "");
38631
- allMatches.push({
38632
- ref: el.ref,
38633
- role: el.role,
38634
- label: normalizedLabel || el.role,
38635
- term,
38636
- isInteractive: INTERACTIVE_LEAF_ROLES.has(el.role)
38637
- });
38638
- break;
38639
- }
38640
- }
38641
- }
38642
- for (const child of el.children) {
38643
- searchElement(child, nowInScope);
38644
- }
38645
- }
38646
- for (const el of elements) {
38647
- searchElement(el, scopeRef === null);
38648
- }
38649
- const totalFound = allMatches.length;
38650
- allMatches.sort((a, b) => {
38651
- if (a.isInteractive && !b.isInteractive) return -1;
38652
- if (!a.isInteractive && b.isInteractive) return 1;
38653
- return 0;
38654
- });
38655
- return {
38656
- matches: allMatches.slice(0, MAX_SEARCH_MATCHES),
38657
- totalFound
38658
- };
38659
- }
38660
- function findElementInSections(sections, targetRef, ancestors = []) {
38661
- for (const section of sections) {
38662
- const newAncestors = [...ancestors, section];
38663
- const path5 = newAncestors.map((s) => s.heading);
38664
- if (section.elements.some((el) => el.ref === targetRef)) {
38665
- return { section, ancestors: newAncestors, path: path5 };
38666
- }
38667
- const found = findElementInSections(section.subsections, targetRef, newAncestors);
38668
- if (found) return found;
38669
- }
38670
- return null;
38671
- }
38672
- function populateSearchContext(matches, sections) {
38673
- for (const match of matches) {
38674
- const result = findElementInSections(sections, match.ref);
38675
- if (result) {
38676
- const { section, ancestors, path: path5 } = result;
38677
- const inFocusedDialog = ancestors.some((s) => s.isFocused);
38678
- const inBackground = section.isBackground || ancestors.some((s) => s.isBackground);
38679
- if (inFocusedDialog) {
38680
- match.context = "focused";
38681
- } else if (inBackground) {
38682
- match.context = "background";
38683
- } else {
38684
- match.context = "foreground";
38685
- }
38686
- const SKIP_HEADINGS = ["generic", "main", "iframe"];
38687
- match.sectionPath = path5.filter((p) => !SKIP_HEADINGS.includes(p.toLowerCase())).slice(-3).join(" > ");
38688
- }
38689
- }
38690
- }
38691
- function sortMatchesByContext(matches) {
38692
- const contextOrder = {
38693
- focused: 0,
38694
- foreground: 1,
38695
- background: 2
38696
- };
38697
- return [...matches].sort((a, b) => {
38698
- const aContext = contextOrder[a.context ?? "foreground"] ?? 1;
38699
- const bContext = contextOrder[b.context ?? "foreground"] ?? 1;
38700
- if (aContext !== bContext) return aContext - bContext;
38701
- if (a.isInteractive && !b.isInteractive) return -1;
38702
- if (!a.isInteractive && b.isInteractive) return 1;
38703
- return 0;
38704
- });
38705
- }
38706
- function cleanLabel(text) {
38707
- return text.replace(/\s+/g, " ").replace(/…+/g, "...").trim();
38708
- }
38709
- function isActiveElement(element) {
38710
- return element.attributes["active"] !== void 0 || element.rawLine.includes("[active]");
38711
- }
38712
- function isDisabledElement(element) {
38713
- return element.attributes["disabled"] !== void 0 || element.rawLine.includes("[disabled]");
38714
- }
38715
- function isClickableByAttribute(element) {
38716
- return element.attributes["cursor"] === "pointer";
38717
- }
38718
- var FRAMEWORK_NOISE_RE = /^(ng-binding|ng-scope|ng-pristine|ng-dirty|ng-valid|ng-invalid|ng-untouched|ng-touched|v-enter|v-leave)$/;
38719
- function getElementText(el) {
38720
- if (el.text && el.inputValue && FRAMEWORK_NOISE_RE.test(el.text.trim())) {
38721
- return el.inputValue;
38722
- }
38723
- return el.text || el.inputValue;
38724
- }
38725
- function composeLabel(element) {
38726
- const directText = getElementText(element);
38727
- if (directText) return directText;
38728
- const textParts = [];
38729
- function collectText(el, depth) {
38730
- if (depth > 3) return;
38731
- const text = getElementText(el);
38732
- if (text && TEXT_CARRYING_ROLES.has(el.role)) {
38733
- textParts.push(text);
38734
- }
38735
- for (const child of el.children) {
38736
- if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38737
- collectText(child, depth + 1);
38738
- }
38739
- }
38740
- }
38741
- for (const child of element.children) {
38742
- if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
38743
- collectText(child, 0);
38744
- }
38752
+ function isMenuTriggerNoise(element) {
38753
+ if (isActiveElement(element)) return false;
38754
+ if (element.role !== "button") return false;
38755
+ const text = element.text || "";
38756
+ if (!text.trim()) return false;
38757
+ for (const pattern of MENU_TRIGGER_PATTERNS) {
38758
+ if (pattern.test(text)) return true;
38745
38759
  }
38746
- return textParts.join(" | ") || element.role;
38760
+ return false;
38747
38761
  }
38748
38762
  function countElementsWithRefs(element) {
38749
38763
  let count = element.ref ? 1 : 0;
@@ -38752,74 +38766,23 @@ function countElementsWithRefs(element) {
38752
38766
  }
38753
38767
  return count;
38754
38768
  }
38755
- function isExpandedElement(element) {
38756
- return element.attributes["expanded"] === "true" || element.attributes["aria-expanded"] === "true";
38757
- }
38758
- function collectExpandedChildren(element, result) {
38759
- for (const child of element.children) {
38760
- if (STRUCTURAL_ROLES.has(child.role)) continue;
38761
- if (INTERACTIVE_LEAF_ROLES.has(child.role) && child.ref) {
38762
- result.push(formatElement(child));
38763
- continue;
38764
- }
38765
- if (child.role === "listitem" && child.ref) {
38766
- const text = child.text || composeLabel(child);
38767
- if (text && text !== "listitem") {
38768
- result.push(formatElement(child, { overrideLabel: text }));
38769
- continue;
38770
- }
38771
- }
38772
- if (isClickableByAttribute(child) && child.ref) {
38773
- result.push(formatElement(child));
38769
+ function collectComboboxOptions(element) {
38770
+ const options = [];
38771
+ function walk(el) {
38772
+ if (el.role === "option" && el.text?.trim()) {
38773
+ options.push(el.text.trim());
38774
38774
  }
38775
- collectExpandedChildren(child, result);
38776
- }
38777
- }
38778
- function hasActionableChildren(element) {
38779
- const children2 = [];
38780
- collectExpandedChildren(element, children2);
38781
- return children2.length > 0;
38782
- }
38783
- function isExpandedWithActionableChildren(element) {
38784
- if (!isExpandedElement(element)) return false;
38785
- if (element.role === "combobox") return false;
38786
- return hasActionableChildren(element);
38787
- }
38788
- function composeTriggerLabel(element) {
38789
- const ariaLabel = element.attributes["aria-label"];
38790
- if (ariaLabel) return ariaLabel;
38791
- const fullText = element.text || "";
38792
- if (fullText) {
38793
- let collectLeafTexts2 = function(el) {
38794
- for (const child of el.children) {
38795
- const text = child.text?.trim();
38796
- if (text) {
38797
- leafTexts.push(text);
38798
- }
38799
- collectLeafTexts2(child);
38800
- }
38801
- };
38802
- var collectLeafTexts = collectLeafTexts2;
38803
- const leafTexts = [];
38804
- collectLeafTexts2(element);
38805
- if (leafTexts.length > 0) {
38806
- let stripped = fullText;
38807
- for (const lt of leafTexts) {
38808
- stripped = stripped.replace(lt, "");
38809
- }
38810
- stripped = stripped.replace(/\s+/g, " ").trim();
38811
- if (stripped) return stripped;
38775
+ for (const child of el.children) {
38776
+ walk(child);
38812
38777
  }
38813
38778
  }
38814
- return element.role;
38815
- }
38816
- function subtreeContainsActive(element) {
38817
- if (isActiveElement(element)) return true;
38818
38779
  for (const child of element.children) {
38819
- if (subtreeContainsActive(child)) return true;
38780
+ walk(child);
38820
38781
  }
38821
- return false;
38782
+ return options.length > 0 ? options : void 0;
38822
38783
  }
38784
+
38785
+ // ../browser-core/src/snapshot-formatter-grid.ts
38823
38786
  function findDescendantByRole(el, role) {
38824
38787
  if (el.role === role) return el;
38825
38788
  for (const child of el.children) {
@@ -38864,6 +38827,177 @@ function isWidgetTable(element) {
38864
38827
  }
38865
38828
  return totalCells > 0 && interactiveCells >= totalCells * 0.5;
38866
38829
  }
38830
+ function gridCellPriority(el) {
38831
+ if (el.isActive) return 5;
38832
+ if (FORM_INPUT_ROLES.has(el.role)) return 4;
38833
+ if (el.isClickableGeneric) return 3;
38834
+ if (el.role !== "button") return 2;
38835
+ return 1;
38836
+ }
38837
+ function extractCellText(el) {
38838
+ const findFirstParagraph = (node) => {
38839
+ if (node.role === "paragraph" && node.text) {
38840
+ return node.text.trim();
38841
+ }
38842
+ for (const child of node.children) {
38843
+ if (child.role === "generic" || child.role === "paragraph") {
38844
+ const found = findFirstParagraph(child);
38845
+ if (found) return found;
38846
+ }
38847
+ }
38848
+ return null;
38849
+ };
38850
+ const paragraphText = findFirstParagraph(el);
38851
+ if (paragraphText) return paragraphText;
38852
+ const text = el.text?.trim();
38853
+ if (text) return text;
38854
+ return null;
38855
+ }
38856
+ function extractGridCell(cellEl) {
38857
+ const interactiveElements = [];
38858
+ const findInteractive = (el) => {
38859
+ if (isClickableByAttribute(el) && el.ref && el.role === "generic") {
38860
+ const childTexts = [];
38861
+ for (const child of el.children) {
38862
+ if (child.role === "generic") {
38863
+ const childText = child.text || child.inputValue;
38864
+ if (childText) childTexts.push(childText);
38865
+ }
38866
+ }
38867
+ interactiveElements.push({
38868
+ ref: el.ref,
38869
+ column: el.attributes["label"] || childTexts[0] || "",
38870
+ value: childTexts.join(" - ") || el.text || el.inputValue || "",
38871
+ role: el.role,
38872
+ isClickableGeneric: true,
38873
+ isActive: isActiveElement(el)
38874
+ });
38875
+ }
38876
+ if (INTERACTIVE_LEAF_ROLES.has(el.role) && el.ref) {
38877
+ const value2 = el.role === "checkbox" ? el.attributes["checked"] === "true" ? "checked" : "unchecked" : el.inputValue || el.text || "";
38878
+ interactiveElements.push({
38879
+ ref: el.ref,
38880
+ column: el.attributes["label"] || el.text || "",
38881
+ value: value2,
38882
+ role: el.role,
38883
+ isClickableGeneric: false,
38884
+ isActive: isActiveElement(el)
38885
+ });
38886
+ }
38887
+ for (const child of el.children) findInteractive(child);
38888
+ };
38889
+ findInteractive(cellEl);
38890
+ if (interactiveElements.length === 0) {
38891
+ const textValue = extractCellText(cellEl);
38892
+ if (textValue && !isNoisyLabel(textValue)) {
38893
+ return {
38894
+ ref: cellEl.ref,
38895
+ column: cellEl.text || "",
38896
+ value: textValue,
38897
+ hasValue: true
38898
+ };
38899
+ }
38900
+ return null;
38901
+ }
38902
+ const found = interactiveElements.reduce(
38903
+ (best, current) => gridCellPriority(current) > gridCellPriority(best) ? current : best
38904
+ );
38905
+ const column = stripBlankPatterns(found.column);
38906
+ const rawValue = isBlankValue(found.value) ? "" : stripBlankPatterns(found.value).trim();
38907
+ const value = isNoisyLabel(rawValue) ? "" : rawValue;
38908
+ const gridcellLabel = cellEl.text ? stripBlankPatterns(cellEl.text).trim() : void 0;
38909
+ return {
38910
+ ref: found.ref,
38911
+ column: column.trim(),
38912
+ value,
38913
+ hasValue: value !== "",
38914
+ gridcellLabel: gridcellLabel || void 0,
38915
+ role: found.role
38916
+ };
38917
+ }
38918
+ function reconcileCellColumn(cell, knownColumns) {
38919
+ if (knownColumns.has(cell.column)) return cell;
38920
+ if (cell.gridcellLabel && knownColumns.has(cell.gridcellLabel)) {
38921
+ const newValue = cell.column || cell.value;
38922
+ const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
38923
+ return {
38924
+ ...cell,
38925
+ column: cell.gridcellLabel,
38926
+ value: cleanValue,
38927
+ hasValue: cleanValue !== ""
38928
+ };
38929
+ }
38930
+ let bestMatch = "";
38931
+ for (const col of knownColumns) {
38932
+ if (cell.column.startsWith(col) && col.length > bestMatch.length && (cell.column.length === col.length || cell.column[col.length] === " ")) {
38933
+ bestMatch = col;
38934
+ }
38935
+ }
38936
+ if (bestMatch) {
38937
+ const remainder = cell.column.slice(bestMatch.length).trim();
38938
+ const newValue = isBlankValue(remainder) ? "" : remainder;
38939
+ return {
38940
+ ...cell,
38941
+ column: bestMatch,
38942
+ value: newValue || cell.value,
38943
+ hasValue: (newValue || cell.value) !== ""
38944
+ };
38945
+ }
38946
+ return cell;
38947
+ }
38948
+ function reconcileGridCells(grid) {
38949
+ if (grid.columns.length === 0) return grid;
38950
+ const knownColumns = new Set(grid.columns.map((c2) => c2.name));
38951
+ return {
38952
+ ...grid,
38953
+ rows: grid.rows.map((row) => {
38954
+ const reconciledCells = row.cells.map((cell) => {
38955
+ const reconciled = reconcileCellColumn(cell, knownColumns);
38956
+ if (knownColumns.has(reconciled.column)) return reconciled;
38957
+ if (grid.columnByPosition && cell.positionInRow !== void 0 && grid.headerChildCount !== void 0 && row.totalChildren === grid.headerChildCount) {
38958
+ const positionalColumn = grid.columnByPosition.get(cell.positionInRow);
38959
+ if (positionalColumn) {
38960
+ const newValue = cell.column || cell.value;
38961
+ const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
38962
+ return {
38963
+ ...cell,
38964
+ column: positionalColumn,
38965
+ value: cleanValue,
38966
+ hasValue: cleanValue !== ""
38967
+ };
38968
+ }
38969
+ }
38970
+ return reconciled;
38971
+ });
38972
+ if (grid.headerCellPositions && row.cellPositions) {
38973
+ const claimedColumns = new Set(
38974
+ reconciledCells.filter((c2) => knownColumns.has(c2.column)).map((c2) => c2.column)
38975
+ );
38976
+ for (let idx = 0; idx < reconciledCells.length; idx++) {
38977
+ const cell = reconciledCells[idx];
38978
+ if (knownColumns.has(cell.column) || cell.positionInRow === void 0) continue;
38979
+ const cellOrdinal = row.cellPositions.indexOf(cell.positionInRow);
38980
+ if (cellOrdinal >= 0 && cellOrdinal < grid.headerCellPositions.length) {
38981
+ const headerPos = grid.headerCellPositions[cellOrdinal];
38982
+ const positionalColumn = grid.columnByPosition?.get(headerPos);
38983
+ if (positionalColumn && !claimedColumns.has(positionalColumn)) {
38984
+ const newValue = cell.column || cell.value;
38985
+ const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
38986
+ reconciledCells[idx] = {
38987
+ ...cell,
38988
+ column: positionalColumn,
38989
+ value: cleanValue,
38990
+ hasValue: cleanValue !== ""
38991
+ };
38992
+ claimedColumns.add(positionalColumn);
38993
+ }
38994
+ }
38995
+ }
38996
+ }
38997
+ return { ...row, cells: reconciledCells };
38998
+ })
38999
+ };
39000
+ }
38867
39001
  function detectGrid(element) {
38868
39002
  if (element.role !== "grid" && element.role !== "treegrid" && element.role !== "table") {
38869
39003
  return null;
@@ -38874,18 +39008,28 @@ function detectGrid(element) {
38874
39008
  let headerRowRef = null;
38875
39009
  const columnByPosition = /* @__PURE__ */ new Map();
38876
39010
  let headerChildCount;
39011
+ let headerCellPositions;
39012
+ const isRowLike = (el) => {
39013
+ if (el.role === "row") return true;
39014
+ const cellCount = el.children.filter(
39015
+ (c2) => c2.role === "cell" || c2.role === "gridcell"
39016
+ ).length;
39017
+ return cellCount >= 2;
39018
+ };
38877
39019
  const processRow = (rowEl, index) => {
38878
- if (rowEl.role !== "row") return null;
39020
+ if (!isRowLike(rowEl)) return null;
38879
39021
  const hasColumnHeaders = rowEl.children.some((c2) => c2.role === "columnheader");
38880
39022
  if (hasColumnHeaders) {
38881
39023
  headerRowRef = rowEl.ref;
38882
39024
  const columnHeaders = rowEl.children.filter((c2) => c2.role === "columnheader");
38883
- const hasChildful = columnHeaders.some((c2) => c2.children.length > 0);
39025
+ const childful = columnHeaders.filter((c2) => c2.children.length > 0);
39026
+ const textBearingChildless = columnHeaders.filter((c2) => c2.children.length === 0 && c2.text);
39027
+ const skipChildlessHeaders = childful.length > 0 && textBearingChildless.length <= childful.length;
38884
39028
  const columnsBefore = columns.length;
38885
39029
  for (let i = 0; i < rowEl.children.length; i++) {
38886
39030
  const child = rowEl.children[i];
38887
39031
  if (child.role === "columnheader") {
38888
- if (hasChildful && child.children.length === 0) continue;
39032
+ if (skipChildlessHeaders && child.children.length === 0) continue;
38889
39033
  const primaryLabel = findPrimaryColumnLabel(child);
38890
39034
  const rawName = (primaryLabel ? getElementText(primaryLabel) : void 0) || child.text || "";
38891
39035
  const cleanName = rawName.replace(/,?\s*sorted in \w+ order/i, "").trim();
@@ -38904,6 +39048,7 @@ function detectGrid(element) {
38904
39048
  }
38905
39049
  if (columns.length > columnsBefore) {
38906
39050
  headerChildCount = rowEl.children.length;
39051
+ headerCellPositions = Array.from(columnByPosition.keys()).sort((a, b) => a - b);
38907
39052
  }
38908
39053
  return null;
38909
39054
  }
@@ -38945,6 +39090,7 @@ function detectGrid(element) {
38945
39090
  }
38946
39091
  if (columns.length > columnsBefore) {
38947
39092
  headerChildCount = rowEl.children.length;
39093
+ headerCellPositions = Array.from(columnByPosition.keys()).sort((a, b) => a - b);
38948
39094
  }
38949
39095
  return null;
38950
39096
  }
@@ -38970,7 +39116,20 @@ function detectGrid(element) {
38970
39116
  findButtons(cellEl);
38971
39117
  }
38972
39118
  }
38973
- const row = { ref: rowEl.ref, index, isSelected, cells, totalChildren: rowEl.children.length };
39119
+ const cellPositions = [];
39120
+ for (let i = 0; i < rowEl.children.length; i++) {
39121
+ if (rowEl.children[i].role === "gridcell" || rowEl.children[i].role === "cell") {
39122
+ cellPositions.push(i);
39123
+ }
39124
+ }
39125
+ const row = {
39126
+ ref: rowEl.ref,
39127
+ index,
39128
+ isSelected,
39129
+ cells,
39130
+ totalChildren: rowEl.children.length,
39131
+ cellPositions
39132
+ };
38974
39133
  if (cells.length === 0 && rowEl.children.some((c2) => c2.role === "rowheader")) {
38975
39134
  const labels = [];
38976
39135
  const buttons = [];
@@ -38998,7 +39157,7 @@ function detectGrid(element) {
38998
39157
  return row;
38999
39158
  };
39000
39159
  const findRows = (el) => {
39001
- if (el.role === "row") {
39160
+ if (isRowLike(el)) {
39002
39161
  const row = processRow(el, rows.length);
39003
39162
  if (row) rows.push(row);
39004
39163
  } else {
@@ -39016,177 +39175,414 @@ function detectGrid(element) {
39016
39175
  totalRows: rows.length,
39017
39176
  containsActive: subtreeContainsActive(element),
39018
39177
  columnByPosition: columnByPosition.size > 0 ? columnByPosition : void 0,
39019
- headerChildCount
39178
+ headerChildCount,
39179
+ headerCellPositions
39020
39180
  };
39021
39181
  return reconcileGridCells(rawGrid);
39022
39182
  }
39023
- function gridCellPriority(el) {
39024
- if (el.isActive) return 5;
39025
- if (FORM_INPUT_ROLES.has(el.role)) return 4;
39026
- if (el.isClickableGeneric) return 3;
39027
- if (el.role !== "button") return 2;
39028
- return 1;
39183
+ function gridContainsRef(grid, targetRef) {
39184
+ for (const column of grid.columns) {
39185
+ if (column.ref === targetRef) return true;
39186
+ }
39187
+ for (const row of grid.rows) {
39188
+ if (row.ref === targetRef) return true;
39189
+ if (row.cells.some((cell) => cell.ref === targetRef)) return true;
39190
+ if (row.rowActions?.some((action) => action.ref === targetRef)) return true;
39191
+ if (row.expandedContent?.buttons.some((button) => button.ref === targetRef)) return true;
39192
+ }
39193
+ return false;
39029
39194
  }
39030
- function extractCellText(el) {
39031
- const findFirstParagraph = (node) => {
39032
- if (node.role === "paragraph" && node.text) {
39033
- return node.text.trim();
39195
+ function countRenderedGridItems(grid) {
39196
+ const knownColumns = grid.columns.length > 0 ? new Set(grid.columns.map((c2) => c2.name)) : void 0;
39197
+ let count = 0;
39198
+ for (const row of grid.rows) {
39199
+ count += row.cells.filter((c2) => c2.hasValue && (!knownColumns || knownColumns.has(c2.column))).length;
39200
+ count += row.rowActions?.length ?? 0;
39201
+ count += row.expandedContent?.buttons.length ?? 0;
39202
+ }
39203
+ return count;
39204
+ }
39205
+ function formatGridOutput(grid, indent, expanded = false) {
39206
+ const indentStr = " ".repeat(indent);
39207
+ const lines = [];
39208
+ const colInfo = grid.columns.length > 0 ? `${grid.columns.length} columns` : "unknown columns";
39209
+ lines.push(`${indentStr}GRID [${grid.ref}]: ${grid.totalRows} rows, ${colInfo}`);
39210
+ const MAX_DISPLAY_COLUMNS = 10;
39211
+ if (grid.columns.length > 0) {
39212
+ const displayColumns = grid.columns.slice(0, MAX_DISPLAY_COLUMNS);
39213
+ const colsWithRefs = displayColumns.map((c2) => `${c2.name} [${c2.ref}]`).join(", ");
39214
+ const moreColsNote = grid.columns.length > MAX_DISPLAY_COLUMNS ? ` (+${grid.columns.length - MAX_DISPLAY_COLUMNS} more)` : "";
39215
+ lines.push(`${indentStr} Columns: ${colsWithRefs}${moreColsNote}`);
39216
+ }
39217
+ lines.push("");
39218
+ const knownColumns = grid.columns.length > 0 ? new Set(grid.columns.map((c2) => c2.name)) : void 0;
39219
+ const MAX_GRID_ROWS = 30;
39220
+ const displayRows = expanded ? grid.rows : grid.rows.slice(0, MAX_GRID_ROWS);
39221
+ for (const row of displayRows) {
39222
+ lines.push(formatGridRow(row, indent + 1, knownColumns));
39223
+ }
39224
+ if (!expanded && grid.rows.length > MAX_GRID_ROWS) {
39225
+ lines.push(`${indentStr} ... and ${grid.rows.length - MAX_GRID_ROWS} more rows (use expand="${grid.ref}" to see all)`);
39226
+ }
39227
+ return lines.join("\n");
39228
+ }
39229
+ function formatGridRow(row, indent, knownColumns) {
39230
+ const indentStr = " ".repeat(indent);
39231
+ if (row.expandedContent) {
39232
+ const parts = [];
39233
+ if (row.expandedContent.labels.length > 0) {
39234
+ parts.push(row.expandedContent.labels.join(", "));
39034
39235
  }
39035
- for (const child of node.children) {
39036
- if (child.role === "generic" || child.role === "paragraph") {
39037
- const found = findFirstParagraph(child);
39038
- if (found) return found;
39236
+ for (const btn of row.expandedContent.buttons) {
39237
+ parts.push(`${btn.label} [${btn.ref}]`);
39238
+ }
39239
+ return `${indentStr} \u21B3 Expanded [${row.ref}]: ${parts.join(" | ")}`;
39240
+ }
39241
+ const marker = row.isSelected ? "ACTIVE \u2192 Row" : "Row";
39242
+ const actionsPrefix = row.rowActions?.length ? row.rowActions.map((a) => `[\u25B6 ${a.ref}]`).join(" ") + " " : "";
39243
+ const cellsWithValue = row.cells.filter(
39244
+ (c2) => c2.hasValue && (!knownColumns || knownColumns.has(c2.column))
39245
+ );
39246
+ if (cellsWithValue.length > 0) {
39247
+ const cellStrs = cellsWithValue.map((c2) => {
39248
+ if (c2.role === "checkbox") {
39249
+ const icon = c2.value === "checked" ? "\u2611" : "\u2610";
39250
+ return `${icon} [${c2.ref}]`;
39251
+ }
39252
+ if (c2.column === c2.value) {
39253
+ return `${c2.column} [${c2.ref}]`;
39039
39254
  }
39255
+ return `${c2.column} [${c2.ref}]: ${c2.value}`;
39256
+ });
39257
+ return `${indentStr}${actionsPrefix}${marker} [${row.ref}]: ${cellStrs.join(" | ")}`;
39258
+ }
39259
+ return `${indentStr}${actionsPrefix}${marker} [${row.ref}]: (empty row)`;
39260
+ }
39261
+
39262
+ // ../browser-core/src/snapshot-formatter-search.ts
39263
+ function findRelevanceScope(elements, activeElementRef) {
39264
+ if (!activeElementRef) return null;
39265
+ const path5 = [];
39266
+ function findPath(el) {
39267
+ if (el.ref) {
39268
+ path5.push({ ref: el.ref, role: el.role });
39040
39269
  }
39041
- return null;
39042
- };
39043
- const paragraphText = findFirstParagraph(el);
39044
- if (paragraphText) return paragraphText;
39045
- const text = el.text?.trim();
39046
- if (text) return text;
39270
+ if (el.ref === activeElementRef) {
39271
+ return true;
39272
+ }
39273
+ for (const child of el.children) {
39274
+ if (findPath(child)) return true;
39275
+ }
39276
+ if (el.ref) path5.pop();
39277
+ return false;
39278
+ }
39279
+ for (const el of elements) {
39280
+ if (findPath(el)) break;
39281
+ }
39282
+ for (let i = path5.length - 1; i >= 0; i--) {
39283
+ if (SECTION_BOUNDARY_ROLES.has(path5[i].role)) {
39284
+ return path5[i].ref;
39285
+ }
39286
+ }
39047
39287
  return null;
39048
39288
  }
39049
- function extractGridCell(cellEl) {
39050
- const interactiveElements = [];
39051
- const findInteractive = (el) => {
39052
- if (isClickableByAttribute(el) && el.ref && el.role === "generic") {
39053
- const childTexts = [];
39054
- for (const child of el.children) {
39055
- if (child.role === "generic") {
39056
- const childText = child.text || child.inputValue;
39057
- if (childText) childTexts.push(childText);
39289
+ function searchElements(elements, searchTerms, scopeRef = null) {
39290
+ const allMatches = [];
39291
+ const normalizedTerms = searchTerms.map((t) => normalizeSearchText(t)).filter(Boolean);
39292
+ if (normalizedTerms.length === 0) return { matches: [], totalFound: 0 };
39293
+ function searchElement(el, inScope) {
39294
+ const nowInScope = inScope || el.ref === scopeRef || scopeRef === null;
39295
+ if (el.ref && nowInScope) {
39296
+ const searchLabel = getDisplayText(el);
39297
+ const searchText = [searchLabel, el.inputValue].filter(Boolean).join(" ");
39298
+ const normalizedSearchText = normalizeSearchText(searchText);
39299
+ for (const term of normalizedTerms) {
39300
+ if (normalizedSearchText.includes(term)) {
39301
+ allMatches.push({
39302
+ ref: el.ref,
39303
+ role: el.role,
39304
+ label: searchLabel || el.role,
39305
+ term,
39306
+ isInteractive: isSearchInteractiveElement(el)
39307
+ });
39308
+ break;
39058
39309
  }
39059
39310
  }
39060
- interactiveElements.push({
39061
- ref: el.ref,
39062
- column: el.attributes["label"] || childTexts[0] || "",
39063
- value: childTexts.join(" - ") || el.text || el.inputValue || "",
39064
- role: el.role,
39065
- isClickableGeneric: true,
39066
- isActive: isActiveElement(el)
39067
- });
39068
39311
  }
39069
- if (INTERACTIVE_LEAF_ROLES.has(el.role) && el.ref) {
39070
- const value2 = el.role === "checkbox" ? el.attributes["checked"] === "true" ? "checked" : "unchecked" : el.inputValue || el.text || "";
39071
- interactiveElements.push({
39072
- ref: el.ref,
39073
- column: el.attributes["label"] || el.text || "",
39074
- value: value2,
39075
- role: el.role,
39076
- isClickableGeneric: false,
39077
- isActive: isActiveElement(el)
39078
- });
39312
+ for (const child of el.children) {
39313
+ searchElement(child, nowInScope);
39079
39314
  }
39080
- for (const child of el.children) findInteractive(child);
39315
+ }
39316
+ for (const el of elements) {
39317
+ searchElement(el, scopeRef === null);
39318
+ }
39319
+ const totalFound = allMatches.length;
39320
+ allMatches.sort((a, b) => {
39321
+ if (a.isInteractive && !b.isInteractive) return -1;
39322
+ if (!a.isInteractive && b.isInteractive) return 1;
39323
+ return 0;
39324
+ });
39325
+ return {
39326
+ matches: allMatches.slice(0, MAX_SEARCH_MATCHES),
39327
+ totalFound
39081
39328
  };
39082
- findInteractive(cellEl);
39083
- if (interactiveElements.length === 0) {
39084
- const textValue = extractCellText(cellEl);
39085
- if (textValue) {
39086
- return {
39087
- ref: cellEl.ref,
39088
- column: cellEl.text || "",
39089
- value: textValue,
39090
- hasValue: true
39091
- };
39329
+ }
39330
+ function findElementInSections(sections, targetRef, ancestors = []) {
39331
+ for (const section of sections) {
39332
+ const newAncestors = [...ancestors, section];
39333
+ const path5 = newAncestors.map((s) => s.heading);
39334
+ if (section.elements.some((el) => el.ref === targetRef)) {
39335
+ return { section, ancestors: newAncestors, path: path5 };
39092
39336
  }
39093
- return null;
39337
+ if (section.gridInfo && gridContainsRef(section.gridInfo, targetRef)) {
39338
+ return { section, ancestors: newAncestors, path: path5 };
39339
+ }
39340
+ const found = findElementInSections(section.subsections, targetRef, newAncestors);
39341
+ if (found) return found;
39094
39342
  }
39095
- const found = interactiveElements.reduce(
39096
- (best, current) => gridCellPriority(current) > gridCellPriority(best) ? current : best
39097
- );
39098
- const column = stripBlankPatterns(found.column);
39099
- const value = isBlankValue(found.value) ? "" : stripBlankPatterns(found.value).trim();
39100
- const gridcellLabel = cellEl.text ? stripBlankPatterns(cellEl.text).trim() : void 0;
39101
- return {
39102
- ref: found.ref,
39103
- column: column.trim(),
39104
- value,
39105
- hasValue: value !== "",
39106
- gridcellLabel: gridcellLabel || void 0,
39107
- role: found.role
39343
+ return null;
39344
+ }
39345
+ function populateSearchContext(matches, sections) {
39346
+ for (const match of matches) {
39347
+ const result = findElementInSections(sections, match.ref);
39348
+ if (result) {
39349
+ const { section, ancestors, path: path5 } = result;
39350
+ const inFocusedDialog = ancestors.some((s) => s.isFocused);
39351
+ const inBackground = section.isBackground || ancestors.some((s) => s.isBackground);
39352
+ if (inFocusedDialog) {
39353
+ match.context = "focused";
39354
+ } else if (inBackground) {
39355
+ match.context = "background";
39356
+ } else {
39357
+ match.context = "foreground";
39358
+ }
39359
+ const SKIP_HEADINGS = ["generic", "main", "iframe"];
39360
+ match.sectionPath = path5.filter((p) => !SKIP_HEADINGS.includes(p.toLowerCase())).slice(-3).join(" > ");
39361
+ }
39362
+ }
39363
+ }
39364
+ function sortMatchesByContext(matches) {
39365
+ const contextOrder = {
39366
+ focused: 0,
39367
+ foreground: 1,
39368
+ background: 2
39108
39369
  };
39370
+ return [...matches].sort((a, b) => {
39371
+ const aContext = contextOrder[a.context ?? "foreground"] ?? 1;
39372
+ const bContext = contextOrder[b.context ?? "foreground"] ?? 1;
39373
+ if (aContext !== bContext) return aContext - bContext;
39374
+ if (a.isInteractive && !b.isInteractive) return -1;
39375
+ if (!a.isInteractive && b.isInteractive) return 1;
39376
+ return 0;
39377
+ });
39109
39378
  }
39110
- function reconcileCellColumn(cell, knownColumns) {
39111
- if (knownColumns.has(cell.column)) return cell;
39112
- if (cell.gridcellLabel && knownColumns.has(cell.gridcellLabel)) {
39113
- const newValue = cell.column || cell.value;
39114
- const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
39115
- return {
39116
- ...cell,
39117
- column: cell.gridcellLabel,
39118
- value: cleanValue,
39119
- hasValue: cleanValue !== ""
39120
- };
39379
+
39380
+ // ../browser-core/src/snapshot-formatter-sections.ts
39381
+ var FIELD_LABEL_TEXT_ROLES = /* @__PURE__ */ new Set([
39382
+ "generic",
39383
+ "paragraph",
39384
+ "text",
39385
+ "strong",
39386
+ "emphasis",
39387
+ "label",
39388
+ "heading"
39389
+ ]);
39390
+ var MIN_ELEMENTS_TO_COLLAPSE = 20;
39391
+ var DIALOG_ROLES = /* @__PURE__ */ new Set(["dialog", "alertdialog"]);
39392
+ function isVisualHeading(element, siblings, siblingIndex) {
39393
+ if (element.role !== "generic" && element.role !== "paragraph") return false;
39394
+ if (isClickableByAttribute(element)) return false;
39395
+ const text = getVisualHeadingText(element);
39396
+ if (!text || text.length < 3 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
39397
+ if (hasInteractiveDescendants(element)) return false;
39398
+ let interactiveCount = 0;
39399
+ for (let i = siblingIndex + 1; i < siblings.length; i++) {
39400
+ const sib = siblings[i];
39401
+ if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) break;
39402
+ if (sib.role === "generic" || sib.role === "paragraph") {
39403
+ if (!isClickableByAttribute(sib) && !hasInteractiveDescendants(sib)) {
39404
+ const sibText = getVisualHeadingText(sib);
39405
+ if (sibText && sibText.length >= 3 && sibText.length <= VISUAL_HEADING_MAX_LENGTH) {
39406
+ break;
39407
+ }
39408
+ }
39409
+ }
39410
+ if (hasInteractiveDescendants(sib) || isClickableByAttribute(sib)) {
39411
+ interactiveCount++;
39412
+ }
39121
39413
  }
39122
- let bestMatch = "";
39123
- for (const col of knownColumns) {
39124
- if (cell.column.startsWith(col) && col.length > bestMatch.length && // Ensure there's a space separator after the column name (not a partial word match)
39125
- (cell.column.length === col.length || cell.column[col.length] === " ")) {
39126
- bestMatch = col;
39414
+ return interactiveCount >= 3;
39415
+ }
39416
+ function getVisualHeadingText(element) {
39417
+ const direct = element.text;
39418
+ if (direct) return direct;
39419
+ for (const child of element.children) {
39420
+ if (TEXT_CARRYING_ROLES.has(child.role) && child.text) return child.text;
39421
+ }
39422
+ return void 0;
39423
+ }
39424
+ function findNextSectionBoundary(siblings, startIndex) {
39425
+ for (let i = startIndex; i < siblings.length; i++) {
39426
+ const sib = siblings[i];
39427
+ if (sib.role === "heading" || STRUCTURAL_ROLES.has(sib.role)) return i;
39428
+ if (isVisualHeading(sib, siblings, i)) return i;
39429
+ }
39430
+ return siblings.length;
39431
+ }
39432
+ function isFieldLabelCandidate(element) {
39433
+ if (element.role !== "generic" && element.role !== "paragraph" && element.role !== "text" && element.role !== "label" && element.role !== "strong" && element.role !== "emphasis") {
39434
+ return false;
39435
+ }
39436
+ if (isClickableByAttribute(element)) return false;
39437
+ if (hasInteractiveDescendants(element)) return false;
39438
+ const text = getFieldLabelText(element);
39439
+ if (!text || text.length < 1 || text.length > VISUAL_HEADING_MAX_LENGTH) return false;
39440
+ return true;
39441
+ }
39442
+ function getFieldLabelText(element) {
39443
+ if (element.text) return element.text.trim();
39444
+ if (element.inputValue && !INTERACTIVE_ROLES.has(element.role)) return element.inputValue.trim();
39445
+ for (const child of element.children) {
39446
+ if (FIELD_LABEL_TEXT_ROLES.has(child.role)) {
39447
+ if (child.text) return child.text.trim();
39448
+ if (child.inputValue && !INTERACTIVE_ROLES.has(child.role)) return child.inputValue.trim();
39127
39449
  }
39128
39450
  }
39129
- if (bestMatch) {
39130
- const remainder = cell.column.slice(bestMatch.length).trim();
39131
- const newValue = isBlankValue(remainder) ? "" : remainder;
39132
- return {
39133
- ...cell,
39134
- column: bestMatch,
39135
- value: newValue || cell.value,
39136
- hasValue: (newValue || cell.value) !== ""
39137
- };
39451
+ return void 0;
39452
+ }
39453
+ function resolveContainerFieldLabel(container) {
39454
+ const kids = container.children;
39455
+ if (kids.length < 2 || kids.length > 4) return void 0;
39456
+ const firstChild = kids[0];
39457
+ if (isFieldLabelCandidate(firstChild)) {
39458
+ return getFieldLabelText(firstChild);
39138
39459
  }
39139
- return cell;
39460
+ return void 0;
39140
39461
  }
39141
- function reconcileGridCells(grid) {
39142
- if (grid.columns.length === 0) return grid;
39143
- const knownColumns = new Set(grid.columns.map((c2) => c2.name));
39144
- return {
39145
- ...grid,
39146
- rows: grid.rows.map((row) => ({
39147
- ...row,
39148
- cells: row.cells.map((cell) => {
39149
- const reconciled = reconcileCellColumn(cell, knownColumns);
39150
- if (knownColumns.has(reconciled.column)) return reconciled;
39151
- if (grid.columnByPosition && cell.positionInRow !== void 0 && grid.headerChildCount !== void 0 && row.totalChildren === grid.headerChildCount) {
39152
- const positionalColumn = grid.columnByPosition.get(cell.positionInRow);
39153
- if (positionalColumn) {
39154
- const newValue = cell.column || cell.value;
39155
- const cleanValue = isBlankValue(newValue) ? "" : newValue.trim();
39156
- return {
39157
- ...cell,
39158
- column: positionalColumn,
39159
- value: cleanValue,
39160
- hasValue: cleanValue !== ""
39161
- };
39462
+ function findFieldLabelFromSiblings(siblings, interactiveIndex) {
39463
+ for (let i = interactiveIndex - 1; i >= 0; i--) {
39464
+ const sib = siblings[i];
39465
+ if (INTERACTIVE_ROLES.has(sib.role) || STRUCTURAL_ROLES.has(sib.role) || sib.role === "heading") {
39466
+ break;
39467
+ }
39468
+ if (isClickableByAttribute(sib)) {
39469
+ if (sib.role === "generic" || sib.role === "paragraph" || sib.role === "label") {
39470
+ for (let j = i - 1; j >= 0; j--) {
39471
+ const prev = siblings[j];
39472
+ if (INTERACTIVE_ROLES.has(prev.role) || isClickableByAttribute(prev) || STRUCTURAL_ROLES.has(prev.role) || prev.role === "heading") {
39473
+ break;
39474
+ }
39475
+ if (isFieldLabelCandidate(prev)) {
39476
+ return getFieldLabelText(prev);
39162
39477
  }
39478
+ if (prev.children.length >= 1 && prev.children.length <= 4) {
39479
+ const containerLabel = resolveContainerFieldLabel(prev);
39480
+ if (containerLabel) return containerLabel;
39481
+ }
39482
+ if (prev.children.length > 4) break;
39163
39483
  }
39164
- return reconciled;
39165
- })
39166
- }))
39167
- };
39484
+ }
39485
+ break;
39486
+ }
39487
+ if (isFieldLabelCandidate(sib)) {
39488
+ return getFieldLabelText(sib);
39489
+ }
39490
+ if (sib.children.length >= 1 && sib.children.length <= 4) {
39491
+ const containerLabel = resolveContainerFieldLabel(sib);
39492
+ if (containerLabel) return containerLabel;
39493
+ }
39494
+ if (sib.children.length > 4) break;
39495
+ }
39496
+ return void 0;
39168
39497
  }
39169
- function findActiveElement(elements) {
39170
- let deepestActive = null;
39171
- function findDeepest(element) {
39172
- for (const child of element.children) {
39173
- findDeepest(child);
39498
+ function findFieldLabelInContext(_element, siblings, siblingIndex) {
39499
+ return findFieldLabelFromSiblings(siblings, siblingIndex);
39500
+ }
39501
+ function shouldDropInheritedFieldLabel(element, inheritedFieldLabel) {
39502
+ if (!inheritedFieldLabel) return false;
39503
+ if (INTERACTIVE_LEAF_ROLES.has(element.role)) return false;
39504
+ if (!isClickableByAttribute(element)) return false;
39505
+ const directText = getElementText(element);
39506
+ if (directText) {
39507
+ return directText.toLowerCase() !== inheritedFieldLabel.toLowerCase();
39508
+ }
39509
+ const childTexts = [];
39510
+ for (const child of element.children) {
39511
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role)) {
39512
+ const text = getElementText(child);
39513
+ if (text && TEXT_CARRYING_ROLES.has(child.role)) {
39514
+ childTexts.push(text);
39515
+ }
39174
39516
  }
39175
- if (isActiveElement(element)) {
39176
- if (!deepestActive) {
39177
- deepestActive = element;
39517
+ }
39518
+ const composedText = childTexts.join(" ");
39519
+ if (!composedText) return false;
39520
+ return composedText.toLowerCase() !== inheritedFieldLabel.toLowerCase();
39521
+ }
39522
+ function isExpandedElement(element) {
39523
+ return element.attributes["expanded"] === "true" || element.attributes["aria-expanded"] === "true";
39524
+ }
39525
+ function collectExpandedChildren(element, result) {
39526
+ for (const child of element.children) {
39527
+ if (STRUCTURAL_ROLES.has(child.role)) continue;
39528
+ if (INTERACTIVE_LEAF_ROLES.has(child.role) && child.ref) {
39529
+ result.push(formatElement(child));
39530
+ continue;
39531
+ }
39532
+ if (child.role === "listitem" && child.ref) {
39533
+ const text = child.text || child.children.map((c2) => getElementText(c2)).filter((value) => Boolean(value)).join(" | ") || getDisplayText(child);
39534
+ if (text && text !== "listitem") {
39535
+ result.push(formatElement(child, { overrideLabel: text }));
39536
+ continue;
39178
39537
  }
39179
39538
  }
39539
+ if (isClickableByAttribute(child) && child.ref) {
39540
+ result.push(formatElement(child));
39541
+ }
39542
+ collectExpandedChildren(child, result);
39180
39543
  }
39181
- for (const element of elements) {
39182
- findDeepest(element);
39544
+ }
39545
+ function hasActionableChildren(element) {
39546
+ const children2 = [];
39547
+ collectExpandedChildren(element, children2);
39548
+ return children2.length > 0;
39549
+ }
39550
+ function isExpandedWithActionableChildren(element) {
39551
+ if (!isExpandedElement(element)) return false;
39552
+ if (element.role === "combobox") return false;
39553
+ return hasActionableChildren(element);
39554
+ }
39555
+ function composeTriggerLabel(element) {
39556
+ const ariaLabel = element.attributes["aria-label"];
39557
+ if (ariaLabel) return ariaLabel;
39558
+ const fullText = element.text || "";
39559
+ if (fullText) {
39560
+ let collectLeafTexts2 = function(el) {
39561
+ for (const child of el.children) {
39562
+ const text = child.text?.trim();
39563
+ if (text) {
39564
+ leafTexts.push(text);
39565
+ }
39566
+ collectLeafTexts2(child);
39567
+ }
39568
+ };
39569
+ var collectLeafTexts = collectLeafTexts2;
39570
+ const leafTexts = [];
39571
+ collectLeafTexts2(element);
39572
+ if (leafTexts.length > 0) {
39573
+ let stripped = fullText;
39574
+ for (const lt of leafTexts) {
39575
+ stripped = stripped.replace(lt, "");
39576
+ }
39577
+ stripped = stripped.replace(/\s+/g, " ").trim();
39578
+ if (stripped) return stripped;
39579
+ }
39183
39580
  }
39184
- return deepestActive;
39581
+ return element.role;
39185
39582
  }
39186
39583
  function formatElement(element, options) {
39187
- const isClickable = isClickableByAttribute(element) && !INTERACTIVE_ROLES.has(element.role);
39188
- const rawTextSource = options?.overrideLabel ?? (isClickable ? composeLabel(element) : element.text ?? "");
39189
- const textSource = normalizeIconLabelText(element.role, rawTextSource);
39584
+ const isClickable = isActionableClickableGeneric(element);
39585
+ const textSource = getDisplayText(element, { overrideLabel: options?.overrideLabel });
39190
39586
  const extracted = extractDataValue(textSource);
39191
39587
  const normalizedAttributes = Object.entries(element.attributes).filter(
39192
39588
  ([key]) => key !== "ref" && key !== "cursor" && key !== "aria-controls" && key !== "aria-owns" && key !== "options" && key !== "field"
@@ -39220,105 +39616,141 @@ function formatElement(element, options) {
39220
39616
  attributes: normalizedAttributes
39221
39617
  };
39222
39618
  }
39223
- function collectComboboxOptions(element) {
39224
- const options = [];
39225
- function walk(el) {
39226
- if (el.role === "option" && el.text?.trim()) {
39227
- options.push(el.text.trim());
39228
- }
39229
- for (const child of el.children) {
39230
- walk(child);
39231
- }
39232
- }
39233
- for (const child of element.children) {
39234
- walk(child);
39235
- }
39236
- return options.length > 0 ? options : void 0;
39237
- }
39238
- var MIN_ELEMENTS_TO_COLLAPSE = 20;
39239
39619
  function shouldCollapse(section) {
39240
39620
  if (section.containsActive) return false;
39241
39621
  if (section.hasSearchMatch) return false;
39242
39622
  if (section.isBackground) return true;
39243
39623
  if (section.role === "dialog" || section.role === "alertdialog") return false;
39244
- if (section.role === "navigation" || section.role === "menubar" || section.role === "menu") {
39245
- return false;
39246
- }
39624
+ if (section.role === "navigation" || section.role === "menubar" || section.role === "menu") return false;
39247
39625
  if (section.role === "banner") return false;
39248
39626
  if (section.role === "form") return false;
39249
39627
  if (section.elementCount >= MIN_ELEMENTS_TO_COLLAPSE) return true;
39250
39628
  return false;
39251
39629
  }
39252
- function buildSectionTree(elements) {
39253
- const sections = [];
39254
- for (const element of elements) {
39255
- const section = buildSection(element, 0);
39256
- if (section) {
39257
- sections.push(section);
39630
+ function deduplicateFieldLabels(elements) {
39631
+ const byFieldLabel = /* @__PURE__ */ new Map();
39632
+ for (const el of elements) {
39633
+ if (!el.fieldLabel) continue;
39634
+ const existing = byFieldLabel.get(el.fieldLabel);
39635
+ if (existing) {
39636
+ existing.push(el);
39637
+ } else {
39638
+ byFieldLabel.set(el.fieldLabel, [el]);
39639
+ }
39640
+ }
39641
+ for (const [, group] of byFieldLabel) {
39642
+ if (group.length < 2) continue;
39643
+ for (let i = 0; i < group.length; i++) {
39644
+ const el = group[i];
39645
+ if (el.name && el.name !== el.fieldLabel) {
39646
+ el.fieldLabel = `${el.fieldLabel}: ${el.name}`;
39647
+ } else {
39648
+ el.fieldLabel = `${el.fieldLabel} (${i + 1})`;
39649
+ }
39650
+ }
39651
+ }
39652
+ }
39653
+ function buildGridSection(element) {
39654
+ const gridInfo = detectGrid(element);
39655
+ if (!gridInfo) {
39656
+ throw new Error("buildGridSection requires a grid element");
39657
+ }
39658
+ return {
39659
+ ref: element.ref || "",
39660
+ role: element.role,
39661
+ heading: "Grid",
39662
+ level: 0,
39663
+ elements: [],
39664
+ subsections: [],
39665
+ containsActive: gridInfo.containsActive,
39666
+ collapsed: false,
39667
+ isBackground: false,
39668
+ isFocused: false,
39669
+ elementCount: gridInfo.totalRows * Math.max(gridInfo.columns.length, 1),
39670
+ gridInfo
39671
+ };
39672
+ }
39673
+ function collectInteractiveElements(element, elements, subsections, depth, inheritedFieldLabel) {
39674
+ if ((INTERACTIVE_LEAF_ROLES.has(element.role) || isClickableByAttribute(element)) && element.ref) {
39675
+ const effectiveFieldLabel = shouldDropInheritedFieldLabel(element, inheritedFieldLabel) ? void 0 : inheritedFieldLabel;
39676
+ if (INTERACTIVE_LEAF_ROLES.has(element.role) && isExpandedWithActionableChildren(element)) {
39677
+ elements.push(formatElement(element, { overrideLabel: composeTriggerLabel(element), fieldLabel: effectiveFieldLabel }));
39678
+ collectExpandedChildren(element, elements);
39679
+ return;
39258
39680
  }
39259
- }
39260
- markBackgroundSections(sections);
39261
- return sections;
39262
- }
39263
- function markBackgroundSections(sections) {
39264
- const hasActiveSibling = sections.some((s) => s.containsActive);
39265
- if (hasActiveSibling) {
39266
- for (const section of sections) {
39267
- if (!section.containsActive) {
39268
- section.isBackground = true;
39269
- section.collapsed = shouldCollapse(section);
39681
+ if (!isMenuTriggerNoise(element)) {
39682
+ elements.push(formatElement(element, { fieldLabel: effectiveFieldLabel }));
39683
+ }
39684
+ if (!INTERACTIVE_LEAF_ROLES.has(element.role) && isClickableByAttribute(element)) {
39685
+ for (const child of element.children) {
39686
+ collectInteractiveElements(child, elements, subsections, depth + 1, effectiveFieldLabel);
39270
39687
  }
39271
39688
  }
39689
+ return;
39272
39690
  }
39273
- for (const section of sections) {
39274
- markBackgroundSections(section.subsections);
39275
- }
39276
- }
39277
- function markMatchingSections(sections, matchedRefs) {
39278
- for (const section of sections) {
39279
- const hasDirectMatch = section.elements.some((el) => matchedRefs.has(el.ref));
39280
- markMatchingSections(section.subsections, matchedRefs);
39281
- const hasSubsectionMatch = section.subsections.some((s) => s.hasSearchMatch);
39282
- if (hasDirectMatch || hasSubsectionMatch) {
39283
- section.hasSearchMatch = true;
39284
- section.collapsed = false;
39691
+ if (element.role === "heading" || STRUCTURAL_ROLES.has(element.role) || element.role === "grid" || element.role === "treegrid" || element.role === "table" && !isWidgetTable(element)) {
39692
+ const section = buildSection(element, depth);
39693
+ if (section) {
39694
+ subsections.push(section);
39285
39695
  }
39696
+ return;
39286
39697
  }
39287
- }
39288
- var DIALOG_ROLES = /* @__PURE__ */ new Set(["dialog", "alertdialog"]);
39289
- function sortSectionsByFocus(sections) {
39290
- const dialogStack = [];
39291
- function processLevel(levelSections) {
39292
- for (const section of levelSections) {
39293
- const result = processLevel(section.subsections);
39294
- section.subsections = result;
39295
- }
39296
- const dialogs = levelSections.filter((s) => DIALOG_ROLES.has(s.role));
39297
- const nonDialogs = levelSections.filter((s) => !DIALOG_ROLES.has(s.role));
39298
- const focusedDialog = dialogs.find((d) => d.containsActive);
39299
- if (focusedDialog) {
39300
- focusedDialog.isFocused = true;
39301
- dialogStack.unshift(focusedDialog.heading);
39302
- }
39303
- for (const dialog of dialogs) {
39304
- if (!dialog.containsActive) {
39305
- dialogStack.push(dialog.heading);
39698
+ const kids = element.children;
39699
+ for (let ki = 0; ki < kids.length; ki++) {
39700
+ const child = kids[ki];
39701
+ if (isVisualHeading(child, kids, ki)) {
39702
+ const headingText = getVisualHeadingText(child);
39703
+ const sectionElements = [];
39704
+ const sectionSubsections = [];
39705
+ const nextBoundary = findNextSectionBoundary(kids, ki + 1);
39706
+ for (let si = ki + 1; si < nextBoundary; si++) {
39707
+ collectInteractiveElements(kids[si], sectionElements, sectionSubsections, depth + 2);
39708
+ }
39709
+ if (sectionElements.length > 0 || sectionSubsections.length > 0) {
39710
+ const totalElements = sectionElements.length + sectionSubsections.reduce((sum, s) => sum + s.elementCount, 0);
39711
+ subsections.push({
39712
+ heading: headingText ?? "",
39713
+ ref: child.ref,
39714
+ role: "heading",
39715
+ level: 4,
39716
+ elements: sectionElements,
39717
+ subsections: sectionSubsections,
39718
+ collapsed: false,
39719
+ containsActive: false,
39720
+ isBackground: false,
39721
+ isFocused: false,
39722
+ elementCount: totalElements
39723
+ });
39724
+ ki = nextBoundary - 1;
39725
+ } else {
39726
+ collectInteractiveElements(child, elements, subsections, depth + 1);
39727
+ }
39728
+ } else if ((INTERACTIVE_LEAF_ROLES.has(child.role) || isClickableByAttribute(child)) && child.ref) {
39729
+ const rawFieldLabel = findFieldLabelInContext(child, kids, ki) ?? inheritedFieldLabel;
39730
+ const fieldLabel = shouldDropInheritedFieldLabel(child, rawFieldLabel) ? void 0 : rawFieldLabel;
39731
+ if (!isMenuTriggerNoise(child)) {
39732
+ if (INTERACTIVE_LEAF_ROLES.has(child.role) && isExpandedWithActionableChildren(child)) {
39733
+ elements.push(formatElement(child, { overrideLabel: composeTriggerLabel(child), fieldLabel }));
39734
+ collectExpandedChildren(child, elements);
39735
+ } else {
39736
+ elements.push(formatElement(child, { fieldLabel }));
39737
+ }
39738
+ }
39739
+ if (!INTERACTIVE_LEAF_ROLES.has(child.role) && isClickableByAttribute(child)) {
39740
+ for (const grandchild of child.children) {
39741
+ collectInteractiveElements(grandchild, elements, subsections, depth + 1, fieldLabel);
39742
+ }
39306
39743
  }
39744
+ } else {
39745
+ const containerLabel = resolveContainerFieldLabel(child) ?? (child.role === "table" ? findFieldLabelFromSiblings(kids, ki) : void 0) ?? inheritedFieldLabel;
39746
+ collectInteractiveElements(child, elements, subsections, depth + 1, containerLabel);
39307
39747
  }
39308
- const sortedDialogs = dialogs.sort((a, b) => {
39309
- if (a.containsActive && !b.containsActive) return -1;
39310
- if (!a.containsActive && b.containsActive) return 1;
39311
- return 0;
39312
- });
39313
- return [...sortedDialogs, ...nonDialogs];
39314
39748
  }
39315
- const sorted = processLevel(sections);
39316
- return { sorted, dialogStack };
39317
39749
  }
39318
39750
  function buildSection(element, depth) {
39319
39751
  const gridInfo = detectGrid(element);
39320
39752
  if (gridInfo) {
39321
- return buildGridSection(element, gridInfo);
39753
+ return buildGridSection(element);
39322
39754
  }
39323
39755
  const isHeading = element.role === "heading";
39324
39756
  const isStructural = STRUCTURAL_ROLES.has(element.role);
@@ -39356,7 +39788,6 @@ function buildSection(element, depth) {
39356
39788
  ref: child.ref,
39357
39789
  role: "heading",
39358
39790
  level: 4,
39359
- // Visual headings rendered at h6 level (4 + 2 = 6)
39360
39791
  elements: sectionElements,
39361
39792
  subsections: sectionSubsections,
39362
39793
  collapsed: false,
@@ -39400,192 +39831,157 @@ function buildSection(element, depth) {
39400
39831
  const section = {
39401
39832
  ref: element.ref || "",
39402
39833
  role: element.role,
39403
- heading: element.text || (isClickableByAttribute(element) ? composeLabel(element) : element.role),
39834
+ heading: element.text || (isClickableByAttribute(element) ? getDisplayText(element) : element.role),
39404
39835
  level,
39405
39836
  elements: interactiveElements,
39406
39837
  subsections,
39407
39838
  containsActive,
39408
39839
  collapsed: false,
39409
39840
  isBackground: false,
39410
- // Will be set by markBackgroundSections if needed
39411
39841
  isFocused: false,
39412
- // Will be set by sortSectionsByFocus if needed
39413
39842
  elementCount
39414
39843
  };
39844
+ deduplicateFieldLabels(interactiveElements);
39415
39845
  section.collapsed = shouldCollapse(section);
39416
39846
  return section;
39417
39847
  }
39418
- function buildGridSection(element, gridInfo) {
39419
- return {
39420
- ref: element.ref || "",
39421
- role: element.role,
39422
- heading: "Grid",
39423
- level: 0,
39424
- elements: [],
39425
- // Grid cells formatted via gridInfo instead
39426
- subsections: [],
39427
- containsActive: gridInfo.containsActive,
39428
- collapsed: false,
39429
- // Grids use their own summarization
39430
- isBackground: false,
39431
- isFocused: false,
39432
- elementCount: gridInfo.totalRows * Math.max(gridInfo.columns.length, 1),
39433
- gridInfo
39434
- };
39435
- }
39436
- function collectInteractiveElements(element, elements, subsections, depth, inheritedFieldLabel) {
39437
- if ((INTERACTIVE_LEAF_ROLES.has(element.role) || isClickableByAttribute(element)) && element.ref) {
39438
- const effectiveFieldLabel = shouldDropInheritedFieldLabel(element, inheritedFieldLabel) ? void 0 : inheritedFieldLabel;
39439
- if (INTERACTIVE_LEAF_ROLES.has(element.role) && isExpandedWithActionableChildren(element)) {
39440
- elements.push(formatElement(element, { overrideLabel: composeTriggerLabel(element), fieldLabel: effectiveFieldLabel }));
39441
- collectExpandedChildren(element, elements);
39442
- return;
39443
- }
39444
- if (!isMenuTriggerNoise(element)) {
39445
- elements.push(formatElement(element, { fieldLabel: effectiveFieldLabel }));
39446
- }
39447
- if (!INTERACTIVE_LEAF_ROLES.has(element.role) && isClickableByAttribute(element)) {
39448
- for (const child of element.children) {
39449
- collectInteractiveElements(child, elements, subsections, depth + 1, effectiveFieldLabel);
39848
+ function markBackgroundSections(sections) {
39849
+ const hasActiveSibling = sections.some((s) => s.containsActive);
39850
+ if (hasActiveSibling) {
39851
+ for (const section of sections) {
39852
+ if (!section.containsActive) {
39853
+ section.isBackground = true;
39854
+ section.collapsed = shouldCollapse(section);
39450
39855
  }
39451
39856
  }
39452
- return;
39453
39857
  }
39454
- if (element.role === "heading" || STRUCTURAL_ROLES.has(element.role) || element.role === "grid" || element.role === "treegrid" || element.role === "table" && !isWidgetTable(element)) {
39455
- const section = buildSection(element, depth);
39456
- if (section) {
39457
- subsections.push(section);
39458
- }
39459
- return;
39858
+ for (const section of sections) {
39859
+ markBackgroundSections(section.subsections);
39460
39860
  }
39461
- const kids = element.children;
39462
- for (let ki = 0; ki < kids.length; ki++) {
39463
- const child = kids[ki];
39464
- if (isVisualHeading(child, kids, ki)) {
39465
- const headingText = getVisualHeadingText(child);
39466
- const sectionElements = [];
39467
- const sectionSubsections = [];
39468
- const nextBoundary = findNextSectionBoundary(kids, ki + 1);
39469
- for (let si = ki + 1; si < nextBoundary; si++) {
39470
- collectInteractiveElements(kids[si], sectionElements, sectionSubsections, depth + 2);
39471
- }
39472
- if (sectionElements.length > 0 || sectionSubsections.length > 0) {
39473
- const totalElements = sectionElements.length + sectionSubsections.reduce((sum, s) => sum + s.elementCount, 0);
39474
- subsections.push({
39475
- heading: headingText ?? "",
39476
- ref: child.ref,
39477
- role: "heading",
39478
- level: 4,
39479
- elements: sectionElements,
39480
- subsections: sectionSubsections,
39481
- collapsed: false,
39482
- containsActive: false,
39483
- isBackground: false,
39484
- isFocused: false,
39485
- elementCount: totalElements
39486
- });
39487
- ki = nextBoundary - 1;
39488
- } else {
39489
- collectInteractiveElements(child, elements, subsections, depth + 1);
39490
- }
39491
- } else if ((INTERACTIVE_LEAF_ROLES.has(child.role) || isClickableByAttribute(child)) && child.ref) {
39492
- const rawFieldLabel = findFieldLabelInContext(child, kids, ki) ?? inheritedFieldLabel;
39493
- const fieldLabel = shouldDropInheritedFieldLabel(child, rawFieldLabel) ? void 0 : rawFieldLabel;
39494
- if (!isMenuTriggerNoise(child)) {
39495
- if (INTERACTIVE_LEAF_ROLES.has(child.role) && isExpandedWithActionableChildren(child)) {
39496
- elements.push(formatElement(child, { overrideLabel: composeTriggerLabel(child), fieldLabel }));
39497
- collectExpandedChildren(child, elements);
39498
- } else {
39499
- elements.push(formatElement(child, { fieldLabel }));
39500
- }
39501
- }
39502
- if (!INTERACTIVE_LEAF_ROLES.has(child.role) && isClickableByAttribute(child)) {
39503
- for (const grandchild of child.children) {
39504
- collectInteractiveElements(grandchild, elements, subsections, depth + 1, fieldLabel);
39505
- }
39861
+ }
39862
+ function mergeFilterDropdownPairs(elements) {
39863
+ for (let i = elements.length - 2; i >= 0; i--) {
39864
+ const current = elements[i];
39865
+ const next = elements[i + 1];
39866
+ if (current.role === "generic" && current.isClickable && next.role === "img" && next.isClickable && (!next.label || next.label === "img")) {
39867
+ if (!current.fieldLabel && next.fieldLabel) {
39868
+ current.fieldLabel = next.fieldLabel;
39506
39869
  }
39507
- } else {
39508
- const containerLabel = resolveContainerFieldLabel(child) ?? (child.role === "table" ? findFieldLabelFromSiblings(kids, ki) : void 0) ?? inheritedFieldLabel;
39509
- collectInteractiveElements(child, elements, subsections, depth + 1, containerLabel);
39870
+ elements.splice(i + 1, 1);
39510
39871
  }
39511
39872
  }
39512
39873
  }
39513
- function formatSectionOutput(section, indent, showCollapsed) {
39514
- if (section.gridInfo) {
39515
- return formatGridOutput(section.gridInfo, indent);
39874
+ function applyFilterMerge(sections) {
39875
+ for (const section of sections) {
39876
+ mergeFilterDropdownPairs(section.elements);
39877
+ applyFilterMerge(section.subsections);
39516
39878
  }
39517
- const indentStr = " ".repeat(indent);
39518
- const lines = [];
39519
- const headingPrefix = section.level > 0 ? "#".repeat(Math.min(section.level + 2, 6)) + " " : "";
39520
- const refPart = section.ref ? ` [${section.ref}]` : "";
39521
- const focusedPart = section.isFocused ? " [FOCUSED]" : "";
39522
- const backgroundPart = section.isBackground ? " [BACKGROUND]" : "";
39523
- const activePart = section.containsActive && !section.isFocused ? " \u2190 ACTIVE" : "";
39524
- const headerLine = `${indentStr}${headingPrefix}${section.heading}${refPart}${focusedPart}${backgroundPart}${activePart}`;
39525
- lines.push(headerLine);
39526
- if (section.collapsed && !showCollapsed) {
39527
- if (section.isBackground) {
39528
- if (section.ref) {
39529
- lines.push(`${indentStr} (${section.elementCount} elements - use expand="${section.ref}" to see content)`);
39530
- } else {
39531
- lines.push(`${indentStr} (${section.elementCount} elements)`);
39532
- }
39533
- return lines.join("\n");
39879
+ }
39880
+ function buildSectionTree(elements) {
39881
+ const sections = [];
39882
+ for (const element of elements) {
39883
+ const section = buildSection(element, 0);
39884
+ if (section) {
39885
+ sections.push(section);
39534
39886
  }
39535
- const MAX_DIRECT_ELEMENTS = 4;
39536
- const previewElements = section.elements.slice(0, MAX_DIRECT_ELEMENTS);
39537
- const remainingDirectCount = section.elements.length - previewElements.length;
39538
- for (const el of previewElements) {
39539
- lines.push(formatElementLine(el, indent + 1));
39887
+ }
39888
+ markBackgroundSections(sections);
39889
+ applyFilterMerge(sections);
39890
+ return sections;
39891
+ }
39892
+ function markMatchingSections(sections, matchedRefs) {
39893
+ for (const section of sections) {
39894
+ const hasDirectMatch = section.elements.some((el) => matchedRefs.has(el.ref)) || (section.gridInfo ? Array.from(matchedRefs).some((ref) => gridContainsRef(section.gridInfo, ref)) : false);
39895
+ markMatchingSections(section.subsections, matchedRefs);
39896
+ const hasSubsectionMatch = section.subsections.some((s) => s.hasSearchMatch);
39897
+ if (hasDirectMatch || hasSubsectionMatch) {
39898
+ section.hasSearchMatch = true;
39899
+ section.collapsed = false;
39540
39900
  }
39541
- if (remainingDirectCount > 0) {
39542
- lines.push(`${indentStr} ... +${remainingDirectCount} more elements`);
39901
+ }
39902
+ }
39903
+ function sortSectionsByFocus(sections) {
39904
+ const dialogStack = [];
39905
+ function processLevel(levelSections) {
39906
+ for (const section of levelSections) {
39907
+ section.subsections = processLevel(section.subsections);
39543
39908
  }
39544
- for (const subsection of section.subsections) {
39545
- lines.push(`${indentStr} ${formatSubsectionSummary(subsection)}`);
39909
+ const dialogs = levelSections.filter((s) => DIALOG_ROLES.has(s.role));
39910
+ const nonDialogs = levelSections.filter((s) => !DIALOG_ROLES.has(s.role));
39911
+ const focusedDialog = dialogs.find((d) => d.containsActive);
39912
+ if (focusedDialog) {
39913
+ focusedDialog.isFocused = true;
39914
+ dialogStack.unshift(focusedDialog.heading);
39546
39915
  }
39547
- if (section.ref && (section.elements.length > MAX_DIRECT_ELEMENTS || section.subsections.length > 0)) {
39548
- lines.push(`${indentStr} (use expand="${section.ref}" to see all)`);
39916
+ for (const dialog of dialogs) {
39917
+ if (!dialog.containsActive) {
39918
+ dialogStack.push(dialog.heading);
39919
+ }
39549
39920
  }
39550
- return lines.join("\n");
39551
- }
39552
- for (const el of section.elements) {
39553
- lines.push(formatElementLine(el, indent + 1));
39921
+ const sortedDialogs = dialogs.sort((a, b) => {
39922
+ if (a.containsActive && !b.containsActive) return -1;
39923
+ if (!a.containsActive && b.containsActive) return 1;
39924
+ return 0;
39925
+ });
39926
+ return [...sortedDialogs, ...nonDialogs];
39554
39927
  }
39555
- for (const subsection of section.subsections) {
39556
- const subsectionOutput = formatSectionOutput(subsection, indent + 1, showCollapsed);
39557
- if (subsectionOutput) {
39558
- lines.push("");
39559
- lines.push(subsectionOutput);
39928
+ const sorted = processLevel(sections);
39929
+ return { sorted, dialogStack };
39930
+ }
39931
+ function expandSectionByRef(sections, targetRef) {
39932
+ for (const section of sections) {
39933
+ if (section.ref === targetRef) {
39934
+ section.collapsed = false;
39935
+ return true;
39936
+ }
39937
+ if (expandSectionByRef(section.subsections, targetRef)) {
39938
+ return true;
39560
39939
  }
39561
39940
  }
39562
- return lines.join("\n");
39941
+ return false;
39563
39942
  }
39564
- function collectAllElementsFlat(section) {
39565
- const elements = [...section.elements];
39566
- for (const subsection of section.subsections) {
39567
- elements.push(...collectAllElementsFlat(subsection));
39943
+ function findSectionByRef(sections, targetRef) {
39944
+ for (const section of sections) {
39945
+ if (section.ref === targetRef) {
39946
+ return section;
39947
+ }
39948
+ const found = findSectionByRef(section.subsections, targetRef);
39949
+ if (found) {
39950
+ return found;
39951
+ }
39568
39952
  }
39569
- return elements;
39953
+ return null;
39570
39954
  }
39571
- function formatSubsectionSummary(subsection) {
39572
- const refPart = subsection.ref ? ` [${subsection.ref}]` : "";
39573
- if (subsection.gridInfo) {
39574
- const colInfo = subsection.gridInfo.columns.length > 0 ? `${subsection.gridInfo.columns.length} columns` : "unknown columns";
39575
- return `GRID${refPart}: ${subsection.gridInfo.totalRows} rows, ${colInfo}`;
39955
+
39956
+ // ../browser-core/src/snapshot-formatter-render.ts
39957
+ var COLOR_NAMES = [
39958
+ "red",
39959
+ "green",
39960
+ "blue",
39961
+ "yellow",
39962
+ "magenta",
39963
+ "cyan",
39964
+ "orange",
39965
+ "purple",
39966
+ "teal",
39967
+ "pink"
39968
+ ];
39969
+ function findActiveElement(elements) {
39970
+ let deepestActive = null;
39971
+ function findDeepest(element) {
39972
+ for (const child of element.children) {
39973
+ findDeepest(child);
39974
+ }
39975
+ if (element.attributes["active"] !== void 0 || element.rawLine.includes("[active]")) {
39976
+ if (!deepestActive) {
39977
+ deepestActive = element;
39978
+ }
39979
+ }
39576
39980
  }
39577
- const allElements = collectAllElementsFlat(subsection);
39578
- const elementNames = allElements.map((el) => el.label || el.name || el.role).filter(Boolean);
39579
- const heading = subsection.heading || subsection.role;
39580
- if (elementNames.length === 0) {
39581
- return `${heading}${refPart}`;
39981
+ for (const element of elements) {
39982
+ findDeepest(element);
39582
39983
  }
39583
- const MAX_PREVIEW_NAMES = 3;
39584
- const previewNames = elementNames.slice(0, MAX_PREVIEW_NAMES);
39585
- const remainingCount = elementNames.length - previewNames.length;
39586
- const namesList = previewNames.join(", ");
39587
- const moreNote = remainingCount > 0 ? `... +${remainingCount} more` : "";
39588
- return `${heading}${refPart}: ${namesList}${moreNote}`;
39984
+ return deepestActive;
39589
39985
  }
39590
39986
  function formatElementLine(el, indent) {
39591
39987
  const indentStr = " ".repeat(indent);
@@ -39637,23 +40033,102 @@ function formatElementLine(el, indent) {
39637
40033
  }
39638
40034
  return indentStr + parts.join(" ");
39639
40035
  }
39640
- var COLOR_NAMES = [
39641
- "red",
39642
- "green",
39643
- "blue",
39644
- "yellow",
39645
- "magenta",
39646
- "cyan",
39647
- "orange",
39648
- "purple",
39649
- "teal",
39650
- "pink"
39651
- ];
40036
+ function collectAllElementsFlat(section) {
40037
+ const elements = [...section.elements];
40038
+ for (const subsection of section.subsections) {
40039
+ elements.push(...collectAllElementsFlat(subsection));
40040
+ }
40041
+ return elements;
40042
+ }
40043
+ function formatSubsectionSummary(subsection) {
40044
+ const refPart = subsection.ref ? ` [${subsection.ref}]` : "";
40045
+ if (subsection.gridInfo) {
40046
+ const colInfo = subsection.gridInfo.columns.length > 0 ? `${subsection.gridInfo.columns.length} columns` : "unknown columns";
40047
+ return `GRID${refPart}: ${subsection.gridInfo.totalRows} rows, ${colInfo}`;
40048
+ }
40049
+ const allElements = collectAllElementsFlat(subsection);
40050
+ const elementNames = allElements.map((el) => el.label || el.name || el.role).filter(Boolean);
40051
+ const heading = subsection.heading || subsection.role;
40052
+ if (elementNames.length === 0) {
40053
+ return `${heading}${refPart}`;
40054
+ }
40055
+ const MAX_PREVIEW_NAMES = 3;
40056
+ const previewNames = elementNames.slice(0, MAX_PREVIEW_NAMES);
40057
+ const remainingCount = elementNames.length - previewNames.length;
40058
+ const namesList = previewNames.join(", ");
40059
+ const moreNote = remainingCount > 0 ? `... +${remainingCount} more` : "";
40060
+ return `${heading}${refPart}: ${namesList}${moreNote}`;
40061
+ }
40062
+ function formatSectionOutput(section, indent, showCollapsed) {
40063
+ if (section.gridInfo) {
40064
+ return formatGridOutput(section.gridInfo, indent);
40065
+ }
40066
+ const indentStr = " ".repeat(indent);
40067
+ const lines = [];
40068
+ const headingPrefix = section.level > 0 ? "#".repeat(Math.min(section.level + 2, 6)) + " " : "";
40069
+ const refPart = section.ref ? ` [${section.ref}]` : "";
40070
+ const focusedPart = section.isFocused ? " [FOCUSED]" : "";
40071
+ const backgroundPart = section.isBackground ? " [BACKGROUND]" : "";
40072
+ const activePart = section.containsActive && !section.isFocused ? " \u2190 ACTIVE" : "";
40073
+ lines.push(`${indentStr}${headingPrefix}${section.heading}${refPart}${focusedPart}${backgroundPart}${activePart}`);
40074
+ if (section.collapsed && !showCollapsed) {
40075
+ if (section.isBackground) {
40076
+ if (section.ref) {
40077
+ lines.push(`${indentStr} (${section.elementCount} elements - use expand="${section.ref}" to see content)`);
40078
+ } else {
40079
+ lines.push(`${indentStr} (${section.elementCount} elements)`);
40080
+ }
40081
+ return lines.join("\n");
40082
+ }
40083
+ const MAX_DIRECT_ELEMENTS = 4;
40084
+ const previewElements = section.elements.slice(0, MAX_DIRECT_ELEMENTS);
40085
+ const remainingDirectCount = section.elements.length - previewElements.length;
40086
+ for (const el of previewElements) {
40087
+ lines.push(formatElementLine(el, indent + 1));
40088
+ }
40089
+ if (remainingDirectCount > 0) {
40090
+ lines.push(`${indentStr} ... +${remainingDirectCount} more elements`);
40091
+ }
40092
+ for (const subsection of section.subsections) {
40093
+ lines.push(`${indentStr} ${formatSubsectionSummary(subsection)}`);
40094
+ }
40095
+ if (section.ref && (section.elements.length > MAX_DIRECT_ELEMENTS || section.subsections.length > 0)) {
40096
+ lines.push(`${indentStr} (use expand="${section.ref}" to see all)`);
40097
+ }
40098
+ return lines.join("\n");
40099
+ }
40100
+ for (const el of section.elements) {
40101
+ lines.push(formatElementLine(el, indent + 1));
40102
+ }
40103
+ for (const subsection of section.subsections) {
40104
+ const subsectionOutput = formatSectionOutput(subsection, indent + 1, showCollapsed);
40105
+ if (subsectionOutput) {
40106
+ lines.push("");
40107
+ lines.push(subsectionOutput);
40108
+ }
40109
+ }
40110
+ return lines.join("\n");
40111
+ }
40112
+ function countRenderedElements(sections) {
40113
+ let count = 0;
40114
+ for (const section of sections) {
40115
+ count += section.elements.length;
40116
+ if (section.gridInfo) {
40117
+ count += countRenderedGridItems(section.gridInfo);
40118
+ }
40119
+ count += countRenderedElements(section.subsections);
40120
+ }
40121
+ return count;
40122
+ }
40123
+ function countTotalSections(sections) {
40124
+ let count = sections.length;
40125
+ for (const section of sections) {
40126
+ count += countTotalSections(section.subsections);
40127
+ }
40128
+ return count;
40129
+ }
39652
40130
  function generateFrontmatter(elementCount, sectionCount, activeElement, dialogStack, searchMatches, totalMatchCount, snapshotFilePath, probeResult, currentTime, capturedToasts) {
39653
- const activeLabel = activeElement ? normalizeIconLabelText(
39654
- activeElement.role,
39655
- INTERACTIVE_LEAF_ROLES.has(activeElement.role) && isExpandedWithActionableChildren(activeElement) ? composeTriggerLabel(activeElement) : activeElement.text || ""
39656
- ) : "";
40131
+ const activeLabel = activeElement ? (FORM_INPUT_ROLES.has(activeElement.role) || activeElement.role === "button" || activeElement.role === "link") && (activeElement.attributes["expanded"] === "true" || activeElement.attributes["aria-expanded"] === "true") ? getDisplayText(activeElement, { overrideLabel: composeTriggerLabel(activeElement) }) : getDisplayText(activeElement) : "";
39657
40132
  const activeDesc = activeElement ? `${activeElement.ref} ${activeElement.role} "${activeLabel}"` : "None";
39658
40133
  const dialogStackLine = dialogStack.length > 0 ? `
39659
40134
  DIALOG STACK: ${dialogStack.join(" \u2192 ")}` : "";
@@ -39692,9 +40167,8 @@ ${toastLines.join("\n")}`;
39692
40167
  SEARCH RESULTS (${matchCountText}${sortNote}):
39693
40168
  ${matchLines.join("\n")}`;
39694
40169
  }
39695
- const INPUT_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox", "searchbox", "spinbutton"]);
39696
40170
  let typeActionHint = "";
39697
- if (activeElement && INPUT_ROLES.has(activeElement.role)) {
40171
+ if (activeElement && FORM_INPUT_ROLES.has(activeElement.role)) {
39698
40172
  typeActionHint = `
39699
40173
  - Type in active field: browser_type ref=${activeElement.ref} text="..."`;
39700
40174
  }
@@ -39729,20 +40203,59 @@ ACTIONS:
39729
40203
  - Use bash or python_execute on the FULL TREE FILE for detailed element analysis
39730
40204
  ---`;
39731
40205
  }
39732
- function countTotalElements(sections) {
39733
- let count = 0;
39734
- for (const section of sections) {
39735
- count += section.elements.length;
39736
- count += countTotalElements(section.subsections);
40206
+ function disambiguateDuplicateLabels(sections) {
40207
+ const elementEntries = [];
40208
+ function collectElements(section) {
40209
+ for (const el of section.elements) {
40210
+ elementEntries.push({ el, sectionHeading: section.heading });
40211
+ }
40212
+ for (const sub of section.subsections) {
40213
+ collectElements(sub);
40214
+ }
39737
40215
  }
39738
- return count;
39739
- }
39740
- function countTotalSections(sections) {
39741
- let count = sections.length;
39742
40216
  for (const section of sections) {
39743
- count += countTotalSections(section.subsections);
40217
+ collectElements(section);
40218
+ }
40219
+ const SKIP_LABELS = /* @__PURE__ */ new Set(["(icon)", "button", "link", "generic"]);
40220
+ const byLabel = /* @__PURE__ */ new Map();
40221
+ for (const entry of elementEntries) {
40222
+ const label = entry.el.label || entry.el.name;
40223
+ if (!label || label.length === 0) continue;
40224
+ if (SKIP_LABELS.has(label)) continue;
40225
+ if (!entry.el.ref) continue;
40226
+ const existing = byLabel.get(label);
40227
+ if (existing) {
40228
+ existing.push(entry);
40229
+ } else {
40230
+ byLabel.set(label, [entry]);
40231
+ }
40232
+ }
40233
+ for (const [, entries] of byLabel) {
40234
+ if (entries.length < 2) continue;
40235
+ const distinctSections = new Set(entries.map((e) => e.sectionHeading));
40236
+ if (distinctSections.size > 1) {
40237
+ for (const entry of entries) {
40238
+ const currentLabel = entry.el.label || entry.el.name || "";
40239
+ if (entry.el.label) {
40240
+ entry.el.label = `${currentLabel} (in ${entry.sectionHeading})`;
40241
+ } else if (entry.el.name) {
40242
+ entry.el.name = `${currentLabel} (in ${entry.sectionHeading})`;
40243
+ }
40244
+ }
40245
+ } else {
40246
+ const ordinals = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th"];
40247
+ for (let i = 0; i < entries.length; i++) {
40248
+ const entry = entries[i];
40249
+ const ordinal = i < ordinals.length ? ordinals[i] : `${i + 1}th`;
40250
+ const currentLabel = entry.el.label || entry.el.name || "";
40251
+ if (entry.el.label) {
40252
+ entry.el.label = `${currentLabel} (${ordinal})`;
40253
+ } else if (entry.el.name) {
40254
+ entry.el.name = `${currentLabel} (${ordinal})`;
40255
+ }
40256
+ }
40257
+ }
39744
40258
  }
39745
- return count;
39746
40259
  }
39747
40260
  function formatSemanticSnapshot(yaml, options) {
39748
40261
  if (!yaml || yaml.trim() === "") {
@@ -39775,10 +40288,10 @@ function formatSemanticSnapshot(yaml, options) {
39775
40288
  }
39776
40289
  }
39777
40290
  if (options?.probeResult?.ref) {
39778
- const probeRefs = /* @__PURE__ */ new Set([options.probeResult.ref]);
39779
- markMatchingSections(sortedSections, probeRefs);
40291
+ markMatchingSections(sortedSections, /* @__PURE__ */ new Set([options.probeResult.ref]));
39780
40292
  }
39781
- const elementCount = countTotalElements(sortedSections);
40293
+ disambiguateDuplicateLabels(sortedSections);
40294
+ const elementCount = countRenderedElements(sortedSections);
39782
40295
  const sectionCount = countTotalSections(sortedSections);
39783
40296
  const lines = [];
39784
40297
  lines.push(
@@ -39788,7 +40301,6 @@ function formatSemanticSnapshot(yaml, options) {
39788
40301
  activeElement,
39789
40302
  dialogStack,
39790
40303
  sortedMatches,
39791
- // Use re-sorted matches with context
39792
40304
  options?.totalMatchCount,
39793
40305
  options?.snapshotFilePath,
39794
40306
  options?.probeResult,
@@ -39804,7 +40316,11 @@ function formatSemanticSnapshot(yaml, options) {
39804
40316
  lines.push("");
39805
40317
  }
39806
40318
  }
39807
- return lines.join("\n").trim();
40319
+ let result = lines.join("\n").trim();
40320
+ if (options?.offScreenRefs && options.offScreenRefs.size > 0) {
40321
+ result = markOffScreenElements(result, options.offScreenRefs);
40322
+ }
40323
+ return result;
39808
40324
  }
39809
40325
  function expandSection(yaml, sectionRef) {
39810
40326
  if (!yaml || yaml.trim() === "") {
@@ -39818,7 +40334,7 @@ function expandSection(yaml, sectionRef) {
39818
40334
  const sections = buildSectionTree(elements);
39819
40335
  const { sorted: sortedSections, dialogStack } = sortSectionsByFocus(sections);
39820
40336
  expandSectionByRef(sortedSections, sectionRef);
39821
- const elementCount = countTotalElements(sortedSections);
40337
+ const elementCount = countRenderedElements(sortedSections);
39822
40338
  const sectionCount = countTotalSections(sortedSections);
39823
40339
  const lines = [];
39824
40340
  lines.push(generateFrontmatter(elementCount, sectionCount, activeElement, dialogStack));
@@ -39832,29 +40348,26 @@ function expandSection(yaml, sectionRef) {
39832
40348
  }
39833
40349
  return lines.join("\n").trim();
39834
40350
  }
39835
- function expandSectionByRef(sections, targetRef) {
39836
- for (const section of sections) {
39837
- if (section.ref === targetRef) {
39838
- section.collapsed = false;
39839
- return true;
39840
- }
39841
- if (expandSectionByRef(section.subsections, targetRef)) {
39842
- return true;
39843
- }
40351
+ function formatExpandedSectionOutput(section) {
40352
+ const lines = [];
40353
+ const subsectionInfo = section.subsections.length > 0 ? `, ${section.subsections.length} subsections` : "";
40354
+ lines.push(`---`);
40355
+ lines.push(`EXPANDED SECTION: ${section.heading} [${section.ref}]`);
40356
+ lines.push(`Contains ${section.elementCount} elements${subsectionInfo}`);
40357
+ lines.push(`---`);
40358
+ lines.push("");
40359
+ if (section.gridInfo) {
40360
+ lines.push(formatGridOutput(section.gridInfo, 0, true));
40361
+ return lines.join("\n").trim();
39844
40362
  }
39845
- return false;
39846
- }
39847
- function findSectionByRef(sections, targetRef) {
39848
- for (const section of sections) {
39849
- if (section.ref === targetRef) {
39850
- return section;
39851
- }
39852
- const found = findSectionByRef(section.subsections, targetRef);
39853
- if (found) {
39854
- return found;
39855
- }
40363
+ for (const el of section.elements) {
40364
+ lines.push(formatElementLine(el, 0));
39856
40365
  }
39857
- return null;
40366
+ for (const subsection of section.subsections) {
40367
+ lines.push("");
40368
+ lines.push(formatSectionOutput(subsection, 0, false));
40369
+ }
40370
+ return lines.join("\n").trim();
39858
40371
  }
39859
40372
  function expandSectionOnly(yaml, sectionRef) {
39860
40373
  if (!yaml || yaml.trim() === "") {
@@ -39881,79 +40394,142 @@ Section with ref="${sectionRef}" not found in snapshot.
39881
40394
  targetSection.collapsed = false;
39882
40395
  return formatExpandedSectionOutput(targetSection);
39883
40396
  }
39884
- function formatExpandedSectionOutput(section) {
39885
- const lines = [];
39886
- const subsectionInfo = section.subsections.length > 0 ? `, ${section.subsections.length} subsections` : "";
39887
- lines.push(`---`);
39888
- lines.push(`EXPANDED SECTION: ${section.heading} [${section.ref}]`);
39889
- lines.push(`Contains ${section.elementCount} elements${subsectionInfo}`);
39890
- lines.push(`---`);
39891
- lines.push("");
39892
- for (const el of section.elements) {
39893
- lines.push(formatElementLine(el, 0));
39894
- }
39895
- for (const subsection of section.subsections) {
39896
- lines.push("");
39897
- lines.push(formatSectionOutput(subsection, 0, false));
40397
+ function markOffScreenElements(text, offScreenRefs) {
40398
+ if (offScreenRefs.size === 0) return text;
40399
+ return text.replace(/\[(e\d+)\](?! \[off-screen\])/g, (match, ref) => {
40400
+ if (offScreenRefs.has(ref)) {
40401
+ return `[${ref}] [off-screen]`;
40402
+ }
40403
+ return match;
40404
+ });
40405
+ }
40406
+
40407
+ // ../browser-core/src/table-extractor.ts
40408
+ function extractTablesFromSnapshot(yamlOrResponse, options) {
40409
+ const yaml = yamlOrResponse.includes("Page Snapshot:") ? extractSnapshotYaml(yamlOrResponse) : yamlOrResponse;
40410
+ if (!yaml) return null;
40411
+ const elements = parseSnapshot(yaml);
40412
+ if (elements.length === 0) return null;
40413
+ const grids = [];
40414
+ const findGrids = (els) => {
40415
+ for (const el of els) {
40416
+ const grid = detectGrid(el);
40417
+ if (grid && grid.rows.length > 0) {
40418
+ grids.push({ grid, element: el });
40419
+ }
40420
+ if (!grid) {
40421
+ findGrids(el.children);
40422
+ }
40423
+ }
40424
+ };
40425
+ findGrids(elements);
40426
+ if (grids.length === 0) return null;
40427
+ let target;
40428
+ if (options?.ref) {
40429
+ const match = grids.find((g) => g.grid.ref === options.ref);
40430
+ if (!match) return null;
40431
+ target = match;
40432
+ } else {
40433
+ target = grids.reduce(
40434
+ (best, current) => current.grid.rows.length > best.grid.rows.length ? current : best
40435
+ );
39898
40436
  }
39899
- return lines.join("\n").trim();
40437
+ const { columns, rows } = gridInfoToRows(target.grid);
40438
+ if (columns.length === 0) return null;
40439
+ const pagination = detectPaginationInfo(elements, target.grid.ref);
40440
+ return {
40441
+ columns,
40442
+ rows,
40443
+ gridRef: target.grid.ref,
40444
+ pagination: pagination ?? void 0
40445
+ };
39900
40446
  }
39901
- function formatGridOutput(grid, indent) {
39902
- const indentStr = " ".repeat(indent);
39903
- const lines = [];
39904
- const colInfo = grid.columns.length > 0 ? `${grid.columns.length} columns` : "unknown columns";
39905
- lines.push(`${indentStr}GRID [${grid.ref}]: ${grid.totalRows} rows, ${colInfo}`);
39906
- const MAX_DISPLAY_COLUMNS = 10;
39907
- if (grid.columns.length > 0) {
39908
- const displayColumns = grid.columns.slice(0, MAX_DISPLAY_COLUMNS);
39909
- const colsWithRefs = displayColumns.map((c2) => `${c2.name} [${c2.ref}]`).join(", ");
39910
- const moreColsNote = grid.columns.length > MAX_DISPLAY_COLUMNS ? ` (+${grid.columns.length - MAX_DISPLAY_COLUMNS} more)` : "";
39911
- lines.push(`${indentStr} Columns: ${colsWithRefs}${moreColsNote}`);
40447
+ function gridInfoToRows(grid) {
40448
+ const columns = grid.columns.map((c2) => c2.name);
40449
+ if (columns.length === 0) return { columns: [], rows: [] };
40450
+ const columnIndex = /* @__PURE__ */ new Map();
40451
+ for (let i = 0; i < columns.length; i++) {
40452
+ columnIndex.set(columns[i], i);
39912
40453
  }
39913
- lines.push("");
39914
- const knownColumns = grid.columns.length > 0 ? new Set(grid.columns.map((c2) => c2.name)) : void 0;
39915
- const MAX_GRID_ROWS = 30;
39916
- const displayRows = grid.rows.slice(0, MAX_GRID_ROWS);
39917
- for (const row of displayRows) {
39918
- lines.push(formatGridRow(row, indent + 1, knownColumns));
40454
+ const rows = [];
40455
+ for (const row of grid.rows) {
40456
+ const values = new Array(columns.length).fill("");
40457
+ for (const cell of row.cells) {
40458
+ const idx = columnIndex.get(cell.column);
40459
+ if (idx !== void 0) {
40460
+ values[idx] = cleanCellValue(cell.value, cell.gridcellLabel);
40461
+ }
40462
+ }
40463
+ rows.push(values);
39919
40464
  }
39920
- if (grid.rows.length > MAX_GRID_ROWS) {
39921
- lines.push(`${indentStr} ... and ${grid.rows.length - MAX_GRID_ROWS} more rows (use expand="${grid.ref}" to see all)`);
40465
+ return { columns, rows };
40466
+ }
40467
+ var SVG_ARTIFACT_PATTERN = /^Styled\(svg\)$/i;
40468
+ function cleanCellValue(value, gridcellLabel) {
40469
+ if (!value || SVG_ARTIFACT_PATTERN.test(value)) {
40470
+ if (gridcellLabel) {
40471
+ return gridcellLabel.replace(/\s*Styled\(svg\)\s*/gi, "").trim();
40472
+ }
40473
+ return "";
39922
40474
  }
39923
- return lines.join("\n");
40475
+ return value;
39924
40476
  }
39925
- function formatGridRow(row, indent, knownColumns) {
39926
- const indentStr = " ".repeat(indent);
39927
- if (row.expandedContent) {
39928
- const parts = [];
39929
- if (row.expandedContent.labels.length > 0) {
39930
- parts.push(row.expandedContent.labels.join(", "));
40477
+ function formatCSVWithFrontmatter(columns, rows, _options) {
40478
+ const lines = [];
40479
+ lines.push(columns.map(escapeCSVField).join(","));
40480
+ for (const row of rows) {
40481
+ lines.push(row.map(escapeCSVField).join(","));
40482
+ }
40483
+ return lines.join("\n") + "\n";
40484
+ }
40485
+ function appendRowsToCSV(existingCSV, newRows, columns, _options) {
40486
+ const existingLines = existingCSV.split("\n").filter((l) => l.trim() !== "");
40487
+ const dataLines = [];
40488
+ let headerLine = "";
40489
+ for (const line of existingLines) {
40490
+ if (line.startsWith("#")) continue;
40491
+ if (!headerLine) {
40492
+ headerLine = line;
40493
+ continue;
39931
40494
  }
39932
- for (const btn of row.expandedContent.buttons) {
39933
- parts.push(`${btn.label} [${btn.ref}]`);
40495
+ dataLines.push(line);
40496
+ }
40497
+ const existingRowSet = new Set(dataLines);
40498
+ const newRowStrings = newRows.map((row) => row.map(escapeCSVField).join(","));
40499
+ const addedRows = [];
40500
+ for (const rowStr of newRowStrings) {
40501
+ if (!existingRowSet.has(rowStr)) {
40502
+ addedRows.push(rowStr);
40503
+ existingRowSet.add(rowStr);
39934
40504
  }
39935
- return `${indentStr} \u21B3 Expanded [${row.ref}]: ${parts.join(" | ")}`;
39936
40505
  }
39937
- const marker = row.isSelected ? "ACTIVE \u2192 Row" : "Row";
39938
- const actionsPrefix = row.rowActions?.length ? row.rowActions.map((a) => `[\u25B6 ${a.ref}]`).join(" ") + " " : "";
39939
- const cellsWithValue = row.cells.filter(
39940
- (c2) => c2.hasValue && (!knownColumns || knownColumns.has(c2.column))
39941
- );
39942
- if (cellsWithValue.length > 0) {
39943
- const cellStrs = cellsWithValue.map((c2) => {
39944
- if (c2.role === "checkbox") {
39945
- const icon = c2.value === "checked" ? "\u2611" : "\u2610";
39946
- return `${icon} [${c2.ref}]`;
39947
- }
39948
- if (c2.column === c2.value) {
39949
- return `${c2.column} [${c2.ref}]`;
40506
+ const allDataLines = [...dataLines, ...addedRows];
40507
+ const result = [headerLine || columns.map(escapeCSVField).join(","), ...allDataLines];
40508
+ return result.join("\n") + "\n";
40509
+ }
40510
+ function detectPaginationInfo(elements, _gridRef) {
40511
+ const paginationPattern = /(\d+)\s*[-–]\s*(\d+)\s+of\s+([\d,]+)/i;
40512
+ const searchText = (els) => {
40513
+ for (const el of els) {
40514
+ const text = el.text || "";
40515
+ const match = text.match(paginationPattern);
40516
+ if (match) {
40517
+ const total = parseInt(match[3].replace(/,/g, ""), 10);
40518
+ return { showing: `${match[1]}-${match[2]}`, total };
39950
40519
  }
39951
- return `${c2.column} [${c2.ref}]: ${c2.value}`;
39952
- });
39953
- return `${indentStr}${actionsPrefix}${marker} [${row.ref}]: ${cellStrs.join(" | ")}`;
39954
- } else {
39955
- return `${indentStr}${actionsPrefix}${marker} [${row.ref}]: (empty row)`;
40520
+ const childResult = searchText(el.children);
40521
+ if (childResult) return childResult;
40522
+ }
40523
+ return null;
40524
+ };
40525
+ return searchText(elements);
40526
+ }
40527
+ function escapeCSVField(field) {
40528
+ if (!field) return "";
40529
+ if (field.includes(",") || field.includes('"') || field.includes("\n")) {
40530
+ return `"${field.replace(/"/g, '""')}"`;
39956
40531
  }
40532
+ return field;
39957
40533
  }
39958
40534
 
39959
40535
  // ../browser-core/src/snapshot-diff.ts
@@ -41007,6 +41583,42 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41007
41583
  await fs2.writeFile(snapshotFilePath, yaml, "utf-8");
41008
41584
  } catch {
41009
41585
  }
41586
+ let offScreenRefs;
41587
+ try {
41588
+ const viewportSize = this.client.getViewportSize();
41589
+ const offScreen = await this.client.evaluate(
41590
+ `() => {
41591
+ const vw = ${viewportSize.width};
41592
+ const vh = ${viewportSize.height};
41593
+ const offscreen = [];
41594
+ const els = document.querySelectorAll('[aria-ref]');
41595
+ for (const el of els) {
41596
+ const ref = el.getAttribute('aria-ref');
41597
+ if (!ref) continue;
41598
+ const r = el.getBoundingClientRect();
41599
+ if (r.width === 0 && r.height === 0) continue;
41600
+ if (r.right < 0 || r.left > vw || r.bottom < 0 || r.top > vh) {
41601
+ offscreen.push(ref);
41602
+ }
41603
+ }
41604
+ return offscreen;
41605
+ }`,
41606
+ { signal: this.signal }
41607
+ );
41608
+ if (typeof offScreen === "string") {
41609
+ try {
41610
+ const parsed = JSON.parse(offScreen);
41611
+ if (Array.isArray(parsed) && parsed.length > 0) {
41612
+ offScreenRefs = new Set(parsed);
41613
+ }
41614
+ } catch {
41615
+ }
41616
+ }
41617
+ } catch (e) {
41618
+ this.logger?.debug?.("[browser_snapshot] Off-screen detection failed", {
41619
+ error: e instanceof Error ? e.message : String(e)
41620
+ });
41621
+ }
41010
41622
  const now = (/* @__PURE__ */ new Date()).toISOString();
41011
41623
  const semanticText = formatSemanticSnapshot(yaml, {
41012
41624
  searchMatches: searchMatches.length > 0 ? searchMatches : void 0,
@@ -41014,7 +41626,8 @@ ${effectiveShowCoordinateGrid ? "Coordinate grid enabled - read coordinates from
41014
41626
  snapshotFilePath,
41015
41627
  probeResult,
41016
41628
  currentTime: now,
41017
- capturedToasts: this.client.getCapturedToasts?.() ?? []
41629
+ capturedToasts: this.client.getCapturedToasts?.() ?? [],
41630
+ offScreenRefs
41018
41631
  });
41019
41632
  if (hasImages) {
41020
41633
  const resolutionNote = buildResolutionNote(
@@ -41728,7 +42341,7 @@ function getBrowserToolDefinitions() {
41728
42341
  },
41729
42342
  toRef: {
41730
42343
  type: "string",
41731
- description: "Scroll the page so this element is centered vertically in the viewport."
42344
+ description: "Scroll the page so this element is centered in the viewport. Handles both vertical and horizontal scrolling automatically \u2014 works for grid columns that are horizontally off-screen."
41732
42345
  },
41733
42346
  x: {
41734
42347
  type: "number",
@@ -41898,6 +42511,136 @@ function getBrowserToolDefinitionsWithLifecycle() {
41898
42511
  ];
41899
42512
  }
41900
42513
 
42514
+ // ../browser-core/src/tools/dispatcher.ts
42515
+ async function dispatchBrowserTool(executor, toolName, args) {
42516
+ switch (toolName) {
42517
+ case "browser_navigate":
42518
+ return executor.navigate(args.url);
42519
+ case "browser_navigate_back":
42520
+ return executor.navigateBack();
42521
+ case "browser_snapshot":
42522
+ return executor.snapshot({
42523
+ mode: args.mode,
42524
+ expand: args.expand,
42525
+ search: args.search,
42526
+ showCoordinateGrid: args.showCoordinateGrid,
42527
+ probeAt: args.probeAt
42528
+ });
42529
+ case "browser_screenshot":
42530
+ return executor.screenshot({
42531
+ fullPage: args.fullPage,
42532
+ element: args.element,
42533
+ ref: args.ref,
42534
+ label: args.label,
42535
+ returnImage: args.returnImage
42536
+ });
42537
+ case "browser_evaluate":
42538
+ return executor.evaluate({
42539
+ expression: args.function,
42540
+ element: args.element,
42541
+ ref: args.ref
42542
+ });
42543
+ case "browser_console_messages":
42544
+ return executor.consoleMessages({
42545
+ onlyErrors: args.onlyErrors
42546
+ });
42547
+ case "browser_network_requests":
42548
+ return executor.networkRequests();
42549
+ case "browser_click":
42550
+ return executor.click({
42551
+ ref: args.ref,
42552
+ x: args.x,
42553
+ y: args.y,
42554
+ element: args.element,
42555
+ doubleClick: args.doubleClick,
42556
+ button: args.button,
42557
+ modifiers: args.modifiers
42558
+ });
42559
+ case "browser_hover":
42560
+ return executor.hover({
42561
+ ref: args.ref,
42562
+ element: args.element
42563
+ });
42564
+ case "browser_drag":
42565
+ return executor.drag({
42566
+ startRef: args.startRef,
42567
+ endRef: args.endRef,
42568
+ startElement: args.startElement,
42569
+ endElement: args.endElement,
42570
+ startX: args.startX,
42571
+ startY: args.startY,
42572
+ endX: args.endX,
42573
+ endY: args.endY
42574
+ });
42575
+ case "browser_type":
42576
+ return executor.type({
42577
+ ref: args.ref,
42578
+ text: args.text,
42579
+ element: args.element,
42580
+ submit: args.submit,
42581
+ delay: args.delay
42582
+ });
42583
+ case "browser_press_key":
42584
+ return executor.pressKey(args.key);
42585
+ case "browser_fill_form":
42586
+ return executor.fillForm(
42587
+ args.fields
42588
+ );
42589
+ case "browser_select_option":
42590
+ return executor.selectOption({
42591
+ ref: args.ref,
42592
+ value: args.value,
42593
+ element: args.element
42594
+ });
42595
+ case "browser_file_upload":
42596
+ return executor.fileUpload(args.paths);
42597
+ case "browser_scroll":
42598
+ return executor.scroll({
42599
+ direction: args.direction,
42600
+ amount: args.amount,
42601
+ withinRef: args.withinRef,
42602
+ toRef: args.toRef,
42603
+ x: args.x,
42604
+ y: args.y
42605
+ });
42606
+ case "browser_handle_dialog":
42607
+ return executor.handleDialog({
42608
+ action: args.action,
42609
+ promptText: args.promptText
42610
+ });
42611
+ case "browser_dismiss_overlay":
42612
+ return executor.dismissOverlay({
42613
+ preferredStrategy: args.preferredStrategy
42614
+ });
42615
+ case "browser_wait_for":
42616
+ return executor.waitFor({
42617
+ timeSec: args.time,
42618
+ text: args.text,
42619
+ textGone: args.textGone,
42620
+ selector: args.selector,
42621
+ state: args.state,
42622
+ timeout: args.timeout
42623
+ });
42624
+ case "browser_close":
42625
+ return executor.close();
42626
+ case "browser_resize":
42627
+ return executor.resize(args.width, args.height);
42628
+ case "browser_tabs":
42629
+ return executor.tabs({
42630
+ action: args.action,
42631
+ index: args.index
42632
+ });
42633
+ case "browser_list_downloads":
42634
+ return executor.listDownloads();
42635
+ case "browser_read_download":
42636
+ return executor.readDownload(args.downloadId);
42637
+ case "browser_pdf_read":
42638
+ return executor.fetchPdfText(args.url);
42639
+ default:
42640
+ throw new Error(`Unknown browser tool: ${toolName}`);
42641
+ }
42642
+ }
42643
+
41901
42644
  // ../browser-core/src/playwright-client.ts
41902
42645
  import {
41903
42646
  chromium as playwrightChromium
@@ -43018,6 +43761,29 @@ function appendAttributeIfMissing(line, key, value) {
43018
43761
  const baseLine = hasTrailingColon ? line.replace(/:\s*$/, "") : line;
43019
43762
  return `${baseLine} [${key}=${value}]${hasTrailingColon ? ":" : ""}`;
43020
43763
  }
43764
+ function findLabelFromYamlSiblings(elements, targetRef) {
43765
+ const findParent = (els, parent2) => {
43766
+ for (const el of els) {
43767
+ if (el.ref === targetRef) return parent2;
43768
+ const found = findParent(el.children, el);
43769
+ if (found) return found;
43770
+ }
43771
+ return null;
43772
+ };
43773
+ const parent = findParent(elements, null);
43774
+ if (!parent) return void 0;
43775
+ const targetIdx = parent.children.findIndex((c2) => c2.ref === targetRef);
43776
+ if (targetIdx < 0) return void 0;
43777
+ for (let i = targetIdx - 1; i >= 0; i--) {
43778
+ const sib = parent.children[i];
43779
+ const sibText = sib.text?.trim();
43780
+ if (sibText && sibText.length > 0 && sibText.length < 80 && /[a-zA-Z0-9]/.test(sibText)) {
43781
+ if (FORM_FIELD_ROLES.has(sib.role)) continue;
43782
+ return sibText;
43783
+ }
43784
+ }
43785
+ return void 0;
43786
+ }
43021
43787
  async function augmentUnlabeledElements(page, yaml, logger2) {
43022
43788
  try {
43023
43789
  const elements = parseSnapshot(yaml);
@@ -43323,6 +44089,9 @@ async function augmentGridCellValues(page, yaml, logger2) {
43323
44089
  }
43324
44090
  }
43325
44091
  var FORM_FIELD_ROLES = /* @__PURE__ */ new Set(["textbox", "combobox", "searchbox"]);
44092
+ function splitCamelCase(name) {
44093
+ return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\s+/g, " ").trim();
44094
+ }
43326
44095
  async function augmentFormFieldLabels(page, yaml, logger2) {
43327
44096
  try {
43328
44097
  const elements = parseSnapshot(yaml);
@@ -43340,16 +44109,30 @@ async function augmentFormFieldLabels(page, yaml, logger2) {
43340
44109
  walkForFields(el);
43341
44110
  }
43342
44111
  if (formFieldRefs.length === 0) return yaml;
43343
- logger2.debug("[DirectPlaywright] Augmenting form field labels", {
44112
+ const glyphFields = formFieldRefs.filter((f) => f.text && !/[a-zA-Z0-9]/.test(f.text));
44113
+ const displayNameIdx = yaml.indexOf("Display Name");
44114
+ const yamlAroundDisplayName = displayNameIdx >= 0 ? yaml.slice(Math.max(0, displayNameIdx - 50), displayNameIdx + 200) : "NOT FOUND";
44115
+ logger2.warn("[DirectPlaywright] augmentFormFieldLabels called", {
43344
44116
  count: formFieldRefs.length,
43345
- refs: formFieldRefs.slice(0, 10).map((f) => f.ref)
44117
+ glyphCount: glyphFields.length,
44118
+ allTexts: formFieldRefs.map((f) => `${f.ref}:${JSON.stringify(f.text)}`),
44119
+ hasDisplayName: yaml.includes("Display Name"),
44120
+ yamlHasGlyph: yaml.includes("\u268A"),
44121
+ yamlAroundDisplayName
43346
44122
  });
43347
44123
  let augmented = yaml;
43348
44124
  for (const { ref, role, text } of formFieldRefs) {
43349
44125
  try {
44126
+ const isGlyphField = text && !/[a-zA-Z0-9]/.test(text);
44127
+ if (isGlyphField) {
44128
+ logger2.info("[DirectPlaywright] Glyph-labeled field entering augmentation", { ref, role, text });
44129
+ }
43350
44130
  const locator = page.locator(`aria-ref=${ref}`);
43351
44131
  const count = await locator.count();
43352
- if (count === 0) continue;
44132
+ if (count === 0) {
44133
+ if (isGlyphField) logger2.info("[DirectPlaywright] Glyph field: locator count=0, skipping", { ref });
44134
+ continue;
44135
+ }
43353
44136
  const label = await locator.first().evaluate((el) => {
43354
44137
  if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
43355
44138
  const labels = el.labels;
@@ -43358,6 +44141,30 @@ async function augmentFormFieldLabels(page, yaml, logger2) {
43358
44141
  if (labelText) return labelText;
43359
44142
  }
43360
44143
  }
44144
+ {
44145
+ const precedingLabels = [];
44146
+ let prevEl = el.previousElementSibling;
44147
+ while (prevEl) {
44148
+ if (prevEl.tagName === "LABEL") {
44149
+ precedingLabels.unshift(prevEl);
44150
+ } else {
44151
+ break;
44152
+ }
44153
+ prevEl = prevEl.previousElementSibling;
44154
+ }
44155
+ if (precedingLabels.length > 1) {
44156
+ const selected = precedingLabels.find((l) => {
44157
+ const cls = l.className.toLowerCase();
44158
+ return cls.includes("selected") || cls.includes("active") || cls.includes("current") || cls.includes("checked");
44159
+ });
44160
+ if (selected?.textContent?.trim()) return selected.textContent.trim();
44161
+ const first = precedingLabels[0];
44162
+ if (first?.textContent?.trim()) return first.textContent.trim();
44163
+ } else if (precedingLabels.length === 1) {
44164
+ const labelText = precedingLabels[0].textContent?.trim();
44165
+ if (labelText) return labelText;
44166
+ }
44167
+ }
43361
44168
  const labelledBy = el.getAttribute("aria-labelledby");
43362
44169
  if (labelledBy) {
43363
44170
  const labelEl = document.getElementById(labelledBy);
@@ -43365,31 +44172,101 @@ async function augmentFormFieldLabels(page, yaml, logger2) {
43365
44172
  }
43366
44173
  const ariaLabel = el.getAttribute("aria-label");
43367
44174
  const placeholder = el.getAttribute("placeholder");
43368
- if (ariaLabel && ariaLabel !== placeholder) return ariaLabel;
43369
- let sibling = el.previousElementSibling;
44175
+ if (ariaLabel && ariaLabel !== placeholder && /[a-zA-Z0-9]/.test(ariaLabel)) return ariaLabel;
43370
44176
  let target = el;
43371
- for (let i = 0; i < 3 && target; i++) {
43372
- sibling = target.previousElementSibling;
43373
- if (sibling) {
44177
+ for (let depth = 0; depth < 3 && target; depth++) {
44178
+ let sibling = target.previousElementSibling;
44179
+ for (let sibCount = 0; sibling && sibCount < 3; sibCount++) {
43374
44180
  const sibText = sibling.textContent?.trim();
43375
44181
  if (sibText && sibText.length > 0 && sibText.length < 80 && !sibling.querySelector("input, textarea, select, button")) {
43376
44182
  return sibText;
43377
44183
  }
44184
+ if (sibling.querySelector("input, textarea, select, button")) break;
44185
+ sibling = sibling.previousElementSibling;
43378
44186
  }
43379
44187
  target = target.parentElement;
43380
44188
  }
43381
44189
  return void 0;
43382
44190
  });
44191
+ if (isGlyphField) {
44192
+ logger2.info("[DirectPlaywright] Glyph field: DOM evaluate result", { ref, label, text });
44193
+ }
43383
44194
  if (label && label !== text) {
43384
44195
  augmented = updateElementLineByRef(
43385
44196
  augmented,
43386
44197
  ref,
43387
44198
  (line) => injectElementLabel(line, role, label)
43388
44199
  );
44200
+ if (isGlyphField) {
44201
+ logger2.info("[DirectPlaywright] Glyph field: injected DOM label", { ref, label });
44202
+ }
44203
+ }
44204
+ const hasGlyphOnlyLabel = text && !/[a-zA-Z0-9]/.test(text);
44205
+ if (hasGlyphOnlyLabel && (!label || label === text || !/[a-zA-Z0-9]/.test(label))) {
44206
+ const yamlLabel = findLabelFromYamlSiblings(elements, ref);
44207
+ logger2.info("[DirectPlaywright] YAML sibling fallback for glyph field", {
44208
+ ref,
44209
+ text,
44210
+ label,
44211
+ yamlLabel
44212
+ });
44213
+ if (yamlLabel) {
44214
+ augmented = updateElementLineByRef(
44215
+ augmented,
44216
+ ref,
44217
+ (line) => injectElementLabel(line, role, yamlLabel)
44218
+ );
44219
+ }
43389
44220
  }
43390
44221
  } catch {
43391
44222
  }
43392
44223
  }
44224
+ try {
44225
+ const postElements = parseSnapshot(augmented);
44226
+ const labeledFields = [];
44227
+ const walkForLabeled = (el) => {
44228
+ if (FORM_FIELD_ROLES.has(el.role) && el.ref && el.text) {
44229
+ labeledFields.push({ ref: el.ref, role: el.role, label: el.text });
44230
+ }
44231
+ for (const child of el.children) walkForLabeled(child);
44232
+ };
44233
+ for (const el of postElements) walkForLabeled(el);
44234
+ const byLabel = /* @__PURE__ */ new Map();
44235
+ for (const f of labeledFields) {
44236
+ const existing = byLabel.get(f.label);
44237
+ if (existing) {
44238
+ existing.push({ ref: f.ref, role: f.role });
44239
+ } else {
44240
+ byLabel.set(f.label, [{ ref: f.ref, role: f.role }]);
44241
+ }
44242
+ }
44243
+ for (const [label, fields] of byLabel) {
44244
+ if (fields.length < 2) continue;
44245
+ for (const field of fields) {
44246
+ try {
44247
+ const locator = page.locator(`aria-ref=${field.ref}`);
44248
+ const count = await locator.count();
44249
+ if (count === 0) continue;
44250
+ const disambiguation = await locator.first().evaluate((el) => {
44251
+ const placeholder = el.getAttribute("placeholder") || void 0;
44252
+ const ariaLabel = el.getAttribute("aria-label") || void 0;
44253
+ const name = el.getAttribute("name") || void 0;
44254
+ return { placeholder, ariaLabel, name };
44255
+ });
44256
+ const disambig = disambiguation.placeholder || disambiguation.ariaLabel || (disambiguation.name ? splitCamelCase(disambiguation.name) : void 0);
44257
+ if (disambig && disambig !== label) {
44258
+ augmented = updateElementLineByRef(
44259
+ augmented,
44260
+ field.ref,
44261
+ (line) => injectElementLabel(line, field.role, disambig)
44262
+ );
44263
+ }
44264
+ } catch {
44265
+ }
44266
+ }
44267
+ }
44268
+ } catch {
44269
+ }
43393
44270
  return augmented;
43394
44271
  } catch (err) {
43395
44272
  logger2.debug("[DirectPlaywright] Form field label augmentation failed", {
@@ -43594,6 +44471,148 @@ async function augmentHiddenSelectOptions(page, yaml, logger2) {
43594
44471
  return yaml;
43595
44472
  }
43596
44473
  }
44474
+ async function enrichModalDialogElements(page, logger2) {
44475
+ try {
44476
+ const enrichedCount = await page.evaluate(() => {
44477
+ let count = 0;
44478
+ const MAX_ENRICHMENTS = 3;
44479
+ function shouldSkip(el) {
44480
+ if (el.getAttribute("role") === "dialog" || el.getAttribute("role") === "alertdialog") return true;
44481
+ if (el.getAttribute("data-enriched-dialog") === "true") return true;
44482
+ return false;
44483
+ }
44484
+ function isVisible(el) {
44485
+ const style = window.getComputedStyle(el);
44486
+ if (style.display === "none" || style.visibility === "hidden") return false;
44487
+ if (el instanceof HTMLElement && el.offsetWidth === 0 && el.offsetHeight === 0) return false;
44488
+ return true;
44489
+ }
44490
+ function enrichAsDialog(el) {
44491
+ if (count >= MAX_ENRICHMENTS) return;
44492
+ if (shouldSkip(el)) return;
44493
+ if (!isVisible(el)) return;
44494
+ el.setAttribute("role", "dialog");
44495
+ const heading = el.querySelector("h1, h2, h3, h4, h5, h6");
44496
+ if (heading?.textContent?.trim()) {
44497
+ el.setAttribute("aria-label", heading.textContent.trim());
44498
+ }
44499
+ el.setAttribute("data-enriched-dialog", "true");
44500
+ count++;
44501
+ }
44502
+ function hasInteractiveContent(el) {
44503
+ return el.querySelector('input, textarea, select, button, a[href], [role="button"], [role="link"], [role="textbox"], [role="combobox"]') !== null;
44504
+ }
44505
+ const CLASS_PATTERNS = [
44506
+ /\bmodal(?![-_]?(backdrop|mask|overlay|fade|bg|background))\b/i,
44507
+ /\bdialog\b/i,
44508
+ /\bpopup\b/i,
44509
+ /\blightbox\b/i,
44510
+ /\bdrawer\b/i
44511
+ ];
44512
+ const FRAMEWORK_SELECTORS = [
44513
+ ".cdk-overlay-pane",
44514
+ ".mat-dialog-container",
44515
+ ".mat-mdc-dialog-container",
44516
+ ".ui-dialog",
44517
+ ".modal-dialog",
44518
+ ".ant-modal-content",
44519
+ ".el-dialog",
44520
+ ".v-dialog",
44521
+ ".p-dialog"
44522
+ ];
44523
+ for (const selector of FRAMEWORK_SELECTORS) {
44524
+ if (count >= MAX_ENRICHMENTS) break;
44525
+ const els = document.querySelectorAll(selector);
44526
+ for (const el of els) {
44527
+ if (count >= MAX_ENRICHMENTS) break;
44528
+ if (hasInteractiveContent(el)) {
44529
+ enrichAsDialog(el);
44530
+ }
44531
+ }
44532
+ }
44533
+ if (count < MAX_ENRICHMENTS) {
44534
+ const allElements = document.querySelectorAll("*");
44535
+ for (const el of allElements) {
44536
+ if (count >= MAX_ENRICHMENTS) break;
44537
+ const className = el.className;
44538
+ if (typeof className !== "string") continue;
44539
+ if (CLASS_PATTERNS.some((pattern) => pattern.test(className))) {
44540
+ if (hasInteractiveContent(el)) {
44541
+ enrichAsDialog(el);
44542
+ }
44543
+ }
44544
+ }
44545
+ }
44546
+ if (count < MAX_ENRICHMENTS) {
44547
+ const allFixed = document.querySelectorAll("*");
44548
+ for (const el of allFixed) {
44549
+ if (count >= MAX_ENRICHMENTS) break;
44550
+ if (!(el instanceof HTMLElement)) continue;
44551
+ const style = window.getComputedStyle(el);
44552
+ if (style.position !== "fixed" && style.position !== "absolute") continue;
44553
+ const rect = el.getBoundingClientRect();
44554
+ const viewportW = window.innerWidth;
44555
+ const viewportH = window.innerHeight;
44556
+ const coversViewport = rect.width >= viewportW * 0.8 && rect.height >= viewportH * 0.8;
44557
+ if (!coversViewport) continue;
44558
+ const bg = style.backgroundColor;
44559
+ const opacity = parseFloat(style.opacity);
44560
+ const isSemiTransparent = bg.includes("rgba") && !bg.includes("rgba(0, 0, 0, 0)") && bg.match(/,\s*([\d.]+)\)/) && parseFloat(bg.match(/,\s*([\d.]+)\)/)?.[1] || "1") < 1 || opacity < 1 && opacity > 0;
44561
+ if (!isSemiTransparent) continue;
44562
+ const nextSib = el.nextElementSibling;
44563
+ if (nextSib && nextSib instanceof HTMLElement && isVisible(nextSib) && hasInteractiveContent(nextSib)) {
44564
+ enrichAsDialog(nextSib);
44565
+ continue;
44566
+ }
44567
+ if (el.parentElement) {
44568
+ const backdropZ = parseInt(style.zIndex) || 0;
44569
+ for (const sibling of el.parentElement.children) {
44570
+ if (sibling === el || !(sibling instanceof HTMLElement)) continue;
44571
+ const sibStyle = window.getComputedStyle(sibling);
44572
+ const sibZ = parseInt(sibStyle.zIndex) || 0;
44573
+ if (sibZ > backdropZ && isVisible(sibling) && hasInteractiveContent(sibling)) {
44574
+ enrichAsDialog(sibling);
44575
+ break;
44576
+ }
44577
+ }
44578
+ }
44579
+ }
44580
+ }
44581
+ if (count < MAX_ENRICHMENTS) {
44582
+ const allElements = document.querySelectorAll("*");
44583
+ for (const el of allElements) {
44584
+ if (count >= MAX_ENRICHMENTS) break;
44585
+ if (!(el instanceof HTMLElement)) continue;
44586
+ if (el.getAttribute("data-enriched-dialog") === "true") continue;
44587
+ if (el.getAttribute("role") === "dialog" || el.getAttribute("role") === "alertdialog") continue;
44588
+ const style = window.getComputedStyle(el);
44589
+ if (style.position !== "fixed" && style.position !== "absolute") continue;
44590
+ const zIndex = parseInt(style.zIndex) || 0;
44591
+ if (zIndex <= 100) continue;
44592
+ if (!isVisible(el)) continue;
44593
+ const rect = el.getBoundingClientRect();
44594
+ if (rect.width < 200 || rect.height < 200) continue;
44595
+ const viewportW = window.innerWidth;
44596
+ const viewportH = window.innerHeight;
44597
+ if (rect.width >= viewportW * 0.95 && rect.height >= viewportH * 0.95) continue;
44598
+ if (hasInteractiveContent(el)) {
44599
+ enrichAsDialog(el);
44600
+ }
44601
+ }
44602
+ }
44603
+ return count;
44604
+ });
44605
+ if (enrichedCount > 0) {
44606
+ logger2.debug("[DirectPlaywright] Enriched modal dialog elements", {
44607
+ count: enrichedCount
44608
+ });
44609
+ }
44610
+ } catch (err) {
44611
+ logger2.debug("[DirectPlaywright] Modal dialog enrichment failed", {
44612
+ error: errorMessage2(err)
44613
+ });
44614
+ }
44615
+ }
43597
44616
  async function enrichInteractiveSVGElements(page, logger2) {
43598
44617
  try {
43599
44618
  const enrichedCount = await page.evaluate(() => {
@@ -43989,7 +45008,8 @@ var PlaywrightClient = class _PlaywrightClient {
43989
45008
  extensions,
43990
45009
  highlightInteractions,
43991
45010
  cursorOverlay,
43992
- extraHTTPHeaders
45011
+ extraHTTPHeaders,
45012
+ httpCredentials
43993
45013
  } = options;
43994
45014
  const resolvedBrowserMode = browserMode ?? "headless";
43995
45015
  const resolvedViewport = viewport ?? DEFAULT_VIEWPORT;
@@ -44002,7 +45022,8 @@ var PlaywrightClient = class _PlaywrightClient {
44002
45022
  extensions,
44003
45023
  highlightInteractions,
44004
45024
  cursorOverlay,
44005
- extraHTTPHeaders
45025
+ extraHTTPHeaders,
45026
+ httpCredentials
44006
45027
  };
44007
45028
  this.storageStatePath = storageStatePath;
44008
45029
  this.pageClosedIntentionally = false;
@@ -44075,6 +45096,9 @@ var PlaywrightClient = class _PlaywrightClient {
44075
45096
  if (extraHTTPHeaders && Object.keys(extraHTTPHeaders).length > 0) {
44076
45097
  contextOptions.extraHTTPHeaders = extraHTTPHeaders;
44077
45098
  }
45099
+ if (httpCredentials) {
45100
+ contextOptions.httpCredentials = httpCredentials;
45101
+ }
44078
45102
  this.logger.info("[DirectPlaywright] Using pooled browser via lease", {
44079
45103
  leaseId: this.browserLease.leaseId,
44080
45104
  browserId: this.browserLease.browserId
@@ -44102,7 +45126,8 @@ var PlaywrightClient = class _PlaywrightClient {
44102
45126
  recordVideo,
44103
45127
  stealth,
44104
45128
  extensions,
44105
- extraHTTPHeaders
45129
+ extraHTTPHeaders,
45130
+ httpCredentials
44106
45131
  }),
44107
45132
  {
44108
45133
  logger: this.logger,
@@ -44121,6 +45146,9 @@ var PlaywrightClient = class _PlaywrightClient {
44121
45146
  const initialPageId = this._assignPageId(this.page);
44122
45147
  this._pageCreationTimes.set(initialPageId, now);
44123
45148
  this._tabFocusLog.push({ pageIndex: initialPageId, timestamp: now });
45149
+ if (storageStatePath) {
45150
+ await this.restoreIndexedDBFromStorageState(storageStatePath);
45151
+ }
44124
45152
  this.logger.info("[DirectPlaywright] Browser connected successfully", {
44125
45153
  browserMode: resolvedBrowserMode,
44126
45154
  stealthEnabled: this.stealthEnabled,
@@ -44200,6 +45228,13 @@ var PlaywrightClient = class _PlaywrightClient {
44200
45228
  headerNames: Object.keys(mergedHeaders)
44201
45229
  });
44202
45230
  }
45231
+ const resolvedHttpCredentials = options.httpCredentials ?? this.lastConnectOptions?.httpCredentials;
45232
+ if (resolvedHttpCredentials) {
45233
+ contextOptions.httpCredentials = resolvedHttpCredentials;
45234
+ if (this.lastConnectOptions) {
45235
+ this.lastConnectOptions.httpCredentials = resolvedHttpCredentials;
45236
+ }
45237
+ }
44203
45238
  if (nextStorageStatePath) {
44204
45239
  try {
44205
45240
  await fs4.access(nextStorageStatePath);
@@ -44251,6 +45286,9 @@ var PlaywrightClient = class _PlaywrightClient {
44251
45286
  const swapPageId = this._assignPageId(this.page);
44252
45287
  this._pageCreationTimes.set(swapPageId, swapNow);
44253
45288
  this._tabFocusLog.push({ pageIndex: swapPageId, timestamp: swapNow });
45289
+ if (nextStorageStatePath) {
45290
+ await this.restoreIndexedDBFromStorageState(nextStorageStatePath);
45291
+ }
44254
45292
  if (wasScreencasting && this.page && this.screencastHandler) {
44255
45293
  await this.startScreencast(this.screencastHandler);
44256
45294
  }
@@ -44269,7 +45307,8 @@ var PlaywrightClient = class _PlaywrightClient {
44269
45307
  recordVideo,
44270
45308
  stealth,
44271
45309
  extensions,
44272
- extraHTTPHeaders
45310
+ extraHTTPHeaders,
45311
+ httpCredentials
44273
45312
  } = options;
44274
45313
  const resolvedViewport = viewport ?? DEFAULT_VIEWPORT;
44275
45314
  const useStealthMode = stealth === true || typeof stealth === "object" && stealth.enabled;
@@ -44300,6 +45339,10 @@ var PlaywrightClient = class _PlaywrightClient {
44300
45339
  headerNames: Object.keys(extraHTTPHeaders)
44301
45340
  });
44302
45341
  }
45342
+ if (httpCredentials) {
45343
+ contextOptions.httpCredentials = httpCredentials;
45344
+ this.logger.info("[DirectPlaywright] HTTP Basic Authentication configured");
45345
+ }
44303
45346
  if (storageStatePath) {
44304
45347
  try {
44305
45348
  await fs4.access(storageStatePath);
@@ -44365,6 +45408,52 @@ var PlaywrightClient = class _PlaywrightClient {
44365
45408
  );
44366
45409
  return { browser, context };
44367
45410
  }
45411
+ /**
45412
+ * Read the storage state file, check for IndexedDB data, and restore it.
45413
+ * IndexedDB is origin-scoped, so we navigate to the appropriate origin
45414
+ * before writing data, then navigate back.
45415
+ */
45416
+ async restoreIndexedDBFromStorageState(storageStatePath) {
45417
+ try {
45418
+ const raw = await fs4.readFile(storageStatePath, "utf-8");
45419
+ const parsed = JSON.parse(raw);
45420
+ if (!parsed.indexedDB || !parsed.indexedDB.databases || parsed.indexedDB.databases.length === 0) {
45421
+ return;
45422
+ }
45423
+ const page = this.page;
45424
+ if (!page || page.isClosed()) return;
45425
+ let targetOrigin = null;
45426
+ if (parsed.origins && parsed.origins.length > 0) {
45427
+ targetOrigin = parsed.origins[0].origin;
45428
+ }
45429
+ if (!targetOrigin) {
45430
+ const cookies = parsed.cookies;
45431
+ if (cookies && cookies.length > 0) {
45432
+ const domain = cookies[0].domain.replace(/^\./, "");
45433
+ targetOrigin = `https://${domain}`;
45434
+ }
45435
+ }
45436
+ if (!targetOrigin) {
45437
+ this.logger.debug("[DirectPlaywright] No origin found for IndexedDB restoration, skipping");
45438
+ return;
45439
+ }
45440
+ this.logger.debug("[DirectPlaywright] Restoring IndexedDB data", {
45441
+ targetOrigin,
45442
+ databaseCount: parsed.indexedDB.databases.length,
45443
+ databaseNames: parsed.indexedDB.databases.map((db) => db.name)
45444
+ });
45445
+ await page.goto(targetOrigin, { waitUntil: "commit", timeout: 15e3 });
45446
+ await restoreIndexedDB(page, parsed.indexedDB);
45447
+ this.logger.info("[DirectPlaywright] IndexedDB data restored successfully", {
45448
+ databaseCount: parsed.indexedDB.databases.length
45449
+ });
45450
+ } catch (err) {
45451
+ this.logger.warn("[DirectPlaywright] Failed to restore IndexedDB from storage state", {
45452
+ error: err instanceof Error ? err.message : String(err),
45453
+ storageStatePath
45454
+ });
45455
+ }
45456
+ }
44368
45457
  /**
44369
45458
  * Resolve a working Chromium executable path, handling version mismatches
44370
45459
  * between the Playwright version bundled in this package and the browser
@@ -45222,17 +46311,42 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45222
46311
  const locator = await this.resolveRef(toRef);
45223
46312
  const box = await locator.boundingBox();
45224
46313
  if (box) {
46314
+ const viewportSize = this.getViewportSize();
45225
46315
  const elementCenterY = box.y + box.height / 2;
45226
- const viewportHeight = 1080;
45227
- const viewportCenterY = viewportHeight / 2;
46316
+ const viewportCenterY = viewportSize.height / 2;
45228
46317
  const scrollY = elementCenterY - viewportCenterY;
46318
+ const elementCenterX = box.x + box.width / 2;
46319
+ const viewportCenterX = viewportSize.width / 2;
46320
+ const boxRight = box.x + box.width;
46321
+ const needsHorizontalScroll = boxRight < 0 || box.x > viewportSize.width || elementCenterX < 0 || elementCenterX > viewportSize.width;
46322
+ const scrollX = needsHorizontalScroll ? elementCenterX - viewportCenterX : 0;
45229
46323
  this.logger.debug("[DirectPlaywright] Scrolling to center element", {
45230
46324
  toRef,
45231
46325
  elementCenterY,
45232
46326
  viewportCenterY,
45233
- scrollY
46327
+ scrollY,
46328
+ elementCenterX,
46329
+ scrollX,
46330
+ needsHorizontalScroll
45234
46331
  });
45235
- await page.evaluate(`window.scrollBy(0, ${scrollY})`);
46332
+ await page.evaluate(`window.scrollBy(${scrollX}, ${scrollY})`);
46333
+ if (needsHorizontalScroll) {
46334
+ await page.evaluate((ref) => {
46335
+ const el = document.querySelector(`[aria-ref="${ref}"]`);
46336
+ if (!el) return;
46337
+ let parent = el.parentElement;
46338
+ for (let i = 0; i < 10 && parent; i++) {
46339
+ if (parent.scrollWidth > parent.clientWidth) {
46340
+ const elRect = el.getBoundingClientRect();
46341
+ const parentRect = parent.getBoundingClientRect();
46342
+ const targetScrollLeft = parent.scrollLeft + (elRect.left - parentRect.left) - parentRect.width / 2 + elRect.width / 2;
46343
+ parent.scrollLeft = Math.max(0, targetScrollLeft);
46344
+ break;
46345
+ }
46346
+ parent = parent.parentElement;
46347
+ }
46348
+ }, toRef);
46349
+ }
45236
46350
  } else {
45237
46351
  this.logger.warn("[DirectPlaywright] No bounding box, using scrollIntoViewIfNeeded", {
45238
46352
  toRef
@@ -45586,7 +46700,25 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45586
46700
  this.logger.debug("[DirectPlaywright] Getting storage state", {
45587
46701
  method: opts?.method ?? "playwright"
45588
46702
  });
45589
- return await context.storageState();
46703
+ const playwrightState = await context.storageState();
46704
+ try {
46705
+ const page = await this.getPage();
46706
+ const indexedDB2 = await extractIndexedDB(page);
46707
+ if (indexedDB2) {
46708
+ this.logger.info("[DirectPlaywright] Extracted IndexedDB data", {
46709
+ databaseCount: indexedDB2.databases.length,
46710
+ databaseNames: indexedDB2.databases.map((db) => db.name)
46711
+ });
46712
+ return { ...playwrightState, indexedDB: indexedDB2 };
46713
+ } else {
46714
+ this.logger.debug("[DirectPlaywright] No IndexedDB databases found on current page");
46715
+ }
46716
+ } catch (err) {
46717
+ this.logger.warn("[DirectPlaywright] Failed to extract IndexedDB, saving without it", {
46718
+ error: err instanceof Error ? err.message : String(err)
46719
+ });
46720
+ }
46721
+ return playwrightState;
45590
46722
  }
45591
46723
  async getCurrentUrl(_opts) {
45592
46724
  return (await this.getPage()).url();
@@ -45833,6 +46965,7 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45833
46965
  }
45834
46966
  await this.enrichInteractiveSVGElements(page);
45835
46967
  await this.enrichHiddenClickableElements(page);
46968
+ await this.enrichModalDialogElements(page);
45836
46969
  let snapshot;
45837
46970
  const maxRetries = 3;
45838
46971
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
@@ -45961,6 +47094,13 @@ Use coordinate-based clicking (x, y) to interact with elements visible in the sc
45961
47094
  async enrichHiddenClickableElements(page) {
45962
47095
  await enrichHiddenClickableElements(page, this.logger);
45963
47096
  }
47097
+ /**
47098
+ * Enrich positioned overlays that look like modals but lack `role="dialog"`.
47099
+ * Injects `role="dialog"` and `aria-label` so the agent recognizes them.
47100
+ */
47101
+ async enrichModalDialogElements(page) {
47102
+ await enrichModalDialogElements(page, this.logger);
47103
+ }
45964
47104
  /**
45965
47105
  * Resolve a ref to a Playwright locator using the internal aria-ref selector.
45966
47106
  * This is the same mechanism MCP uses to resolve refs.
@@ -46993,6 +48133,8 @@ export {
46993
48133
  CursorOverlay,
46994
48134
  setCdpScreencastLogger,
46995
48135
  CdpScreencastManager,
48136
+ extractIndexedDB,
48137
+ restoreIndexedDB,
46996
48138
  setSnapshotAnalyzerLogger,
46997
48139
  extractSnapshotYaml,
46998
48140
  parseSnapshot,
@@ -47007,22 +48149,27 @@ export {
47007
48149
  normalizeIconLabelText,
47008
48150
  normalizeSearchText,
47009
48151
  extractDataValue,
48152
+ extractCellText,
48153
+ extractGridCell,
48154
+ reconcileCellColumn,
48155
+ detectGrid,
48156
+ formatGridOutput,
48157
+ formatGridRow,
47010
48158
  findRelevanceScope,
47011
48159
  searchElements,
47012
48160
  populateSearchContext,
47013
48161
  sortMatchesByContext,
47014
- detectGrid,
47015
- extractCellText,
47016
- extractGridCell,
47017
- reconcileCellColumn,
47018
- findActiveElement,
47019
48162
  buildSectionTree,
47020
48163
  markMatchingSections,
48164
+ findActiveElement,
47021
48165
  formatSemanticSnapshot,
47022
48166
  expandSection,
47023
48167
  expandSectionOnly,
47024
- formatGridOutput,
47025
- formatGridRow,
48168
+ extractTablesFromSnapshot,
48169
+ gridInfoToRows,
48170
+ formatCSVWithFrontmatter,
48171
+ appendRowsToCSV,
48172
+ detectPaginationInfo,
47026
48173
  captureSnapshotState,
47027
48174
  compareSnapshots,
47028
48175
  hasDiffChanges,
@@ -47042,6 +48189,7 @@ export {
47042
48189
  getBrowserToolDefinitions,
47043
48190
  BROWSER_LIFECYCLE_TOOLS,
47044
48191
  getBrowserToolDefinitionsWithLifecycle,
48192
+ dispatchBrowserTool,
47045
48193
  extractSemanticHint,
47046
48194
  PlaywrightClient
47047
48195
  };
@@ -47324,4 +48472,4 @@ playwright-extra/dist/index.esm.js:
47324
48472
  * @license MIT
47325
48473
  *)
47326
48474
  */
47327
- //# sourceMappingURL=chunk-FK3EZADZ.js.map
48475
+ //# sourceMappingURL=chunk-MSMC6UXW.js.map