@detiq/crawler 1.1.13 → 1.1.15

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 (112) hide show
  1. package/Dockerfile +1 -1
  2. package/dist/accessibility-tree-crawler.js +156 -0
  3. package/dist/accessibility-tree-crawler.js.map +7 -0
  4. package/dist/advanced-ui-probes.js +310 -0
  5. package/dist/advanced-ui-probes.js.map +7 -0
  6. package/dist/content-chunker.js +2 -2
  7. package/dist/content-chunker.js.map +2 -2
  8. package/dist/crawl-session-manager.js +145 -0
  9. package/dist/crawl-session-manager.js.map +7 -0
  10. package/dist/crawler.js +1142 -263
  11. package/dist/crawler.js.map +2 -2
  12. package/dist/crud-interaction.js +264 -0
  13. package/dist/crud-interaction.js.map +7 -0
  14. package/dist/dropdown-enumerator.js +230 -0
  15. package/dist/dropdown-enumerator.js.map +7 -0
  16. package/dist/filter-pagination-crawler.js +299 -0
  17. package/dist/filter-pagination-crawler.js.map +7 -0
  18. package/dist/frontier.js +5 -3
  19. package/dist/frontier.js.map +2 -2
  20. package/dist/gap-pass.js +47 -14
  21. package/dist/gap-pass.js.map +2 -2
  22. package/dist/http-engine.js +28 -15
  23. package/dist/http-engine.js.map +2 -2
  24. package/dist/index.js +15 -1
  25. package/dist/index.js.map +2 -2
  26. package/dist/job-queue.js +12 -5
  27. package/dist/job-queue.js.map +2 -2
  28. package/dist/locator-extractor.js +23 -0
  29. package/dist/locator-extractor.js.map +2 -2
  30. package/dist/multi-context-handler.js +182 -0
  31. package/dist/multi-context-handler.js.map +7 -0
  32. package/dist/nested-window-crawler.js +172 -0
  33. package/dist/nested-window-crawler.js.map +7 -0
  34. package/dist/network-action-correlator.js +209 -0
  35. package/dist/network-action-correlator.js.map +7 -0
  36. package/dist/performance-metrics-collector.js +155 -0
  37. package/dist/performance-metrics-collector.js.map +7 -0
  38. package/dist/performance-metrics.js.map +2 -2
  39. package/dist/policies/crawl-safety.js +5 -1
  40. package/dist/policies/crawl-safety.js.map +2 -2
  41. package/dist/profiles/domain-profile.js +21 -1
  42. package/dist/profiles/domain-profile.js.map +2 -2
  43. package/dist/rate-limiter.js +2 -2
  44. package/dist/rate-limiter.js.map +2 -2
  45. package/dist/screen-reader.js +1 -0
  46. package/dist/screen-reader.js.map +2 -2
  47. package/dist/service-worker-interceptor.js +136 -0
  48. package/dist/service-worker-interceptor.js.map +7 -0
  49. package/dist/session-capture.js +4 -2
  50. package/dist/session-capture.js.map +2 -2
  51. package/dist/shadow-dom-crawler.js +132 -0
  52. package/dist/shadow-dom-crawler.js.map +7 -0
  53. package/dist/sitemap.js +16 -10
  54. package/dist/sitemap.js.map +2 -2
  55. package/dist/state-graph.js +193 -0
  56. package/dist/state-graph.js.map +7 -0
  57. package/dist/stealth/human-behavior.js +36 -3
  58. package/dist/stealth/human-behavior.js.map +2 -2
  59. package/dist/url-pattern-completer.js +48 -14
  60. package/dist/url-pattern-completer.js.map +2 -2
  61. package/dist/visual-diff-verdict.js +3 -1
  62. package/dist/visual-diff-verdict.js.map +2 -2
  63. package/package.json +2 -1
  64. package/src/accessibility-tree-crawler.ts +246 -0
  65. package/src/advanced-ui-probes.ts +470 -0
  66. package/src/crawl-session-manager.ts +231 -0
  67. package/src/crawler.ts +1071 -142
  68. package/src/crud-interaction.ts +386 -0
  69. package/src/dropdown-enumerator.ts +312 -0
  70. package/src/filter-pagination-crawler.ts +500 -0
  71. package/src/gap-pass.ts +58 -16
  72. package/src/http-engine.ts +29 -15
  73. package/src/index.ts +14 -1
  74. package/src/job-queue.ts +3 -1
  75. package/src/locator-extractor.ts +45 -0
  76. package/src/multi-context-handler.ts +296 -0
  77. package/src/nested-window-crawler.ts +310 -0
  78. package/src/network-action-correlator.ts +320 -0
  79. package/src/performance-metrics-collector.ts +243 -0
  80. package/src/performance-metrics.ts +1 -1
  81. package/src/screen-reader.ts +4 -1
  82. package/src/service-worker-interceptor.ts +185 -0
  83. package/src/session-capture.ts +3 -0
  84. package/src/shadow-dom-crawler.ts +198 -0
  85. package/src/sitemap.ts +17 -10
  86. package/src/state-graph.ts +280 -0
  87. package/src/stealth/human-behavior.ts +38 -4
  88. package/src/url-pattern-completer.ts +61 -15
  89. package/tsconfig.json +1 -1
  90. package/types/accessibility-tree-crawler.d.ts +44 -0
  91. package/types/advanced-ui-probes.d.ts +47 -0
  92. package/types/crawl-session-manager.d.ts +44 -0
  93. package/types/crawler.d.ts +3 -1
  94. package/types/crud-interaction.d.ts +31 -0
  95. package/types/dropdown-enumerator.d.ts +20 -0
  96. package/types/filter-pagination-crawler.d.ts +52 -0
  97. package/types/gap-pass.d.ts +1 -0
  98. package/types/index.d.ts +14 -1
  99. package/types/job-queue.d.ts +18 -9
  100. package/types/locator-extractor.d.ts +7 -0
  101. package/types/multi-context-handler.d.ts +55 -0
  102. package/types/nested-window-crawler.d.ts +41 -0
  103. package/types/network-action-correlator.d.ts +47 -0
  104. package/types/performance-metrics-collector.d.ts +50 -0
  105. package/types/performance-metrics.d.ts +1 -2
  106. package/types/screen-reader.d.ts +1 -2
  107. package/types/service-worker-interceptor.d.ts +36 -0
  108. package/types/session-capture.d.ts +1 -1
  109. package/types/shadow-dom-crawler.d.ts +35 -0
  110. package/types/sitemap.d.ts +2 -2
  111. package/types/state-graph.d.ts +59 -0
  112. package/types/stealth/human-behavior.d.ts +1 -1
package/Dockerfile CHANGED
@@ -10,7 +10,7 @@ ENV NODE_ENV=production
10
10
  ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
11
11
 
12
12
  # Install Node.js 20 (Playwright base image ships with 18)
13
- RUN apt-get update && apt-get install -y curl && \
13
+ RUN apt-get update && apt-get install -y curl build-essential python3 && \
14
14
  curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
15
15
  apt-get install -y nodejs && \
16
16
  apt-get clean && rm -rf /var/lib/apt/lists/*
@@ -0,0 +1,156 @@
1
+ async function extractLandmarks(page) {
2
+ try {
3
+ const raw = await page.evaluate(() => {
4
+ const LANDMARK_SELECTORS = [
5
+ { role: "banner", selector: 'header,[role="banner"]' },
6
+ { role: "navigation", selector: 'nav,[role="navigation"]' },
7
+ { role: "main", selector: 'main,[role="main"]' },
8
+ { role: "complementary", selector: 'aside,[role="complementary"]' },
9
+ { role: "contentinfo", selector: 'footer,[role="contentinfo"]' },
10
+ { role: "search", selector: '[role="search"]' },
11
+ { role: "form", selector: 'form[aria-label],[role="form"]' },
12
+ { role: "region", selector: '[role="region"][aria-label]' }
13
+ ];
14
+ return LANDMARK_SELECTORS.flatMap(
15
+ ({ role, selector }) => [...document.querySelectorAll(selector)].slice(0, 10).map((el) => ({
16
+ role,
17
+ label: el.getAttribute("aria-label") || el.getAttribute("aria-labelledby") || "",
18
+ elementId: el.id || "",
19
+ interactiveCount: el.querySelectorAll(
20
+ 'button,a[href],input,select,textarea,[role="button"],[tabindex="0"]'
21
+ ).length
22
+ }))
23
+ );
24
+ });
25
+ return raw.slice(0, 50);
26
+ } catch {
27
+ return [];
28
+ }
29
+ }
30
+ async function extractHeadings(page) {
31
+ try {
32
+ const raw = await page.evaluate(() => {
33
+ const headingEls = [...document.querySelectorAll('h1,h2,h3,h4,h5,h6,[role="heading"]')];
34
+ return headingEls.map((el) => ({
35
+ level: parseInt(el.tagName.slice(1)) || parseInt(el.getAttribute("aria-level") || "0") || 0,
36
+ text: el.textContent?.trim().slice(0, 100) || "",
37
+ elementId: el.id || ""
38
+ })).filter((h) => h.level >= 1 && h.level <= 6);
39
+ });
40
+ return raw.slice(0, 100);
41
+ } catch {
42
+ return [];
43
+ }
44
+ }
45
+ function detectHeadingViolations(headings) {
46
+ const violations = [];
47
+ if (headings.length === 0) return violations;
48
+ const h1s = headings.filter((h) => h.level === 1);
49
+ if (h1s.length === 0) {
50
+ violations.push("Missing h1 landmark");
51
+ } else if (h1s.length > 1) {
52
+ violations.push("Multiple h1 elements found");
53
+ }
54
+ let prevLevel = 0;
55
+ for (const heading of headings) {
56
+ if (prevLevel > 0 && heading.level > prevLevel + 1) {
57
+ violations.push(
58
+ `Heading level skip: h${prevLevel} to h${heading.level}`
59
+ );
60
+ }
61
+ prevLevel = heading.level;
62
+ }
63
+ return violations;
64
+ }
65
+ async function findMissingNames(page) {
66
+ try {
67
+ return await page.evaluate(() => {
68
+ const INTERACTIVE_SELECTOR = 'button,a[href],input:not([type="hidden"]),select,textarea,[role="button"],[role="link"],[role="checkbox"],[role="radio"],[role="switch"],[role="menuitem"],[role="tab"]';
69
+ return [...document.querySelectorAll(INTERACTIVE_SELECTOR)].filter((el) => {
70
+ const labelledById = el.getAttribute("aria-labelledby") || "";
71
+ const name = (el.getAttribute("aria-label") || "").trim() || (labelledById ? document.getElementById(labelledById)?.textContent?.trim() ?? "" : "") || (el.textContent || "").trim() || (el.placeholder || "").trim() || (el.title || "").trim() || (el.alt || "").trim();
72
+ return !name || name.length === 0;
73
+ }).slice(0, 30).map((el) => ({
74
+ tagName: el.tagName.toLowerCase(),
75
+ role: el.getAttribute("role") || "",
76
+ elementId: el.id || "",
77
+ htmlSnippet: el.outerHTML.slice(0, 120)
78
+ }));
79
+ });
80
+ } catch {
81
+ return [];
82
+ }
83
+ }
84
+ async function extractLiveRegions(page) {
85
+ try {
86
+ return await page.evaluate(() => {
87
+ const els = [
88
+ ...document.querySelectorAll(
89
+ '[aria-live],[role="alert"],[role="status"],[role="log"]'
90
+ )
91
+ ];
92
+ return els.slice(0, 20).map((el) => ({
93
+ role: el.getAttribute("role") || "",
94
+ ariaLive: el.getAttribute("aria-live") || "",
95
+ ariaAtomic: el.getAttribute("aria-atomic") || "",
96
+ currentText: (el.textContent || "").trim().slice(0, 200)
97
+ }));
98
+ });
99
+ } catch {
100
+ return [];
101
+ }
102
+ }
103
+ async function checkSkipLink(page) {
104
+ try {
105
+ return await page.evaluate(() => {
106
+ const firstFocusable = document.querySelector('a[href^="#"],a:first-of-type');
107
+ return !!(firstFocusable && /skip/i.test(
108
+ (firstFocusable.textContent || "") + (firstFocusable.getAttribute("href") || "")
109
+ ));
110
+ });
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+ async function crawlAccessibilityTree(page) {
116
+ const [
117
+ landmarksResult,
118
+ headingsResult,
119
+ missingNamesResult,
120
+ liveRegionsResult,
121
+ skipLinkResult,
122
+ interactiveCountResult
123
+ ] = await Promise.allSettled([
124
+ extractLandmarks(page),
125
+ extractHeadings(page),
126
+ findMissingNames(page),
127
+ extractLiveRegions(page),
128
+ checkSkipLink(page),
129
+ page.evaluate(() => {
130
+ const INTERACTIVE_SELECTOR = 'button,a[href],input:not([type="hidden"]),select,textarea,[role="button"],[role="link"],[role="checkbox"],[role="radio"],[role="switch"],[role="menuitem"],[role="tab"]';
131
+ return document.querySelectorAll(INTERACTIVE_SELECTOR).length;
132
+ }).catch(() => 0)
133
+ ]);
134
+ const landmarks = landmarksResult.status === "fulfilled" ? landmarksResult.value : [];
135
+ const headings = headingsResult.status === "fulfilled" ? headingsResult.value : [];
136
+ const missingNameElements = missingNamesResult.status === "fulfilled" ? missingNamesResult.value : [];
137
+ const liveRegions = liveRegionsResult.status === "fulfilled" ? liveRegionsResult.value : [];
138
+ const hasSkipLink = skipLinkResult.status === "fulfilled" ? skipLinkResult.value : false;
139
+ const totalInteractiveCount = interactiveCountResult.status === "fulfilled" ? interactiveCountResult.value : 0;
140
+ const headingHierarchyViolations = detectHeadingViolations(headings);
141
+ return {
142
+ landmarkCount: landmarks.length,
143
+ landmarks,
144
+ headings,
145
+ headingHierarchyViolations,
146
+ missingNameElements,
147
+ missingNameCount: missingNameElements.length,
148
+ liveRegions,
149
+ totalInteractiveCount,
150
+ hasSkipLink
151
+ };
152
+ }
153
+ export {
154
+ crawlAccessibilityTree
155
+ };
156
+ //# sourceMappingURL=accessibility-tree-crawler.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/accessibility-tree-crawler.ts"],
4
+ "sourcesContent": ["/**\n * Accessibility tree crawler \u2014 extract landmark, heading, and ARIA metadata from crawled pages.\n *\n * Captures: landmarks with interactive counts, heading hierarchy with violation detection,\n * interactive elements missing accessible names, live regions, and skip-link presence.\n *\n * This data feeds ZeTa's accessibility test generator to surface WCAG 2.1 Level A/AA\n * failures (missing names, heading skips, absent skip links, unlabeled live regions).\n */\n\nimport type { Page } from 'playwright';\n\nexport interface LandmarkInfo {\n role: string;\n label: string;\n elementId: string;\n interactiveCount: number;\n}\n\nexport interface HeadingInfo {\n level: number;\n text: string;\n elementId: string;\n}\n\nexport interface MissingNameEl {\n tagName: string;\n role: string;\n elementId: string;\n htmlSnippet: string;\n}\n\nexport interface LiveRegionInfo {\n role: string;\n ariaLive: string;\n ariaAtomic: string;\n currentText: string;\n}\n\nexport interface AccessibilityTreeResult {\n landmarkCount: number;\n landmarks: LandmarkInfo[];\n headings: HeadingInfo[];\n headingHierarchyViolations: string[];\n missingNameElements: MissingNameEl[];\n missingNameCount: number;\n liveRegions: LiveRegionInfo[];\n totalInteractiveCount: number;\n hasSkipLink: boolean;\n}\n\nasync function extractLandmarks(page: any): Promise<LandmarkInfo[]> {\n try {\n const raw = await page.evaluate(() => {\n const LANDMARK_SELECTORS = [\n { role: 'banner', selector: 'header,[role=\"banner\"]' },\n { role: 'navigation', selector: 'nav,[role=\"navigation\"]' },\n { role: 'main', selector: 'main,[role=\"main\"]' },\n { role: 'complementary', selector: 'aside,[role=\"complementary\"]' },\n { role: 'contentinfo', selector: 'footer,[role=\"contentinfo\"]' },\n { role: 'search', selector: '[role=\"search\"]' },\n { role: 'form', selector: 'form[aria-label],[role=\"form\"]' },\n { role: 'region', selector: '[role=\"region\"][aria-label]' },\n ];\n return LANDMARK_SELECTORS.flatMap(({ role, selector }) =>\n [...document.querySelectorAll(selector)].slice(0, 10).map(el => ({\n role,\n label: el.getAttribute('aria-label') || el.getAttribute('aria-labelledby') || '',\n elementId: el.id || '',\n interactiveCount: el.querySelectorAll(\n 'button,a[href],input,select,textarea,[role=\"button\"],[tabindex=\"0\"]'\n ).length,\n }))\n );\n });\n return raw.slice(0, 50);\n } catch {\n return [];\n }\n}\n\nasync function extractHeadings(page: any): Promise<HeadingInfo[]> {\n try {\n const raw = await page.evaluate(() => {\n const headingEls = [...document.querySelectorAll('h1,h2,h3,h4,h5,h6,[role=\"heading\"]')];\n return headingEls\n .map(el => ({\n level:\n parseInt(el.tagName.slice(1)) ||\n parseInt(el.getAttribute('aria-level') || '0') ||\n 0,\n text: el.textContent?.trim().slice(0, 100) || '',\n elementId: el.id || '',\n }))\n .filter(h => h.level >= 1 && h.level <= 6);\n });\n return raw.slice(0, 100);\n } catch {\n return [];\n }\n}\n\nfunction detectHeadingViolations(headings: HeadingInfo[]): string[] {\n const violations: string[] = [];\n if (headings.length === 0) return violations;\n\n const h1s = headings.filter(h => h.level === 1);\n if (h1s.length === 0) {\n violations.push('Missing h1 landmark');\n } else if (h1s.length > 1) {\n violations.push('Multiple h1 elements found');\n }\n\n let prevLevel = 0;\n for (const heading of headings) {\n if (prevLevel > 0 && heading.level > prevLevel + 1) {\n violations.push(\n `Heading level skip: h${prevLevel} to h${heading.level}`\n );\n }\n prevLevel = heading.level;\n }\n\n return violations;\n}\n\nasync function findMissingNames(page: any): Promise<MissingNameEl[]> {\n try {\n return await page.evaluate(() => {\n const INTERACTIVE_SELECTOR =\n 'button,a[href],input:not([type=\"hidden\"]),select,textarea,' +\n '[role=\"button\"],[role=\"link\"],[role=\"checkbox\"],[role=\"radio\"],' +\n '[role=\"switch\"],[role=\"menuitem\"],[role=\"tab\"]';\n return [...document.querySelectorAll(INTERACTIVE_SELECTOR)]\n .filter(el => {\n const labelledById = el.getAttribute('aria-labelledby') || '';\n const name =\n (el.getAttribute('aria-label') || '').trim() ||\n (labelledById\n ? document.getElementById(labelledById)?.textContent?.trim() ?? ''\n : '') ||\n (el.textContent || '').trim() ||\n ((el as HTMLInputElement).placeholder || '').trim() ||\n ((el as HTMLElement).title || '').trim() ||\n ((el as HTMLImageElement).alt || '').trim();\n return !name || name.length === 0;\n })\n .slice(0, 30)\n .map(el => ({\n tagName: el.tagName.toLowerCase(),\n role: el.getAttribute('role') || '',\n elementId: el.id || '',\n htmlSnippet: el.outerHTML.slice(0, 120),\n }));\n });\n } catch {\n return [];\n }\n}\n\nasync function extractLiveRegions(page: any): Promise<LiveRegionInfo[]> {\n try {\n return await page.evaluate(() => {\n const els = [\n ...document.querySelectorAll(\n '[aria-live],[role=\"alert\"],[role=\"status\"],[role=\"log\"]'\n ),\n ];\n return els.slice(0, 20).map(el => ({\n role: el.getAttribute('role') || '',\n ariaLive: el.getAttribute('aria-live') || '',\n ariaAtomic: el.getAttribute('aria-atomic') || '',\n currentText: (el.textContent || '').trim().slice(0, 200),\n }));\n });\n } catch {\n return [];\n }\n}\n\nasync function checkSkipLink(page: any): Promise<boolean> {\n try {\n return await page.evaluate(() => {\n const firstFocusable = document.querySelector('a[href^=\"#\"],a:first-of-type');\n return !!(\n firstFocusable &&\n /skip/i.test(\n (firstFocusable.textContent || '') + (firstFocusable.getAttribute('href') || '')\n )\n );\n });\n } catch {\n return false;\n }\n}\n\nexport async function crawlAccessibilityTree(page: any): Promise<AccessibilityTreeResult> {\n const [\n landmarksResult,\n headingsResult,\n missingNamesResult,\n liveRegionsResult,\n skipLinkResult,\n interactiveCountResult,\n ] = await Promise.allSettled([\n extractLandmarks(page),\n extractHeadings(page),\n findMissingNames(page),\n extractLiveRegions(page),\n checkSkipLink(page),\n page.evaluate(() => {\n const INTERACTIVE_SELECTOR =\n 'button,a[href],input:not([type=\"hidden\"]),select,textarea,' +\n '[role=\"button\"],[role=\"link\"],[role=\"checkbox\"],[role=\"radio\"],' +\n '[role=\"switch\"],[role=\"menuitem\"],[role=\"tab\"]';\n return document.querySelectorAll(INTERACTIVE_SELECTOR).length;\n }).catch(() => 0),\n ]);\n\n const landmarks =\n landmarksResult.status === 'fulfilled' ? landmarksResult.value : [];\n const headings =\n headingsResult.status === 'fulfilled' ? headingsResult.value : [];\n const missingNameElements =\n missingNamesResult.status === 'fulfilled' ? missingNamesResult.value : [];\n const liveRegions =\n liveRegionsResult.status === 'fulfilled' ? liveRegionsResult.value : [];\n const hasSkipLink =\n skipLinkResult.status === 'fulfilled' ? skipLinkResult.value : false;\n const totalInteractiveCount =\n interactiveCountResult.status === 'fulfilled' ? interactiveCountResult.value : 0;\n\n const headingHierarchyViolations = detectHeadingViolations(headings);\n\n return {\n landmarkCount: landmarks.length,\n landmarks,\n headings,\n headingHierarchyViolations,\n missingNameElements,\n missingNameCount: missingNameElements.length,\n liveRegions,\n totalInteractiveCount,\n hasSkipLink,\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,310 @@
1
+ async function detectDragAndDrop(page) {
2
+ const detected = await page.evaluate(() => {
3
+ if (document.querySelector("[data-dnd-kit-draggable]")) {
4
+ return {
5
+ library: "dnd-kit",
6
+ draggableCount: document.querySelectorAll("[data-dnd-kit-draggable]").length,
7
+ dropZoneCount: document.querySelectorAll("[data-dnd-kit-droppable]").length
8
+ };
9
+ }
10
+ if (document.querySelector("[data-rbd-draggable-id]")) {
11
+ return {
12
+ library: "react-beautiful-dnd",
13
+ draggableCount: document.querySelectorAll("[data-rbd-draggable-id]").length,
14
+ dropZoneCount: document.querySelectorAll("[data-rbd-droppable-id]").length
15
+ };
16
+ }
17
+ if (document.querySelector(".sortable-ghost") || document.querySelector("[data-sortable]")) {
18
+ return {
19
+ library: "sortablejs",
20
+ draggableCount: document.querySelectorAll("[data-sortable], .sortable-item").length,
21
+ dropZoneCount: document.querySelectorAll("[data-sortable-list], .sortable-list").length
22
+ };
23
+ }
24
+ const html5Draggables = document.querySelectorAll('[draggable="true"]');
25
+ if (html5Draggables.length > 0) {
26
+ return {
27
+ library: "html5",
28
+ draggableCount: html5Draggables.length,
29
+ dropZoneCount: document.querySelectorAll("[data-drop-target], [ondrop], .drop-zone").length
30
+ };
31
+ }
32
+ return { library: "none", draggableCount: 0, dropZoneCount: 0 };
33
+ });
34
+ if (detected.library === "none") {
35
+ return { ...detected, testAttempted: false, testCompleted: false };
36
+ }
37
+ let testAttempted = false;
38
+ let testCompleted = false;
39
+ try {
40
+ if (detected.library === "html5" && detected.draggableCount >= 2) {
41
+ const sources = await page.$$('[draggable="true"]');
42
+ if (sources.length >= 2) {
43
+ testAttempted = true;
44
+ await page.dragAndDrop('[draggable="true"]:nth-child(1)', '[draggable="true"]:nth-child(2)');
45
+ testCompleted = true;
46
+ }
47
+ } else if (detected.draggableCount >= 2) {
48
+ const draggableSel = detected.library === "dnd-kit" ? "[data-dnd-kit-draggable]" : detected.library === "react-beautiful-dnd" ? "[data-rbd-draggable-id]" : "[data-sortable], .sortable-item";
49
+ const elements = await page.$$(draggableSel);
50
+ if (elements.length >= 2) {
51
+ testAttempted = true;
52
+ const src = elements[0];
53
+ const dst = elements[1];
54
+ const srcBox = await src.boundingBox();
55
+ const dstBox = await dst.boundingBox();
56
+ if (srcBox && dstBox) {
57
+ const srcX = srcBox.x + srcBox.width / 2;
58
+ const srcY = srcBox.y + srcBox.height / 2;
59
+ const dstX = dstBox.x + dstBox.width / 2;
60
+ const dstY = dstBox.y + dstBox.height / 2;
61
+ await page.mouse.move(srcX, srcY);
62
+ await page.mouse.down();
63
+ const steps = 10;
64
+ for (let i = 1; i <= steps; i++) {
65
+ await page.mouse.move(
66
+ srcX + (dstX - srcX) * i / steps,
67
+ srcY + (dstY - srcY) * i / steps
68
+ );
69
+ }
70
+ await page.mouse.up();
71
+ testCompleted = true;
72
+ }
73
+ }
74
+ }
75
+ } catch {
76
+ }
77
+ return { ...detected, testAttempted, testCompleted };
78
+ }
79
+ async function captureDatePickers(page) {
80
+ const results = [];
81
+ const nativeInputs = await page.$$('input[type="date"], input[type="datetime-local"]');
82
+ for (const input of nativeInputs.slice(0, 5)) {
83
+ try {
84
+ const attrs = await input.evaluate((el) => ({
85
+ type: el.type,
86
+ min: el.min || void 0,
87
+ max: el.max || void 0,
88
+ id: el.id || "",
89
+ name: el.name || ""
90
+ }));
91
+ let fieldLabel = attrs.id || attrs.name || "unknown";
92
+ if (attrs.id) {
93
+ const lblText = await page.evaluate((id) => {
94
+ const lbl = document.querySelector(`label[for="${id}"]`);
95
+ return lbl ? (lbl.textContent ?? "").trim() : null;
96
+ }, attrs.id);
97
+ if (lblText) fieldLabel = lblText;
98
+ }
99
+ let sampleSelectedValue;
100
+ try {
101
+ await input.fill("2026-09-15");
102
+ sampleSelectedValue = await input.inputValue();
103
+ if (!sampleSelectedValue) sampleSelectedValue = void 0;
104
+ } catch {
105
+ }
106
+ results.push({
107
+ fieldLabel,
108
+ pickerType: attrs.type === "datetime-local" ? "native-datetime" : "native-date",
109
+ minDate: attrs.min,
110
+ maxDate: attrs.max,
111
+ disabledDatesFound: false,
112
+ // native inputs have no visual disabled cells
113
+ sampleSelectedValue
114
+ });
115
+ } catch {
116
+ }
117
+ }
118
+ const customInputs = await page.$$('input:not([type="date"]):not([type="datetime-local"]):not([type="hidden"])');
119
+ for (const input of customInputs.slice(0, 5)) {
120
+ if (results.length >= 5) break;
121
+ try {
122
+ await input.click();
123
+ const calendarAppeared = await page.waitForSelector('[role="grid"]', { timeout: 2e3 }).then(() => true).catch(() => false);
124
+ if (!calendarAppeared) {
125
+ await page.keyboard.press("Escape");
126
+ continue;
127
+ }
128
+ const monthHeader = await page.evaluate(() => {
129
+ const header = document.querySelector('[role="grid"] caption, [role="columnheader"], .calendar-header, .rdp-caption');
130
+ return header ? (header.textContent ?? "").trim().slice(0, 60) : "";
131
+ });
132
+ const disabledCount = await page.evaluate(
133
+ () => document.querySelectorAll('[role="gridcell"][aria-disabled="true"], [role="gridcell"].disabled').length
134
+ );
135
+ const inputId = await input.evaluate((el) => el.id || el.name || "");
136
+ let fieldLabel = inputId || "datepicker";
137
+ if (inputId) {
138
+ const lblText = await page.evaluate((id) => {
139
+ const lbl = document.querySelector(`label[for="${id}"]`);
140
+ return lbl ? (lbl.textContent ?? "").trim() : null;
141
+ }, inputId);
142
+ if (lblText) fieldLabel = lblText;
143
+ }
144
+ let sampleSelectedValue;
145
+ try {
146
+ const firstEnabled = await page.$('[role="gridcell"]:not([aria-disabled="true"]):not(.disabled)');
147
+ if (firstEnabled) {
148
+ await firstEnabled.click();
149
+ sampleSelectedValue = await input.inputValue().catch(() => void 0);
150
+ }
151
+ } catch {
152
+ }
153
+ await page.keyboard.press("Escape");
154
+ results.push({
155
+ fieldLabel,
156
+ pickerType: "calendar-popup",
157
+ disabledDatesFound: disabledCount > 0,
158
+ sampleSelectedValue: sampleSelectedValue || void 0
159
+ });
160
+ } catch {
161
+ try {
162
+ await page.keyboard.press("Escape");
163
+ } catch {
164
+ }
165
+ }
166
+ }
167
+ return results;
168
+ }
169
+ async function captureSliders(page) {
170
+ const results = [];
171
+ const handles = await page.$$('input[type="range"], [role="slider"]');
172
+ for (const handle of handles.slice(0, 5)) {
173
+ try {
174
+ const attrs = await handle.evaluate((el) => {
175
+ const min = parseFloat(el.min ?? el.getAttribute("aria-valuemin") ?? "0");
176
+ const max = parseFloat(el.max ?? el.getAttribute("aria-valuemax") ?? "100");
177
+ const step = parseFloat(el.step ?? el.getAttribute("aria-valuestep") ?? "1") || 1;
178
+ const value = parseFloat(el.value ?? el.getAttribute("aria-valuenow") ?? String(min));
179
+ const id = el.id || el.name || "";
180
+ return { min, max, step, value, id };
181
+ });
182
+ let label = attrs.id || "slider";
183
+ if (attrs.id) {
184
+ const lblText = await page.evaluate((id) => {
185
+ const lbl = document.querySelector(`label[for="${id}"]`);
186
+ return lbl ? (lbl.textContent ?? "").trim() : null;
187
+ }, attrs.id);
188
+ if (lblText) label = lblText;
189
+ }
190
+ const isNative = await handle.evaluate((el) => el.tagName.toLowerCase() === "input");
191
+ if (isNative) {
192
+ try {
193
+ await handle.fill(String(attrs.min));
194
+ await handle.fill(String(attrs.max));
195
+ await handle.fill(String(attrs.value));
196
+ } catch {
197
+ }
198
+ }
199
+ results.push({
200
+ label,
201
+ min: attrs.min,
202
+ max: attrs.max,
203
+ step: attrs.step,
204
+ currentValue: attrs.value
205
+ });
206
+ } catch {
207
+ }
208
+ }
209
+ return results;
210
+ }
211
+ async function captureTypeaheads(page) {
212
+ const results = [];
213
+ const candidates = await page.$$(
214
+ '[role="combobox"][aria-autocomplete], [aria-autocomplete="list"], input.autocomplete, input[aria-autocomplete]'
215
+ );
216
+ for (const input of candidates.slice(0, 3)) {
217
+ try {
218
+ const inputId = await input.evaluate((el) => el.id || el.name || el.placeholder || "");
219
+ let label = inputId || "typeahead";
220
+ if (inputId) {
221
+ const lblText = await page.evaluate((id) => {
222
+ const lbl = document.querySelector(`label[for="${id}"]`);
223
+ return lbl ? (lbl.textContent ?? "").trim() : null;
224
+ }, inputId);
225
+ if (lblText) label = lblText;
226
+ }
227
+ await input.click();
228
+ await input.fill("");
229
+ await input.type("a", { delay: 100 });
230
+ await page.waitForTimeout(500);
231
+ let listboxVisible = await page.evaluate(() => {
232
+ const lb = document.querySelector('[role="listbox"]');
233
+ if (!lb) return false;
234
+ const style = window.getComputedStyle(lb);
235
+ return style.display !== "none" && style.visibility !== "hidden";
236
+ });
237
+ let minCharsToTrigger = listboxVisible ? 1 : 2;
238
+ if (!listboxVisible) {
239
+ await input.type("b", { delay: 100 });
240
+ await page.waitForTimeout(500);
241
+ listboxVisible = await page.evaluate(() => {
242
+ const lb = document.querySelector('[role="listbox"]');
243
+ if (!lb) return false;
244
+ const style = window.getComputedStyle(lb);
245
+ return style.display !== "none" && style.visibility !== "hidden";
246
+ });
247
+ if (listboxVisible) minCharsToTrigger = 2;
248
+ }
249
+ const sampleSuggestions = [];
250
+ if (listboxVisible) {
251
+ const suggestions = await page.evaluate(
252
+ () => Array.from(document.querySelectorAll('[role="option"]')).slice(0, 8).map((o) => (o.textContent ?? "").trim()).filter(Boolean)
253
+ );
254
+ sampleSuggestions.push(...suggestions);
255
+ }
256
+ await input.fill("ZZXQQ_NORESULT_9999");
257
+ await page.waitForTimeout(500);
258
+ const noResultsText = await page.evaluate(() => {
259
+ const selectors = [
260
+ '[role="listbox"] [role="option"]',
261
+ '[role="status"]',
262
+ ".no-results",
263
+ ".empty-message",
264
+ '[data-testid*="no-result"]'
265
+ ];
266
+ for (const sel of selectors) {
267
+ const el = document.querySelector(sel);
268
+ if (el) {
269
+ const txt = (el.textContent ?? "").trim();
270
+ if (txt) return txt.slice(0, 120);
271
+ }
272
+ }
273
+ return null;
274
+ });
275
+ await input.fill("");
276
+ results.push({
277
+ label,
278
+ minCharsToTrigger,
279
+ sampleSuggestions,
280
+ noResultsText
281
+ });
282
+ } catch {
283
+ try {
284
+ await input.fill("");
285
+ } catch {
286
+ }
287
+ }
288
+ }
289
+ return results;
290
+ }
291
+ async function probeAdvancedUI(page) {
292
+ const defaultDragDrop = {
293
+ library: "none",
294
+ draggableCount: 0,
295
+ dropZoneCount: 0,
296
+ testAttempted: false,
297
+ testCompleted: false
298
+ };
299
+ const [dragDrop, datePickers, sliders, typeaheads] = await Promise.all([
300
+ detectDragAndDrop(page).catch(() => defaultDragDrop),
301
+ captureDatePickers(page).catch(() => []),
302
+ captureSliders(page).catch(() => []),
303
+ captureTypeaheads(page).catch(() => [])
304
+ ]);
305
+ return { dragDrop, datePickers, sliders, typeaheads };
306
+ }
307
+ export {
308
+ probeAdvancedUI
309
+ };
310
+ //# sourceMappingURL=advanced-ui-probes.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/advanced-ui-probes.ts"],
4
+ "sourcesContent": ["/**\n * Advanced UI probes \u2014 detect and exercise drag-drop, date pickers, sliders,\n * and typeahead/autocomplete widgets on a crawled page.\n *\n * Results feed ZeTa's test generator so it can emit interaction-level test\n * cases for these widget families instead of falling back to generic clicks.\n *\n * Each probe is independently try/caught so a broken widget does not abort\n * the others. All browser-side code runs inside page.evaluate / page.$$eval\n * to avoid serialisation issues with DOM handles.\n */\n\nimport type { Page } from 'playwright';\n\n// ---------------------------------------------------------------------------\n// Public interfaces\n// ---------------------------------------------------------------------------\n\nexport interface DragDropCapture {\n library: 'html5' | 'dnd-kit' | 'react-beautiful-dnd' | 'sortablejs' | 'pointer-event' | 'none';\n draggableCount: number;\n dropZoneCount: number;\n testAttempted: boolean;\n testCompleted: boolean;\n}\n\nexport interface DatePickerCapture {\n fieldLabel: string;\n pickerType: 'calendar-popup' | 'native-date' | 'native-datetime' | 'none';\n minDate?: string;\n maxDate?: string;\n disabledDatesFound: boolean;\n sampleSelectedValue?: string;\n}\n\nexport interface SliderCapture {\n label: string;\n min: number;\n max: number;\n step: number;\n currentValue: number;\n}\n\nexport interface TypeaheadCapture {\n label: string;\n minCharsToTrigger: number;\n sampleSuggestions: string[];\n noResultsText: string | null;\n}\n\nexport interface AdvancedProbeResult {\n dragDrop: DragDropCapture;\n datePickers: DatePickerCapture[];\n sliders: SliderCapture[];\n typeaheads: TypeaheadCapture[];\n}\n\n// ---------------------------------------------------------------------------\n// Internal: drag-and-drop detection\n// ---------------------------------------------------------------------------\n\nasync function detectDragAndDrop(page: Page): Promise<DragDropCapture> {\n // Detect library and count elements in the browser context\n const detected = await page.evaluate((): {\n library: DragDropCapture['library'];\n draggableCount: number;\n dropZoneCount: number;\n } => {\n // dnd-kit\n if (document.querySelector('[data-dnd-kit-draggable]')) {\n return {\n library: 'dnd-kit',\n draggableCount: document.querySelectorAll('[data-dnd-kit-draggable]').length,\n dropZoneCount: document.querySelectorAll('[data-dnd-kit-droppable]').length,\n };\n }\n // react-beautiful-dnd\n if (document.querySelector('[data-rbd-draggable-id]')) {\n return {\n library: 'react-beautiful-dnd',\n draggableCount: document.querySelectorAll('[data-rbd-draggable-id]').length,\n dropZoneCount: document.querySelectorAll('[data-rbd-droppable-id]').length,\n };\n }\n // SortableJS\n if (document.querySelector('.sortable-ghost') || document.querySelector('[data-sortable]')) {\n return {\n library: 'sortablejs',\n draggableCount: document.querySelectorAll('[data-sortable], .sortable-item').length,\n dropZoneCount: document.querySelectorAll('[data-sortable-list], .sortable-list').length,\n };\n }\n // Native HTML5 draggable\n const html5Draggables = document.querySelectorAll('[draggable=\"true\"]');\n if (html5Draggables.length > 0) {\n return {\n library: 'html5',\n draggableCount: html5Draggables.length,\n dropZoneCount: document.querySelectorAll('[data-drop-target], [ondrop], .drop-zone').length,\n };\n }\n return { library: 'none', draggableCount: 0, dropZoneCount: 0 };\n });\n\n if (detected.library === 'none') {\n return { ...detected, testAttempted: false, testCompleted: false };\n }\n\n let testAttempted = false;\n let testCompleted = false;\n\n try {\n if (detected.library === 'html5' && detected.draggableCount >= 2) {\n // Attempt a real dragAndDrop between the first two draggable elements\n const sources = await page.$$('[draggable=\"true\"]');\n if (sources.length >= 2) {\n testAttempted = true;\n await page.dragAndDrop('[draggable=\"true\"]:nth-child(1)', '[draggable=\"true\"]:nth-child(2)');\n testCompleted = true;\n }\n } else if (detected.draggableCount >= 2) {\n // Pointer-event simulation for library-managed drag\n const draggableSel =\n detected.library === 'dnd-kit' ? '[data-dnd-kit-draggable]' :\n detected.library === 'react-beautiful-dnd' ? '[data-rbd-draggable-id]' :\n '[data-sortable], .sortable-item';\n\n const elements = await page.$$(draggableSel);\n if (elements.length >= 2) {\n testAttempted = true;\n const src = elements[0];\n const dst = elements[1];\n const srcBox = await src.boundingBox();\n const dstBox = await dst.boundingBox();\n\n if (srcBox && dstBox) {\n const srcX = srcBox.x + srcBox.width / 2;\n const srcY = srcBox.y + srcBox.height / 2;\n const dstX = dstBox.x + dstBox.width / 2;\n const dstY = dstBox.y + dstBox.height / 2;\n\n await page.mouse.move(srcX, srcY);\n await page.mouse.down();\n // Move in small steps to allow drag handlers to fire\n const steps = 10;\n for (let i = 1; i <= steps; i++) {\n await page.mouse.move(\n srcX + ((dstX - srcX) * i) / steps,\n srcY + ((dstY - srcY) * i) / steps,\n );\n }\n await page.mouse.up();\n testCompleted = true;\n }\n }\n }\n } catch {\n // testCompleted remains false \u2014 that is the signal to the caller\n }\n\n return { ...detected, testAttempted, testCompleted };\n}\n\n// ---------------------------------------------------------------------------\n// Internal: date pickers\n// ---------------------------------------------------------------------------\n\nasync function captureDatePickers(page: Page): Promise<DatePickerCapture[]> {\n const results: DatePickerCapture[] = [];\n\n // Native date / datetime-local inputs\n const nativeInputs = await page.$$('input[type=\"date\"], input[type=\"datetime-local\"]');\n for (const input of nativeInputs.slice(0, 5)) {\n try {\n const attrs = await input.evaluate((el: any) => ({\n type: el.type as string,\n min: el.min || undefined,\n max: el.max || undefined,\n id: el.id || '',\n name: el.name || '',\n }));\n\n // Resolve label text\n let fieldLabel = attrs.id || attrs.name || 'unknown';\n if (attrs.id) {\n const lblText = await page.evaluate((id: string) => {\n const lbl = document.querySelector(`label[for=\"${id}\"]`);\n return lbl ? (lbl.textContent ?? '').trim() : null;\n }, attrs.id);\n if (lblText) fieldLabel = lblText;\n }\n\n // Try filling a known date and reading it back\n let sampleSelectedValue: string | undefined;\n try {\n await input.fill('2026-09-15');\n sampleSelectedValue = await input.inputValue();\n if (!sampleSelectedValue) sampleSelectedValue = undefined;\n } catch {\n // ignore fill errors\n }\n\n results.push({\n fieldLabel,\n pickerType: attrs.type === 'datetime-local' ? 'native-datetime' : 'native-date',\n minDate: attrs.min,\n maxDate: attrs.max,\n disabledDatesFound: false, // native inputs have no visual disabled cells\n sampleSelectedValue,\n });\n } catch {\n // skip this element\n }\n }\n\n // Calendar-popup pickers (custom widgets triggered by click)\n // Only look for inputs not already classified as native date inputs\n const customInputs = await page.$$('input:not([type=\"date\"]):not([type=\"datetime-local\"]):not([type=\"hidden\"])');\n for (const input of customInputs.slice(0, 5)) {\n if (results.length >= 5) break;\n\n try {\n await input.click();\n // Wait up to 2 s for a calendar grid to appear\n const calendarAppeared = await page.waitForSelector('[role=\"grid\"]', { timeout: 2000 })\n .then(() => true)\n .catch(() => false);\n\n if (!calendarAppeared) {\n await page.keyboard.press('Escape');\n continue;\n }\n\n // Capture month/year header\n const monthHeader = await page.evaluate(() => {\n const header = document.querySelector('[role=\"grid\"] caption, [role=\"columnheader\"], .calendar-header, .rdp-caption');\n return header ? (header.textContent ?? '').trim().slice(0, 60) : '';\n });\n\n // Count disabled cells\n const disabledCount = await page.evaluate(() =>\n document.querySelectorAll('[role=\"gridcell\"][aria-disabled=\"true\"], [role=\"gridcell\"].disabled').length\n );\n\n // Resolve label for this input\n const inputId = await input.evaluate((el: any) => el.id || el.name || '');\n let fieldLabel = inputId || 'datepicker';\n if (inputId) {\n const lblText = await page.evaluate((id: string) => {\n const lbl = document.querySelector(`label[for=\"${id}\"]`);\n return lbl ? (lbl.textContent ?? '').trim() : null;\n }, inputId);\n if (lblText) fieldLabel = lblText;\n }\n\n // Click the first enabled cell and read value\n let sampleSelectedValue: string | undefined;\n try {\n const firstEnabled = await page.$('[role=\"gridcell\"]:not([aria-disabled=\"true\"]):not(.disabled)');\n if (firstEnabled) {\n await firstEnabled.click();\n sampleSelectedValue = await input.inputValue().catch(() => undefined);\n }\n } catch {\n // ignore\n }\n\n await page.keyboard.press('Escape');\n\n results.push({\n fieldLabel,\n pickerType: 'calendar-popup',\n disabledDatesFound: disabledCount > 0,\n sampleSelectedValue: sampleSelectedValue || undefined,\n });\n } catch {\n try { await page.keyboard.press('Escape'); } catch { /* ignore */ }\n }\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Internal: sliders\n// ---------------------------------------------------------------------------\n\nasync function captureSliders(page: Page): Promise<SliderCapture[]> {\n const results: SliderCapture[] = [];\n\n // Collect both native range inputs and ARIA sliders\n const handles = await page.$$('input[type=\"range\"], [role=\"slider\"]');\n\n for (const handle of handles.slice(0, 5)) {\n try {\n const attrs = await handle.evaluate((el: any) => {\n const min = parseFloat(el.min ?? el.getAttribute('aria-valuemin') ?? '0');\n const max = parseFloat(el.max ?? el.getAttribute('aria-valuemax') ?? '100');\n const step = parseFloat(el.step ?? el.getAttribute('aria-valuestep') ?? '1') || 1;\n const value = parseFloat(el.value ?? el.getAttribute('aria-valuenow') ?? String(min));\n const id = el.id || el.name || '';\n return { min, max, step, value, id };\n });\n\n // Resolve label\n let label = attrs.id || 'slider';\n if (attrs.id) {\n const lblText = await page.evaluate((id: string) => {\n const lbl = document.querySelector(`label[for=\"${id}\"]`);\n return lbl ? (lbl.textContent ?? '').trim() : null;\n }, attrs.id);\n if (lblText) label = lblText;\n }\n\n // Exercise fill to min then max then restore (native range only)\n const isNative = await handle.evaluate((el: any) => el.tagName.toLowerCase() === 'input');\n if (isNative) {\n try {\n await handle.fill(String(attrs.min));\n await handle.fill(String(attrs.max));\n await handle.fill(String(attrs.value));\n } catch {\n // ignore \u2014 read-only or detached\n }\n }\n\n results.push({\n label,\n min: attrs.min,\n max: attrs.max,\n step: attrs.step,\n currentValue: attrs.value,\n });\n } catch {\n // skip this element\n }\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Internal: typeahead / autocomplete\n// ---------------------------------------------------------------------------\n\nasync function captureTypeaheads(page: Page): Promise<TypeaheadCapture[]> {\n const results: TypeaheadCapture[] = [];\n\n // Candidate selectors for typeahead inputs\n const candidates = await page.$$(\n '[role=\"combobox\"][aria-autocomplete], [aria-autocomplete=\"list\"], input.autocomplete, input[aria-autocomplete]'\n );\n\n for (const input of candidates.slice(0, 3)) {\n try {\n const inputId = await input.evaluate((el: any) => el.id || el.name || el.placeholder || '');\n\n // Resolve label\n let label = inputId || 'typeahead';\n if (inputId) {\n const lblText = await page.evaluate((id: string) => {\n const lbl = document.querySelector(`label[for=\"${id}\"]`);\n return lbl ? (lbl.textContent ?? '').trim() : null;\n }, inputId);\n if (lblText) label = lblText;\n }\n\n await input.click();\n await input.fill('');\n\n // Type 'a' and check whether a listbox appears\n await input.type('a', { delay: 100 });\n await page.waitForTimeout(500);\n\n let listboxVisible = await page.evaluate(() => {\n const lb = document.querySelector('[role=\"listbox\"]');\n if (!lb) return false;\n const style = window.getComputedStyle(lb);\n return style.display !== 'none' && style.visibility !== 'hidden';\n });\n\n let minCharsToTrigger = listboxVisible ? 1 : 2;\n\n if (!listboxVisible) {\n // Try a second character\n await input.type('b', { delay: 100 });\n await page.waitForTimeout(500);\n listboxVisible = await page.evaluate(() => {\n const lb = document.querySelector('[role=\"listbox\"]');\n if (!lb) return false;\n const style = window.getComputedStyle(lb);\n return style.display !== 'none' && style.visibility !== 'hidden';\n });\n if (listboxVisible) minCharsToTrigger = 2;\n }\n\n // Collect suggestion text from visible options\n const sampleSuggestions: string[] = [];\n if (listboxVisible) {\n const suggestions = await page.evaluate(() =>\n Array.from(document.querySelectorAll('[role=\"option\"]'))\n .slice(0, 8)\n .map(o => (o.textContent ?? '').trim())\n .filter(Boolean)\n );\n sampleSuggestions.push(...suggestions);\n }\n\n // Type a nonsense string to capture the \"no results\" message\n await input.fill('ZZXQQ_NORESULT_9999');\n await page.waitForTimeout(500);\n\n const noResultsText = await page.evaluate(() => {\n // Common no-results patterns\n const selectors = [\n '[role=\"listbox\"] [role=\"option\"]',\n '[role=\"status\"]',\n '.no-results',\n '.empty-message',\n '[data-testid*=\"no-result\"]',\n ];\n for (const sel of selectors) {\n const el = document.querySelector(sel);\n if (el) {\n const txt = (el.textContent ?? '').trim();\n if (txt) return txt.slice(0, 120);\n }\n }\n return null;\n });\n\n // Clear the input to leave the page in a clean state\n await input.fill('');\n\n results.push({\n label,\n minCharsToTrigger,\n sampleSuggestions,\n noResultsText,\n });\n } catch {\n try { await input.fill(''); } catch { /* ignore */ }\n }\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Public orchestrator\n// ---------------------------------------------------------------------------\n\nexport async function probeAdvancedUI(page: Page): Promise<AdvancedProbeResult> {\n const defaultDragDrop: DragDropCapture = {\n library: 'none',\n draggableCount: 0,\n dropZoneCount: 0,\n testAttempted: false,\n testCompleted: false,\n };\n\n const [dragDrop, datePickers, sliders, typeaheads] = await Promise.all([\n detectDragAndDrop(page).catch((): DragDropCapture => defaultDragDrop),\n captureDatePickers(page).catch((): DatePickerCapture[] => []),\n captureSliders(page).catch((): SliderCapture[] => []),\n captureTypeaheads(page).catch((): TypeaheadCapture[] => []),\n ]);\n\n return { dragDrop, datePickers, sliders, typeaheads };\n}\n"],
5
+ "mappings": "AA6DA,eAAe,kBAAkB,MAAsC;AAErE,QAAM,WAAW,MAAM,KAAK,SAAS,MAIhC;AAEH,QAAI,SAAS,cAAc,0BAA0B,GAAG;AACtD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,gBAAgB,SAAS,iBAAiB,0BAA0B,EAAE;AAAA,QACtE,eAAe,SAAS,iBAAiB,0BAA0B,EAAE;AAAA,MACvE;AAAA,IACF;AAEA,QAAI,SAAS,cAAc,yBAAyB,GAAG;AACrD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,gBAAgB,SAAS,iBAAiB,yBAAyB,EAAE;AAAA,QACrE,eAAe,SAAS,iBAAiB,yBAAyB,EAAE;AAAA,MACtE;AAAA,IACF;AAEA,QAAI,SAAS,cAAc,iBAAiB,KAAK,SAAS,cAAc,iBAAiB,GAAG;AAC1F,aAAO;AAAA,QACL,SAAS;AAAA,QACT,gBAAgB,SAAS,iBAAiB,iCAAiC,EAAE;AAAA,QAC7E,eAAe,SAAS,iBAAiB,sCAAsC,EAAE;AAAA,MACnF;AAAA,IACF;AAEA,UAAM,kBAAkB,SAAS,iBAAiB,oBAAoB;AACtE,QAAI,gBAAgB,SAAS,GAAG;AAC9B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,gBAAgB,gBAAgB;AAAA,QAChC,eAAe,SAAS,iBAAiB,0CAA0C,EAAE;AAAA,MACvF;AAAA,IACF;AACA,WAAO,EAAE,SAAS,QAAQ,gBAAgB,GAAG,eAAe,EAAE;AAAA,EAChE,CAAC;AAED,MAAI,SAAS,YAAY,QAAQ;AAC/B,WAAO,EAAE,GAAG,UAAU,eAAe,OAAO,eAAe,MAAM;AAAA,EACnE;AAEA,MAAI,gBAAgB;AACpB,MAAI,gBAAgB;AAEpB,MAAI;AACF,QAAI,SAAS,YAAY,WAAW,SAAS,kBAAkB,GAAG;AAEhE,YAAM,UAAU,MAAM,KAAK,GAAG,oBAAoB;AAClD,UAAI,QAAQ,UAAU,GAAG;AACvB,wBAAgB;AAChB,cAAM,KAAK,YAAY,mCAAmC,iCAAiC;AAC3F,wBAAgB;AAAA,MAClB;AAAA,IACF,WAAW,SAAS,kBAAkB,GAAG;AAEvC,YAAM,eACJ,SAAS,YAAY,YAAY,6BACjC,SAAS,YAAY,wBAAwB,4BAC7C;AAEF,YAAM,WAAW,MAAM,KAAK,GAAG,YAAY;AAC3C,UAAI,SAAS,UAAU,GAAG;AACxB,wBAAgB;AAChB,cAAM,MAAM,SAAS,CAAC;AACtB,cAAM,MAAM,SAAS,CAAC;AACtB,cAAM,SAAS,MAAM,IAAI,YAAY;AACrC,cAAM,SAAS,MAAM,IAAI,YAAY;AAErC,YAAI,UAAU,QAAQ;AACpB,gBAAM,OAAO,OAAO,IAAI,OAAO,QAAQ;AACvC,gBAAM,OAAO,OAAO,IAAI,OAAO,SAAS;AACxC,gBAAM,OAAO,OAAO,IAAI,OAAO,QAAQ;AACvC,gBAAM,OAAO,OAAO,IAAI,OAAO,SAAS;AAExC,gBAAM,KAAK,MAAM,KAAK,MAAM,IAAI;AAChC,gBAAM,KAAK,MAAM,KAAK;AAEtB,gBAAM,QAAQ;AACd,mBAAS,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,kBAAM,KAAK,MAAM;AAAA,cACf,QAAS,OAAO,QAAQ,IAAK;AAAA,cAC7B,QAAS,OAAO,QAAQ,IAAK;AAAA,YAC/B;AAAA,UACF;AACA,gBAAM,KAAK,MAAM,GAAG;AACpB,0BAAgB;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO,EAAE,GAAG,UAAU,eAAe,cAAc;AACrD;AAMA,eAAe,mBAAmB,MAA0C;AAC1E,QAAM,UAA+B,CAAC;AAGtC,QAAM,eAAe,MAAM,KAAK,GAAG,kDAAkD;AACrF,aAAW,SAAS,aAAa,MAAM,GAAG,CAAC,GAAG;AAC5C,QAAI;AACF,YAAM,QAAQ,MAAM,MAAM,SAAS,CAAC,QAAa;AAAA,QAC/C,MAAM,GAAG;AAAA,QACT,KAAK,GAAG,OAAO;AAAA,QACf,KAAK,GAAG,OAAO;AAAA,QACf,IAAI,GAAG,MAAM;AAAA,QACb,MAAM,GAAG,QAAQ;AAAA,MACnB,EAAE;AAGF,UAAI,aAAa,MAAM,MAAM,MAAM,QAAQ;AAC3C,UAAI,MAAM,IAAI;AACZ,cAAM,UAAU,MAAM,KAAK,SAAS,CAAC,OAAe;AAClD,gBAAM,MAAM,SAAS,cAAc,cAAc,EAAE,IAAI;AACvD,iBAAO,OAAO,IAAI,eAAe,IAAI,KAAK,IAAI;AAAA,QAChD,GAAG,MAAM,EAAE;AACX,YAAI,QAAS,cAAa;AAAA,MAC5B;AAGA,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,KAAK,YAAY;AAC7B,8BAAsB,MAAM,MAAM,WAAW;AAC7C,YAAI,CAAC,oBAAqB,uBAAsB;AAAA,MAClD,QAAQ;AAAA,MAER;AAEA,cAAQ,KAAK;AAAA,QACX;AAAA,QACA,YAAY,MAAM,SAAS,mBAAmB,oBAAoB;AAAA,QAClE,SAAS,MAAM;AAAA,QACf,SAAS,MAAM;AAAA,QACf,oBAAoB;AAAA;AAAA,QACpB;AAAA,MACF,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAIA,QAAM,eAAe,MAAM,KAAK,GAAG,4EAA4E;AAC/G,aAAW,SAAS,aAAa,MAAM,GAAG,CAAC,GAAG;AAC5C,QAAI,QAAQ,UAAU,EAAG;AAEzB,QAAI;AACF,YAAM,MAAM,MAAM;AAElB,YAAM,mBAAmB,MAAM,KAAK,gBAAgB,iBAAiB,EAAE,SAAS,IAAK,CAAC,EACnF,KAAK,MAAM,IAAI,EACf,MAAM,MAAM,KAAK;AAEpB,UAAI,CAAC,kBAAkB;AACrB,cAAM,KAAK,SAAS,MAAM,QAAQ;AAClC;AAAA,MACF;AAGA,YAAM,cAAc,MAAM,KAAK,SAAS,MAAM;AAC5C,cAAM,SAAS,SAAS,cAAc,8EAA8E;AACpH,eAAO,UAAU,OAAO,eAAe,IAAI,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI;AAAA,MACnE,CAAC;AAGD,YAAM,gBAAgB,MAAM,KAAK;AAAA,QAAS,MACxC,SAAS,iBAAiB,qEAAqE,EAAE;AAAA,MACnG;AAGA,YAAM,UAAU,MAAM,MAAM,SAAS,CAAC,OAAY,GAAG,MAAM,GAAG,QAAQ,EAAE;AACxE,UAAI,aAAa,WAAW;AAC5B,UAAI,SAAS;AACX,cAAM,UAAU,MAAM,KAAK,SAAS,CAAC,OAAe;AAClD,gBAAM,MAAM,SAAS,cAAc,cAAc,EAAE,IAAI;AACvD,iBAAO,OAAO,IAAI,eAAe,IAAI,KAAK,IAAI;AAAA,QAChD,GAAG,OAAO;AACV,YAAI,QAAS,cAAa;AAAA,MAC5B;AAGA,UAAI;AACJ,UAAI;AACF,cAAM,eAAe,MAAM,KAAK,EAAE,8DAA8D;AAChG,YAAI,cAAc;AAChB,gBAAM,aAAa,MAAM;AACzB,gCAAsB,MAAM,MAAM,WAAW,EAAE,MAAM,MAAM,MAAS;AAAA,QACtE;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,YAAM,KAAK,SAAS,MAAM,QAAQ;AAElC,cAAQ,KAAK;AAAA,QACX;AAAA,QACA,YAAY;AAAA,QACZ,oBAAoB,gBAAgB;AAAA,QACpC,qBAAqB,uBAAuB;AAAA,MAC9C,CAAC;AAAA,IACH,QAAQ;AACN,UAAI;AAAE,cAAM,KAAK,SAAS,MAAM,QAAQ;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IACpE;AAAA,EACF;AAEA,SAAO;AACT;AAMA,eAAe,eAAe,MAAsC;AAClE,QAAM,UAA2B,CAAC;AAGlC,QAAM,UAAU,MAAM,KAAK,GAAG,sCAAsC;AAEpE,aAAW,UAAU,QAAQ,MAAM,GAAG,CAAC,GAAG;AACxC,QAAI;AACF,YAAM,QAAQ,MAAM,OAAO,SAAS,CAAC,OAAY;AAC/C,cAAM,MAAM,WAAW,GAAG,OAAO,GAAG,aAAa,eAAe,KAAK,GAAG;AACxE,cAAM,MAAM,WAAW,GAAG,OAAO,GAAG,aAAa,eAAe,KAAK,KAAK;AAC1E,cAAM,OAAO,WAAW,GAAG,QAAQ,GAAG,aAAa,gBAAgB,KAAK,GAAG,KAAK;AAChF,cAAM,QAAQ,WAAW,GAAG,SAAS,GAAG,aAAa,eAAe,KAAK,OAAO,GAAG,CAAC;AACpF,cAAM,KAAK,GAAG,MAAM,GAAG,QAAQ;AAC/B,eAAO,EAAE,KAAK,KAAK,MAAM,OAAO,GAAG;AAAA,MACrC,CAAC;AAGD,UAAI,QAAQ,MAAM,MAAM;AACxB,UAAI,MAAM,IAAI;AACZ,cAAM,UAAU,MAAM,KAAK,SAAS,CAAC,OAAe;AAClD,gBAAM,MAAM,SAAS,cAAc,cAAc,EAAE,IAAI;AACvD,iBAAO,OAAO,IAAI,eAAe,IAAI,KAAK,IAAI;AAAA,QAChD,GAAG,MAAM,EAAE;AACX,YAAI,QAAS,SAAQ;AAAA,MACvB;AAGA,YAAM,WAAW,MAAM,OAAO,SAAS,CAAC,OAAY,GAAG,QAAQ,YAAY,MAAM,OAAO;AACxF,UAAI,UAAU;AACZ,YAAI;AACF,gBAAM,OAAO,KAAK,OAAO,MAAM,GAAG,CAAC;AACnC,gBAAM,OAAO,KAAK,OAAO,MAAM,GAAG,CAAC;AACnC,gBAAM,OAAO,KAAK,OAAO,MAAM,KAAK,CAAC;AAAA,QACvC,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,cAAQ,KAAK;AAAA,QACX;AAAA,QACA,KAAK,MAAM;AAAA,QACX,KAAK,MAAM;AAAA,QACX,MAAM,MAAM;AAAA,QACZ,cAAc,MAAM;AAAA,MACtB,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAMA,eAAe,kBAAkB,MAAyC;AACxE,QAAM,UAA8B,CAAC;AAGrC,QAAM,aAAa,MAAM,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,aAAW,SAAS,WAAW,MAAM,GAAG,CAAC,GAAG;AAC1C,QAAI;AACF,YAAM,UAAU,MAAM,MAAM,SAAS,CAAC,OAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,eAAe,EAAE;AAG1F,UAAI,QAAQ,WAAW;AACvB,UAAI,SAAS;AACX,cAAM,UAAU,MAAM,KAAK,SAAS,CAAC,OAAe;AAClD,gBAAM,MAAM,SAAS,cAAc,cAAc,EAAE,IAAI;AACvD,iBAAO,OAAO,IAAI,eAAe,IAAI,KAAK,IAAI;AAAA,QAChD,GAAG,OAAO;AACV,YAAI,QAAS,SAAQ;AAAA,MACvB;AAEA,YAAM,MAAM,MAAM;AAClB,YAAM,MAAM,KAAK,EAAE;AAGnB,YAAM,MAAM,KAAK,KAAK,EAAE,OAAO,IAAI,CAAC;AACpC,YAAM,KAAK,eAAe,GAAG;AAE7B,UAAI,iBAAiB,MAAM,KAAK,SAAS,MAAM;AAC7C,cAAM,KAAK,SAAS,cAAc,kBAAkB;AACpD,YAAI,CAAC,GAAI,QAAO;AAChB,cAAM,QAAQ,OAAO,iBAAiB,EAAE;AACxC,eAAO,MAAM,YAAY,UAAU,MAAM,eAAe;AAAA,MAC1D,CAAC;AAED,UAAI,oBAAoB,iBAAiB,IAAI;AAE7C,UAAI,CAAC,gBAAgB;AAEnB,cAAM,MAAM,KAAK,KAAK,EAAE,OAAO,IAAI,CAAC;AACpC,cAAM,KAAK,eAAe,GAAG;AAC7B,yBAAiB,MAAM,KAAK,SAAS,MAAM;AACzC,gBAAM,KAAK,SAAS,cAAc,kBAAkB;AACpD,cAAI,CAAC,GAAI,QAAO;AAChB,gBAAM,QAAQ,OAAO,iBAAiB,EAAE;AACxC,iBAAO,MAAM,YAAY,UAAU,MAAM,eAAe;AAAA,QAC1D,CAAC;AACD,YAAI,eAAgB,qBAAoB;AAAA,MAC1C;AAGA,YAAM,oBAA8B,CAAC;AACrC,UAAI,gBAAgB;AAClB,cAAM,cAAc,MAAM,KAAK;AAAA,UAAS,MACtC,MAAM,KAAK,SAAS,iBAAiB,iBAAiB,CAAC,EACpD,MAAM,GAAG,CAAC,EACV,IAAI,QAAM,EAAE,eAAe,IAAI,KAAK,CAAC,EACrC,OAAO,OAAO;AAAA,QACnB;AACA,0BAAkB,KAAK,GAAG,WAAW;AAAA,MACvC;AAGA,YAAM,MAAM,KAAK,qBAAqB;AACtC,YAAM,KAAK,eAAe,GAAG;AAE7B,YAAM,gBAAgB,MAAM,KAAK,SAAS,MAAM;AAE9C,cAAM,YAAY;AAAA,UAChB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,mBAAW,OAAO,WAAW;AAC3B,gBAAM,KAAK,SAAS,cAAc,GAAG;AACrC,cAAI,IAAI;AACN,kBAAM,OAAO,GAAG,eAAe,IAAI,KAAK;AACxC,gBAAI,IAAK,QAAO,IAAI,MAAM,GAAG,GAAG;AAAA,UAClC;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AAGD,YAAM,MAAM,KAAK,EAAE;AAEnB,cAAQ,KAAK;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,QAAQ;AACN,UAAI;AAAE,cAAM,MAAM,KAAK,EAAE;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IACrD;AAAA,EACF;AAEA,SAAO;AACT;AAMA,eAAsB,gBAAgB,MAA0C;AAC9E,QAAM,kBAAmC;AAAA,IACvC,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,eAAe;AAAA,IACf,eAAe;AAAA,EACjB;AAEA,QAAM,CAAC,UAAU,aAAa,SAAS,UAAU,IAAI,MAAM,QAAQ,IAAI;AAAA,IACrE,kBAAkB,IAAI,EAAE,MAAM,MAAuB,eAAe;AAAA,IACpE,mBAAmB,IAAI,EAAE,MAAM,MAA2B,CAAC,CAAC;AAAA,IAC5D,eAAe,IAAI,EAAE,MAAM,MAAuB,CAAC,CAAC;AAAA,IACpD,kBAAkB,IAAI,EAAE,MAAM,MAA0B,CAAC,CAAC;AAAA,EAC5D,CAAC;AAED,SAAO,EAAE,UAAU,aAAa,SAAS,WAAW;AACtD;",
6
+ "names": []
7
+ }
@@ -2,9 +2,9 @@ function estimateTokens(text) {
2
2
  return Math.ceil(text.length / 4);
3
3
  }
4
4
  function chunkContent(text, opts) {
5
- const size = opts.chunkSize ?? 2e3;
5
+ const size = opts.chunkSize ?? (parseInt(process.env.CRAWLER_CONTENT_CHUNK_SIZE ?? "") || 2e3);
6
6
  const charSize = size * 4;
7
- const overlap = opts.overlap ?? 200;
7
+ const overlap = opts.overlap ?? (parseInt(process.env.CRAWLER_CONTENT_CHUNK_OVERLAP ?? "") || 200);
8
8
  const overlapChars = overlap * 4;
9
9
  switch (opts.strategy) {
10
10
  case "fixed":
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/content-chunker.ts"],
4
- "sourcesContent": ["export type ChunkingStrategy = 'fixed' | 'overlap' | 'sentence' | 'paragraph' | 'markdown-heading' | 'semantic' | 'recursive';\n\nexport interface ChunkOpts {\n strategy: ChunkingStrategy;\n chunkSize?: number; // token estimate target (1 token \u2248 4 chars). Default 2000.\n overlap?: number; // overlap tokens for 'overlap' strategy. Default 200.\n separators?: string[]; // for 'recursive' strategy. Default ['\\\\n\\\\n', '\\\\n', '. ', ' ']\n}\n\nexport interface Chunk {\n index: number;\n text: string;\n tokenEstimate: number;\n startChar: number;\n endChar: number;\n heading?: string;\n}\n\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nexport function chunkContent(text: string, opts: ChunkOpts): Chunk[] {\n const size = opts.chunkSize ?? 2000;\n const charSize = size * 4;\n const overlap = opts.overlap ?? 200;\n const overlapChars = overlap * 4;\n\n switch (opts.strategy) {\n case 'fixed': return fixedChunk(text, charSize);\n case 'overlap': return overlapChunk(text, charSize, overlapChars);\n case 'sentence': return sentenceChunk(text, charSize);\n case 'paragraph': return paragraphChunk(text, charSize);\n case 'markdown-heading': return markdownHeadingChunk(text, charSize);\n case 'semantic': return semanticChunk(text, charSize);\n case 'recursive': return recursiveChunk(text, charSize, opts.separators ?? ['\\n\\n', '\\n', '. ', ' ']);\n default: return fixedChunk(text, charSize);\n }\n}\n\nfunction makeChunk(text: string, index: number, startChar: number, heading?: string): Chunk {\n return { index, text, tokenEstimate: estimateTokens(text), startChar, endChar: startChar + text.length, heading };\n}\n\nfunction fixedChunk(text: string, charSize: number): Chunk[] {\n const chunks: Chunk[] = [];\n let i = 0;\n while (i < text.length) {\n const slice = text.slice(i, i + charSize);\n chunks.push(makeChunk(slice, chunks.length, i));\n i += charSize;\n }\n return chunks;\n}\n\nfunction overlapChunk(text: string, charSize: number, overlapChars: number): Chunk[] {\n const chunks: Chunk[] = [];\n let i = 0;\n while (i < text.length) {\n const slice = text.slice(i, i + charSize);\n chunks.push(makeChunk(slice, chunks.length, i));\n i += charSize - overlapChars;\n if (i <= 0) break;\n }\n return chunks;\n}\n\nfunction sentenceChunk(text: string, charSize: number): Chunk[] {\n const sentences = text.match(/[^.!?]*[.!?]+[\"']?\\s*/g) ?? [text];\n return groupIntoChunks(sentences, charSize);\n}\n\nfunction paragraphChunk(text: string, charSize: number): Chunk[] {\n const paras = text.split(/\\n\\n+/).filter(p => p.trim());\n return groupIntoChunks(paras, charSize);\n}\n\nfunction markdownHeadingChunk(text: string, charSize: number): Chunk[] {\n const chunks: Chunk[] = [];\n const headingRe = /^(#{1,3}\\s.+)$/m;\n const sections = text.split(/(?=^#{1,3}\\s)/m).filter(s => s.trim());\n let pos = 0;\n for (const section of sections) {\n const headingMatch = section.match(headingRe);\n const heading = headingMatch ? headingMatch[1].replace(/^#+\\s*/, '').trim() : undefined;\n if (section.length <= charSize) {\n chunks.push({ ...makeChunk(section.trim(), chunks.length, pos), heading });\n } else {\n // Section too large \u2014 sub-chunk by paragraph\n const sub = paragraphChunk(section, charSize);\n for (const s of sub) {\n chunks.push({ ...s, index: chunks.length, startChar: pos + s.startChar, endChar: pos + s.endChar, heading });\n }\n }\n pos += section.length;\n }\n return chunks.length > 0 ? chunks : fixedChunk(text, charSize);\n}\n\nfunction semanticChunk(text: string, charSize: number): Chunk[] {\n // Like paragraph but merges short paragraphs with the next\n const paras = text.split(/\\n\\n+/).filter(p => p.trim());\n const merged: string[] = [];\n let current = '';\n for (const para of paras) {\n if (current.length + para.length + 2 <= charSize) {\n current = current ? current + '\\n\\n' + para : para;\n } else {\n if (current) merged.push(current);\n current = para;\n }\n }\n if (current) merged.push(current);\n return groupIntoChunks(merged, charSize);\n}\n\nfunction recursiveChunk(text: string, charSize: number, separators: string[]): Chunk[] {\n if (text.length <= charSize) return [makeChunk(text, 0, 0)];\n const [sep, ...rest] = separators;\n if (!sep) return fixedChunk(text, charSize);\n const parts = text.split(sep).filter(p => p);\n const chunks: Chunk[] = [];\n let pos = 0;\n for (const part of parts) {\n if (part.length <= charSize) {\n chunks.push(makeChunk(part, chunks.length, pos));\n } else {\n const sub = recursiveChunk(part, charSize, rest);\n for (const s of sub) chunks.push({ ...s, index: chunks.length, startChar: pos + s.startChar, endChar: pos + s.endChar });\n }\n pos += part.length + sep.length;\n }\n return chunks;\n}\n\nfunction groupIntoChunks(parts: string[], charSize: number): Chunk[] {\n const chunks: Chunk[] = [];\n let current = '';\n let startChar = 0;\n let pos = 0;\n for (const part of parts) {\n if (current.length + part.length + 1 > charSize && current) {\n chunks.push(makeChunk(current.trim(), chunks.length, startChar));\n startChar = pos;\n current = part;\n } else {\n current = current ? current + ' ' + part : part;\n }\n pos += part.length + 1;\n }\n if (current.trim()) chunks.push(makeChunk(current.trim(), chunks.length, startChar));\n return chunks;\n}\n\nexport function mergeSmallChunks(chunks: Chunk[], minTokens: number): Chunk[] {\n const minChars = minTokens * 4;\n const result: Chunk[] = [];\n let i = 0;\n while (i < chunks.length) {\n if (chunks[i].text.length < minChars && i + 1 < chunks.length) {\n const merged = chunks[i].text + '\\n\\n' + chunks[i + 1].text;\n result.push(makeChunk(merged, result.length, chunks[i].startChar, chunks[i].heading ?? chunks[i + 1].heading));\n i += 2;\n } else {\n result.push({ ...chunks[i], index: result.length });\n i++;\n }\n }\n return result;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["export type ChunkingStrategy = 'fixed' | 'overlap' | 'sentence' | 'paragraph' | 'markdown-heading' | 'semantic' | 'recursive';\n\nexport interface ChunkOpts {\n strategy: ChunkingStrategy;\n chunkSize?: number; // token estimate target (1 token \u2248 4 chars). Default 2000.\n overlap?: number; // overlap tokens for 'overlap' strategy. Default 200.\n separators?: string[]; // for 'recursive' strategy. Default ['\\\\n\\\\n', '\\\\n', '. ', ' ']\n}\n\nexport interface Chunk {\n index: number;\n text: string;\n tokenEstimate: number;\n startChar: number;\n endChar: number;\n heading?: string;\n}\n\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nexport function chunkContent(text: string, opts: ChunkOpts): Chunk[] {\n const size = opts.chunkSize ?? (parseInt(process.env.CRAWLER_CONTENT_CHUNK_SIZE ?? '') || 2000);\n const charSize = size * 4;\n const overlap = opts.overlap ?? (parseInt(process.env.CRAWLER_CONTENT_CHUNK_OVERLAP ?? '') || 200);\n const overlapChars = overlap * 4;\n\n switch (opts.strategy) {\n case 'fixed': return fixedChunk(text, charSize);\n case 'overlap': return overlapChunk(text, charSize, overlapChars);\n case 'sentence': return sentenceChunk(text, charSize);\n case 'paragraph': return paragraphChunk(text, charSize);\n case 'markdown-heading': return markdownHeadingChunk(text, charSize);\n case 'semantic': return semanticChunk(text, charSize);\n case 'recursive': return recursiveChunk(text, charSize, opts.separators ?? ['\\n\\n', '\\n', '. ', ' ']);\n default: return fixedChunk(text, charSize);\n }\n}\n\nfunction makeChunk(text: string, index: number, startChar: number, heading?: string): Chunk {\n return { index, text, tokenEstimate: estimateTokens(text), startChar, endChar: startChar + text.length, heading };\n}\n\nfunction fixedChunk(text: string, charSize: number): Chunk[] {\n const chunks: Chunk[] = [];\n let i = 0;\n while (i < text.length) {\n const slice = text.slice(i, i + charSize);\n chunks.push(makeChunk(slice, chunks.length, i));\n i += charSize;\n }\n return chunks;\n}\n\nfunction overlapChunk(text: string, charSize: number, overlapChars: number): Chunk[] {\n const chunks: Chunk[] = [];\n let i = 0;\n while (i < text.length) {\n const slice = text.slice(i, i + charSize);\n chunks.push(makeChunk(slice, chunks.length, i));\n i += charSize - overlapChars;\n if (i <= 0) break;\n }\n return chunks;\n}\n\nfunction sentenceChunk(text: string, charSize: number): Chunk[] {\n const sentences = text.match(/[^.!?]*[.!?]+[\"']?\\s*/g) ?? [text];\n return groupIntoChunks(sentences, charSize);\n}\n\nfunction paragraphChunk(text: string, charSize: number): Chunk[] {\n const paras = text.split(/\\n\\n+/).filter(p => p.trim());\n return groupIntoChunks(paras, charSize);\n}\n\nfunction markdownHeadingChunk(text: string, charSize: number): Chunk[] {\n const chunks: Chunk[] = [];\n const headingRe = /^(#{1,3}\\s.+)$/m;\n const sections = text.split(/(?=^#{1,3}\\s)/m).filter(s => s.trim());\n let pos = 0;\n for (const section of sections) {\n const headingMatch = section.match(headingRe);\n const heading = headingMatch ? headingMatch[1].replace(/^#+\\s*/, '').trim() : undefined;\n if (section.length <= charSize) {\n chunks.push({ ...makeChunk(section.trim(), chunks.length, pos), heading });\n } else {\n // Section too large \u2014 sub-chunk by paragraph\n const sub = paragraphChunk(section, charSize);\n for (const s of sub) {\n chunks.push({ ...s, index: chunks.length, startChar: pos + s.startChar, endChar: pos + s.endChar, heading });\n }\n }\n pos += section.length;\n }\n return chunks.length > 0 ? chunks : fixedChunk(text, charSize);\n}\n\nfunction semanticChunk(text: string, charSize: number): Chunk[] {\n // Like paragraph but merges short paragraphs with the next\n const paras = text.split(/\\n\\n+/).filter(p => p.trim());\n const merged: string[] = [];\n let current = '';\n for (const para of paras) {\n if (current.length + para.length + 2 <= charSize) {\n current = current ? current + '\\n\\n' + para : para;\n } else {\n if (current) merged.push(current);\n current = para;\n }\n }\n if (current) merged.push(current);\n return groupIntoChunks(merged, charSize);\n}\n\nfunction recursiveChunk(text: string, charSize: number, separators: string[]): Chunk[] {\n if (text.length <= charSize) return [makeChunk(text, 0, 0)];\n const [sep, ...rest] = separators;\n if (!sep) return fixedChunk(text, charSize);\n const parts = text.split(sep).filter(p => p);\n const chunks: Chunk[] = [];\n let pos = 0;\n for (const part of parts) {\n if (part.length <= charSize) {\n chunks.push(makeChunk(part, chunks.length, pos));\n } else {\n const sub = recursiveChunk(part, charSize, rest);\n for (const s of sub) chunks.push({ ...s, index: chunks.length, startChar: pos + s.startChar, endChar: pos + s.endChar });\n }\n pos += part.length + sep.length;\n }\n return chunks;\n}\n\nfunction groupIntoChunks(parts: string[], charSize: number): Chunk[] {\n const chunks: Chunk[] = [];\n let current = '';\n let startChar = 0;\n let pos = 0;\n for (const part of parts) {\n if (current.length + part.length + 1 > charSize && current) {\n chunks.push(makeChunk(current.trim(), chunks.length, startChar));\n startChar = pos;\n current = part;\n } else {\n current = current ? current + ' ' + part : part;\n }\n pos += part.length + 1;\n }\n if (current.trim()) chunks.push(makeChunk(current.trim(), chunks.length, startChar));\n return chunks;\n}\n\nexport function mergeSmallChunks(chunks: Chunk[], minTokens: number): Chunk[] {\n const minChars = minTokens * 4;\n const result: Chunk[] = [];\n let i = 0;\n while (i < chunks.length) {\n if (chunks[i].text.length < minChars && i + 1 < chunks.length) {\n const merged = chunks[i].text + '\\n\\n' + chunks[i + 1].text;\n result.push(makeChunk(merged, result.length, chunks[i].startChar, chunks[i].heading ?? chunks[i + 1].heading));\n i += 2;\n } else {\n result.push({ ...chunks[i], index: result.length });\n i++;\n }\n }\n return result;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }