fast-replay 0.4.0 → 0.4.1

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.
@@ -23,7 +23,17 @@ const TEST_ID_ATTRS = [
23
23
  'data-sentry-label',
24
24
  'data-testid-label',
25
25
  ];
26
- const MAX_CANDIDATES = 5;
26
+ const MAX_CANDIDATES = 6;
27
+ /**
28
+ * Elements that handle a click themselves.
29
+ *
30
+ * `data-focusable` is React Native Web's marker for a pressable, and `tabindex`
31
+ * is how any framework makes a plain div operable. Both are far more common in
32
+ * a component library than a `<button>` element.
33
+ */
34
+ const INTERACTIVE = 'button, a[href], [role="button"], [role="link"], [role="menuitem"], [role="menuitemradio"], ' +
35
+ '[role="option"], [role="tab"], [role="checkbox"], [role="radio"], [role="switch"], ' +
36
+ '[tabindex], [data-focusable="true"], [onclick]';
27
37
  const MAX_CSS_PATH_DEPTH = 6;
28
38
  export function testIdSelector(el) {
29
39
  for (const attr of TEST_ID_ATTRS) {
@@ -146,6 +156,11 @@ export function buildCandidates(el) {
146
156
  const matches = allElements().filter((c) => getRole(c) === role && accessibleName(c) === name);
147
157
  push(withNth(roleName, el, matches));
148
158
  }
159
+ // The control that handles the click, when the cursor was over its label.
160
+ // Ranked here because acting on the control is more faithful than acting on
161
+ // a text node that merely happens to sit inside it.
162
+ for (const selector of interactiveHostSelectors(el))
163
+ push(selector);
149
164
  // Ranked above the CSS path on purpose: when a click lands on an inner node
150
165
  // of a labelled control, `[data-testid="x"]` is strictly more durable than
151
166
  // `[data-testid="x"] > span` or a chain of :nth-of-type positions.
@@ -170,6 +185,53 @@ export function buildCandidates(el) {
170
185
  }
171
186
  return out.slice(0, MAX_CANDIDATES);
172
187
  }
188
+ /**
189
+ * Selectors for the control the click will actually be handled by.
190
+ *
191
+ * A card-style option — an icon and a label inside a pressable wrapper — puts
192
+ * the cursor over the label, so that is what the click event names. Targeting
193
+ * the label works only as long as the click reaches the wrapper: text nodes in
194
+ * a component library are routinely `pointer-events: none`, and a wrapper is
195
+ * the thing that keeps its identity when the label's markup changes.
196
+ */
197
+ function interactiveHostSelectors(el) {
198
+ let host = null;
199
+ try {
200
+ const found = el.closest(INTERACTIVE);
201
+ host = found && found !== el ? found : null;
202
+ }
203
+ catch {
204
+ host = null;
205
+ }
206
+ if (!host)
207
+ return [];
208
+ const out = [];
209
+ const push = (s) => {
210
+ if (s && out.indexOf(s) === -1)
211
+ out.push(s);
212
+ };
213
+ push(testIdSelector(host));
214
+ push(idSelector(host));
215
+ push(roleNameSelector(host));
216
+ // Nothing identifies it, so name it by the control's own visible text. A
217
+ // wrapper that contains the label is exactly what `:has-text` selects, and it
218
+ // survives the label being re-wrapped or restyled.
219
+ const label = renderedText(host, 60);
220
+ if (label) {
221
+ const tag = host.tagName.toLowerCase();
222
+ const role = host.getAttribute('role');
223
+ const qualifier = host.getAttribute('data-focusable')
224
+ ? '[data-focusable="true"]'
225
+ : role
226
+ ? `[role="${escAttr(role)}"]`
227
+ : host.hasAttribute('tabindex')
228
+ ? `[tabindex="${escAttr(host.getAttribute('tabindex') ?? '0')}"]`
229
+ : '';
230
+ if (qualifier)
231
+ push(`${tag}${qualifier}:has-text("${escAttr(label)}")`);
232
+ }
233
+ return out;
234
+ }
173
235
  /** True when the selector's uniqueness rests entirely on :nth-of-type. */
174
236
  function isPositionalOnly(selector) {
175
237
  if (!selector)
@@ -1 +1 @@
1
- {"version":3,"file":"selectors.js","sourceRoot":"","sources":["../../../src/recorder/agent/selectors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"selectors.js","sourceRoot":"","sources":["../../../src/recorder/agent/selectors.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
1
  /** The in-page capture agent, bundled as a standalone IIFE. */
2
- export declare const AGENT_BUNDLE = "\"use strict\";\n(() => {\n // src/recorder/agent/config.ts\n var INSTALL_GLOBAL = \"__replayInstall\";\n var AGENT_READY_FLAG = \"__replayAgentReady\";\n var FLUSH_GLOBAL = \"__replayFlush\";\n\n // src/recorder/agent/text.ts\n function clean(s, max = 80) {\n if (!s) return \"\";\n const t = s.replace(/\\s+/g, \" \").trim();\n return t.length > max ? `${t.slice(0, max - 1)}\\u2026` : t;\n }\n function escAttr(v) {\n return v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n }\n function escId(v) {\n return typeof window.CSS?.escape === \"function\" ? window.CSS.escape(v) : v.replace(/([^\\w-])/g, \"\\\\$1\");\n }\n function isStableToken(t) {\n if (!t || t.length > 40) return false;\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t)) return false;\n if (/^(css|sc|emotion|styled|jsx|glamor|makeStyles)-/i.test(t)) return false;\n if (/[0-9a-f]{6,}/i.test(t)) return false;\n if ((t.match(/\\d/g) || []).length >= 3) return false;\n if (/[A-Z]/.test(t) && /\\d/.test(t)) return false;\n if (/^[a-z]{1,2}-[a-z0-9]{5,}$/i.test(t) && /\\d/.test(t)) return false;\n if (t.split(/[-_]/).some(looksGenerated)) return false;\n if (/-(aria|diff|live|announcer|portal)$/i.test(t)) return false;\n return true;\n }\n function looksGenerated(segment) {\n if (segment.length < 6) return false;\n if (!/^[a-z0-9]+$/i.test(segment)) return false;\n const vowels = (segment.match(/[aeiouy]/gi) || []).length;\n return /\\d/.test(segment) ? vowels <= 1 : vowels === 0;\n }\n function isStableClass(c) {\n return isStableToken(c) && !/^_/.test(c);\n }\n function renderedText(el, max = 80) {\n const inner = el.innerText;\n return clean(typeof inner === \"string\" ? inner : el.textContent, max);\n }\n\n // src/recorder/agent/roles.ts\n var TAG_ROLES = {\n button: \"button\",\n select: \"combobox\",\n textarea: \"textbox\",\n nav: \"navigation\",\n main: \"main\",\n header: \"banner\",\n footer: \"contentinfo\",\n aside: \"complementary\",\n form: \"form\",\n table: \"table\",\n tr: \"row\",\n td: \"cell\",\n th: \"columnheader\",\n ul: \"list\",\n ol: \"list\",\n li: \"listitem\",\n dialog: \"dialog\",\n option: \"option\",\n h1: \"heading\",\n h2: \"heading\",\n h3: \"heading\",\n h4: \"heading\",\n h5: \"heading\",\n h6: \"heading\"\n };\n var INPUT_ROLES = {\n button: \"button\",\n submit: \"button\",\n reset: \"button\",\n image: \"button\",\n checkbox: \"checkbox\",\n radio: \"radio\",\n range: \"slider\",\n number: \"spinbutton\",\n search: \"searchbox\",\n email: \"textbox\",\n tel: \"textbox\",\n text: \"textbox\",\n url: \"textbox\"\n };\n var NAME_FROM_CONTENT = [\n \"button\",\n \"link\",\n \"heading\",\n \"cell\",\n \"columnheader\",\n \"rowheader\",\n \"listitem\",\n \"option\",\n \"tab\",\n \"menuitem\",\n \"menuitemcheckbox\",\n \"menuitemradio\",\n \"treeitem\",\n \"switch\",\n \"checkbox\",\n \"radio\"\n ];\n var NON_EDITABLE_INPUTS = [\"checkbox\", \"radio\", \"button\", \"submit\", \"reset\", \"file\", \"image\"];\n function getRole(el) {\n const explicit = el.getAttribute(\"role\");\n if (explicit) return explicit.trim().split(/\\s+/)[0] || null;\n const tag = el.tagName.toLowerCase();\n if (tag === \"a\") return el.hasAttribute(\"href\") ? \"link\" : null;\n if (tag === \"img\") return el.getAttribute(\"alt\") === \"\" ? null : \"img\";\n if (tag === \"input\") {\n const type = (el.getAttribute(\"type\") || \"text\").toLowerCase();\n return INPUT_ROLES[type] ?? null;\n }\n return TAG_ROLES[tag] ?? null;\n }\n function isEditable(el) {\n if (!el || el.nodeType !== 1) return false;\n const tag = el.tagName.toLowerCase();\n if (tag === \"textarea\") return true;\n if (el.isContentEditable) return true;\n if (tag !== \"input\") return false;\n const type = (el.getAttribute(\"type\") || \"text\").toLowerCase();\n return !NON_EDITABLE_INPUTS.includes(type);\n }\n function accessibleName(el) {\n const aria = el.getAttribute(\"aria-label\");\n if (aria && aria.trim()) return clean(aria);\n const labelledby = el.getAttribute(\"aria-labelledby\");\n if (labelledby) {\n const parts = labelledby.split(/\\s+/).map((id) => document.getElementById(id)).filter((n) => !!n).map((n) => clean(n.textContent));\n const joined = clean(parts.filter(Boolean).join(\" \"));\n if (joined) return joined;\n }\n const tag = el.tagName.toLowerCase();\n if ([\"input\", \"select\", \"textarea\"].includes(tag)) {\n const id = el.getAttribute(\"id\");\n if (id) {\n const forLabel = document.querySelector(`label[for=\"${escAttr(id)}\"]`);\n if (forLabel) {\n const t = clean(forLabel.textContent);\n if (t) return t;\n }\n }\n const wrapping = el.closest(\"label\");\n if (wrapping) {\n const t = clean(wrapping.textContent);\n if (t) return t;\n }\n }\n for (const attr of [\"alt\", \"title\", \"placeholder\"]) {\n const v = el.getAttribute(attr);\n if (v && v.trim()) return clean(v);\n }\n const role = getRole(el);\n if (role && NAME_FROM_CONTENT.includes(role)) {\n const t = clean(el.textContent);\n if (t) return t;\n }\n return \"\";\n }\n function ownText(el) {\n return clean(el.textContent, 60);\n }\n\n // src/recorder/agent/selectors.ts\n var TEST_ID_ATTRS = [\n \"data-testid\",\n \"data-test\",\n \"data-test-id\",\n \"data-cy\",\n \"data-sentry-label\",\n \"data-testid-label\"\n ];\n var MAX_CANDIDATES = 5;\n var MAX_CSS_PATH_DEPTH = 6;\n function testIdSelector(el) {\n for (const attr of TEST_ID_ATTRS) {\n const v = el.getAttribute(attr);\n if (v) return `[${attr}=\"${escAttr(v)}\"]`;\n }\n return null;\n }\n function idSelector(el) {\n const id = el.getAttribute(\"id\");\n return id && isStableToken(id) ? `#${escId(id)}` : null;\n }\n function roleNameSelector(el) {\n const role = getRole(el);\n if (!role) return null;\n const name = accessibleName(el);\n if (!name) return null;\n return `role=${role}[name=\"${escAttr(name)}\"]`;\n }\n function appearedSelector(el) {\n return testIdSelector(el) ?? idSelector(el) ?? roleNameSelector(el);\n }\n function goneSelector(el) {\n return testIdSelector(el) ?? idSelector(el);\n }\n function cssSelectorFor(el) {\n return testIdSelector(el) ?? idSelector(el) ?? buildCssPath(el);\n }\n function allElements() {\n return Array.from(document.querySelectorAll(\"*\"));\n }\n function withNth(base, el, matches) {\n if (matches.length <= 1) return base;\n const idx = matches.indexOf(el);\n return idx < 0 ? base : `${base} >> nth=${idx}`;\n }\n function buildCssPath(el) {\n const segs = [];\n let cur = el;\n let depth = 0;\n while (cur && cur.nodeType === 1 && cur !== document.documentElement && depth < MAX_CSS_PATH_DEPTH) {\n const node = cur;\n if (depth > 0) {\n const anchor = testIdSelector(node) ?? idSelector(node);\n if (anchor) {\n segs.unshift(anchor);\n break;\n }\n }\n let seg = node.tagName.toLowerCase();\n const classes = Array.from(node.classList).filter(isStableClass).slice(0, 2);\n if (classes.length) seg += `.${classes.map(escId).join(\".\")}`;\n const parent = node.parentElement;\n if (parent) {\n const sameTag = Array.from(parent.children).filter((c) => c.tagName === node.tagName);\n if (sameTag.length > 1) seg += `:nth-of-type(${sameTag.indexOf(node) + 1})`;\n }\n segs.unshift(seg);\n const partial = segs.join(\" > \");\n try {\n if (document.querySelectorAll(partial).length === 1) return partial;\n } catch {\n return null;\n }\n cur = parent;\n depth++;\n }\n if (!segs.length) return null;\n const sel = segs.join(\" > \");\n try {\n return document.querySelectorAll(sel).length ? sel : null;\n } catch {\n return null;\n }\n }\n function buildCandidates(el) {\n const out = [];\n const push = (s) => {\n if (s && out.indexOf(s) === -1) out.push(s);\n };\n push(testIdSelector(el));\n push(idSelector(el));\n const nameAttr = el.getAttribute(\"name\");\n if (nameAttr && isStableToken(nameAttr)) {\n push(`${el.tagName.toLowerCase()}[name=\"${escAttr(nameAttr)}\"]`);\n }\n const roleName = roleNameSelector(el);\n if (roleName) {\n const role = getRole(el);\n const name = accessibleName(el);\n const matches = allElements().filter((c) => getRole(c) === role && accessibleName(c) === name);\n push(withNth(roleName, el, matches));\n }\n push(labelledAncestorSelector(el));\n const cssPath = buildCssPath(el);\n const text = ownText(el);\n const textSelector = text && isSmallestWithText(el, text) ? withNth(`text=\"${escAttr(text)}\"`, el, allElements().filter((c) => isSmallestWithText(c, text))) : null;\n if (isPositionalOnly(cssPath)) {\n push(textSelector);\n push(cssPath);\n } else {\n push(cssPath);\n push(textSelector);\n }\n return out.slice(0, MAX_CANDIDATES);\n }\n function isPositionalOnly(selector) {\n if (!selector) return false;\n if (!selector.includes(\":nth-of-type(\")) return false;\n return !selector.includes(\"[\") && !selector.includes(\".\");\n }\n function labelledAncestorSelector(el) {\n if (testIdSelector(el) ?? idSelector(el)) return null;\n let cur = el.parentElement;\n for (let depth = 0; cur && depth < MAX_CSS_PATH_DEPTH; depth++) {\n const anchor = testIdSelector(cur);\n if (anchor) {\n try {\n return document.querySelectorAll(anchor).length === 1 ? anchor : null;\n } catch {\n return null;\n }\n }\n cur = cur.parentElement;\n }\n return null;\n }\n function isSmallestWithText(el, text) {\n if (ownText(el) !== text) return false;\n return !Array.from(el.children).some((child) => ownText(child) === text);\n }\n function contextLabel(el) {\n const row = el.closest('tr, [role=\"row\"], li, [role=\"listitem\"], [data-testid*=\"row\"]');\n if (row && row !== el) {\n const own = renderedText(el, 60);\n const full = renderedText(row, 120);\n const t = clean(own ? full.split(own).join(\" \") : full, 60);\n if (t) return ` in the row containing \"${t}\"`;\n }\n const section = el.closest('form, section, dialog, [role=\"dialog\"], nav, main, aside');\n if (section && section !== el) {\n const label = section.getAttribute(\"aria-label\") || clean(section.querySelector(\"h1, h2, h3, h4, legend\")?.textContent) || \"\";\n if (label) return ` in the ${section.tagName.toLowerCase()} labelled \"${clean(label, 40)}\"`;\n }\n return \"\";\n }\n function semanticOf(el) {\n const role = getRole(el) || el.tagName.toLowerCase();\n const name = accessibleName(el) || ownText(el);\n const head = name ? `${name} ${role}` : role;\n return head + contextLabel(el);\n }\n function describe(el) {\n const candidates = buildCandidates(el);\n if (!candidates.length) return null;\n return { candidates, semantic: semanticOf(el) };\n }\n\n // src/recorder/agent/capture.ts\n var ECHO_WINDOW_MS = 30;\n var GESTURE_WINDOW_MS = 1e3;\n var FOCUS_SETTLE_MS = 120;\n var MEANINGFUL_KEYS = [\n \"Enter\",\n \"Escape\",\n \"Tab\",\n \"ArrowUp\",\n \"ArrowDown\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Backspace\",\n \"Delete\",\n \"Home\",\n \"End\",\n \"PageUp\",\n \"PageDown\",\n \" \"\n ];\n function targetOf(e) {\n const t = e.target;\n return t && t.nodeType === 1 ? t : null;\n }\n function playwrightKey(e) {\n const mods = [];\n if (e.ctrlKey) mods.push(\"Control\");\n if (e.altKey) mods.push(\"Alt\");\n if (e.metaKey) mods.push(\"Meta\");\n if (e.shiftKey && e.key.length > 1) mods.push(\"Shift\");\n const key = e.key === \" \" ? \"Space\" : e.key;\n return [...mods, key].join(\"+\");\n }\n function isStopHotkey(e) {\n return (e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === \"x\";\n }\n function installCapture(ctx) {\n const { config, transport, reveals } = ctx;\n const focusValue = /* @__PURE__ */ new WeakMap();\n const committed = /* @__PURE__ */ new WeakMap();\n let lastRecorded = null;\n let lastPointerDown = null;\n function resolveGestureTarget(clicked) {\n const pd = lastPointerDown;\n if (!pd || pd.el === clicked) return clicked;\n if (Date.now() - pd.t > GESTURE_WINDOW_MS) return clicked;\n const retargeted = clicked === document.documentElement || clicked === document.body;\n if (retargeted && pd.el.isConnected && describe(pd.el)) return pd.el;\n if (!describe(clicked) && pd.el.isConnected && describe(pd.el)) return pd.el;\n return clicked;\n }\n function isEchoOfRecordedClick(el) {\n const prev = lastRecorded;\n if (!prev) return false;\n if (Date.now() - prev.t > ECHO_WINDOW_MS) return false;\n if (prev.el === el) return true;\n if (prev.el.contains(el) || el.contains(prev.el)) return true;\n const label = el.closest(\"label\");\n return Boolean(label && label === prev.el.closest(\"label\"));\n }\n function emitAction(action, el, value) {\n ctx.beforeAction?.();\n const target = el ? describe(el) : null;\n if (el && !target) return false;\n const t = Date.now();\n reveals.noteAction(t);\n const ev = { kind: \"action\", action, value, target, t, author: \"human\" };\n transport.emit(ev);\n return true;\n }\n let focusTimer = null;\n let lastFocus = null;\n function noteFocusChange() {\n if (focusTimer) clearTimeout(focusTimer);\n focusTimer = setTimeout(() => {\n const el = document.activeElement;\n const selector = el ? cssSelectorFor(el) : null;\n if (!selector || selector === lastFocus) return;\n lastFocus = selector;\n transport.emit({ kind: \"focus\", selector, t: Date.now() });\n }, FOCUS_SETTLE_MS);\n }\n function flushRevealingHover(actionTarget) {\n const hovered = reveals.takeLoadBearingHover(actionTarget);\n if (hovered) emitAction(\"hover\", hovered, null);\n }\n function valueOf(el) {\n if (el.isContentEditable) return clean(el.innerText, 500);\n return el.value ?? \"\";\n }\n function commitFill(el) {\n if (!isEditable(el)) return;\n const value = valueOf(el);\n if (committed.get(el) === value) return;\n if (focusValue.get(el) === value && committed.get(el) === void 0) return;\n committed.set(el, value);\n flushRevealingHover(el);\n emitAction(\"fill\", el, value);\n }\n function flushPendingFill() {\n const active = document.activeElement;\n if (active && isEditable(active)) commitFill(active);\n }\n const on = (type, handler) => {\n document.addEventListener(type, handler, true);\n };\n on(\"focusin\", (e) => {\n const el = targetOf(e);\n if (el && isEditable(el)) focusValue.set(el, valueOf(el));\n noteFocusChange();\n });\n on(\"focusout\", () => noteFocusChange());\n on(\"mouseover\", (e) => {\n const el = targetOf(e);\n if (el) reveals.noteHover(el);\n });\n on(\"pointerdown\", (e) => {\n const el = targetOf(e);\n if (el) lastPointerDown = { el, t: Date.now() };\n });\n on(\"click\", (e) => {\n const clicked = targetOf(e);\n if (!clicked) return;\n const el = resolveGestureTarget(clicked);\n if (isEchoOfRecordedClick(el)) return;\n flushPendingFill();\n flushRevealingHover(el);\n if (emitAction(\"click\", el, null)) lastRecorded = { el, t: Date.now() };\n });\n on(\"dblclick\", (e) => {\n const el = targetOf(e);\n if (!el) return;\n flushPendingFill();\n emitAction(\"dblclick\", el, null);\n });\n on(\"change\", (e) => {\n const el = targetOf(e);\n if (!el) return;\n if (el.tagName.toLowerCase() === \"select\") {\n const sel = el;\n const opt = sel.selectedOptions[0];\n flushPendingFill();\n flushRevealingHover(el);\n emitAction(\"select\", el, opt ? opt.value || clean(opt.textContent) : sel.value);\n return;\n }\n if (isEditable(el)) commitFill(el);\n });\n on(\"blur\", (e) => {\n const el = targetOf(e);\n if (el && isEditable(el)) commitFill(el);\n });\n on(\"keydown\", (e) => {\n if (isStopHotkey(e)) {\n e.preventDefault();\n e.stopPropagation();\n transport.binding(config.stopBinding)?.({ reason: \"hotkey\" });\n return;\n }\n const el = targetOf(e);\n if (isEditable(el)) {\n if (e.key !== \"Enter\" && e.key !== \"Escape\") return;\n commitFill(el);\n } else if (!MEANINGFUL_KEYS.includes(e.key) && !(e.ctrlKey || e.metaKey || e.altKey)) {\n return;\n } else {\n flushPendingFill();\n }\n emitAction(\"press\", el, playwrightKey(e));\n });\n installScrollCapture(ctx, emitAction);\n }\n var RESIZE_PROBE = [\n \".erd_scroll_detection_container\",\n \".resize-sensor\",\n \".resize-triggers\",\n \"[data-resize-sensor]\"\n ];\n function isResizeProbe(el) {\n if (!el) return false;\n return RESIZE_PROBE.some((sel) => {\n try {\n return el.matches(sel) || el.closest(sel) !== null;\n } catch {\n return false;\n }\n });\n }\n function installScrollCapture(ctx, emitAction) {\n const { config } = ctx;\n const lastPosition = /* @__PURE__ */ new WeakMap();\n const timers = /* @__PURE__ */ new WeakMap();\n document.addEventListener(\n \"scroll\",\n (e) => {\n const raw = e.target;\n if (raw && raw.nodeType === 1 && isResizeProbe(raw)) return;\n const isDocument = !raw || raw === document || raw === document.documentElement || raw === document.body;\n const key = isDocument ? document : raw;\n const el = isDocument ? null : raw;\n const x = el ? el.scrollLeft : window.scrollX;\n const y = el ? el.scrollTop : window.scrollY;\n const existing = timers.get(key);\n if (existing) clearTimeout(existing);\n timers.set(\n key,\n setTimeout(() => {\n const last = lastPosition.get(key) ?? { x: 0, y: 0 };\n const moved = Math.abs(x - last.x) >= config.scrollMinDeltaPx || Math.abs(y - last.y) >= config.scrollMinDeltaPx;\n if (!moved) return;\n lastPosition.set(key, { x, y });\n emitAction(\"scroll\", el, JSON.stringify({ x, y }));\n }, config.scrollDebounceMs)\n );\n },\n true\n );\n }\n\n // src/noise.ts\n var TRANSIENT_NAME = /\\b(loading|spinner|progress|skeleton|please wait|submitting|saving)\\b/i;\n function isTransientIndicator(role, name) {\n if (role === \"progressbar\") return true;\n return TRANSIENT_NAME.test(name);\n }\n\n // src/recorder/agent/visibility.ts\n function isVisible(el) {\n if (!el.isConnected) return false;\n const he = el;\n if (he.hidden || el.getAttribute(\"aria-hidden\") === \"true\") return false;\n const rect = he.getBoundingClientRect();\n if (rect.width === 0 && rect.height === 0) return false;\n const cs = window.getComputedStyle(he);\n return cs.visibility !== \"hidden\" && cs.display !== \"none\" && cs.opacity !== \"0\";\n }\n\n // src/recorder/agent/dom-reaction.ts\n var INTERESTING = \"[data-testid], [data-test], [data-test-id], [data-cy], [id], [role], button, a[href]\";\n var OBSERVED_ATTRS = [\"class\", \"style\", \"hidden\", \"aria-hidden\"];\n var APPEAR_CONFIRM_MS = 600;\n function toCssSelector(selector) {\n if (selector.startsWith(\"role=\") || selector.startsWith(\"text=\")) {\n throw new Error(\"not a CSS selector\");\n }\n return selector;\n }\n function harvest(nodes, into, pick, revealed, max) {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n if (!node || node.nodeType !== 1) continue;\n const el = node;\n revealed.push(el);\n const own = pick(el);\n if (own && into.size < max) into.add(own);\n const descendants = el.querySelectorAll(INTERESTING);\n for (let j = 0; j < descendants.length && into.size < max; j++) {\n const d = descendants[j];\n if (!d) continue;\n const sel = pick(d);\n if (sel) into.add(sel);\n }\n }\n }\n function observeDomReactions(ctx) {\n const { config, transport, reveals } = ctx;\n const visibility = /* @__PURE__ */ new WeakMap();\n let pending = [];\n const stillVisible = (sel) => {\n try {\n const el = document.querySelector(toCssSelector(sel));\n return el ? isVisible(el) : false;\n } catch {\n return true;\n }\n };\n const confirm = (entry) => {\n const survived = entry.selectors.filter(stillVisible);\n if (survived.length) {\n transport.emit({ kind: \"dom\", appeared: survived, gone: [], t: entry.at });\n }\n };\n const flushAppearances = () => {\n const due = pending;\n pending = [];\n for (const entry of due) {\n clearTimeout(entry.timer);\n confirm(entry);\n }\n };\n const visibleAppearedSelector = (el) => {\n if (!isVisible(el)) return null;\n if (isTransientIndicator(getRole(el), accessibleName(el))) return null;\n return appearedSelector(el);\n };\n const start = () => {\n const observer = new MutationObserver((records) => {\n const appeared = /* @__PURE__ */ new Set();\n const gone = /* @__PURE__ */ new Set();\n const revealed = [];\n for (const rec of records) {\n if (rec.type === \"childList\") {\n harvest(rec.addedNodes, appeared, visibleAppearedSelector, revealed, config.maxSelectorsPerMutation);\n harvest(rec.removedNodes, gone, goneSelector, [], config.maxSelectorsPerMutation);\n continue;\n }\n const el = rec.target;\n const sel = appearedSelector(el);\n if (!sel) continue;\n const now = isVisible(el);\n const was = visibility.get(el);\n visibility.set(el, now);\n if (was === void 0 || was === now) continue;\n if (now) {\n appeared.add(sel);\n revealed.push(el);\n } else {\n const g = goneSelector(el);\n if (g) gone.add(g);\n }\n }\n const at = Date.now();\n reveals.noteRevealed(revealed, at);\n if (gone.size) {\n transport.emit({ kind: \"dom\", appeared: [], gone: Array.from(gone), t: at });\n }\n if (appeared.size) {\n const entry = {\n selectors: Array.from(appeared),\n at,\n timer: setTimeout(() => {\n pending = pending.filter((e) => e !== entry);\n confirm(entry);\n }, APPEAR_CONFIRM_MS)\n };\n pending.push(entry);\n }\n });\n observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: OBSERVED_ATTRS\n });\n };\n if (document.documentElement) start();\n else document.addEventListener(\"DOMContentLoaded\", start, { once: true });\n return { flushAppearances };\n }\n\n // src/recorder/agent/reveal-tracker.ts\n var ACTION_COOLDOWN_MS = 600;\n function createRevealTracker(config) {\n let pending = null;\n let lastHovered = null;\n let lastActionAt = 0;\n return {\n noteHover(el) {\n lastHovered = el;\n },\n noteAction(at) {\n lastActionAt = at;\n },\n noteRevealed(nodes, at) {\n if (!nodes.length || !lastHovered) return;\n if (at - lastActionAt <= ACTION_COOLDOWN_MS) return;\n pending = { el: lastHovered, nodes, at };\n },\n takeLoadBearingHover(actionTarget) {\n const reveal = pending;\n pending = null;\n if (!reveal || !actionTarget) return null;\n if (Date.now() - reveal.at > config.hoverRevealWindowMs) return null;\n if (!reveal.el.isConnected) return null;\n const inside = reveal.nodes.some((n) => n === actionTarget || n.contains(actionTarget));\n return inside ? reveal.el : null;\n }\n };\n }\n\n // src/recorder/agent/transport.ts\n var DRAIN_INTERVAL_MS = 25;\n function createTransport(config) {\n const globals = window;\n const pending = [];\n let drainTimer = null;\n function binding(name) {\n const fn = globals[name];\n return typeof fn === \"function\" ? fn : null;\n }\n function drain() {\n const fn = binding(config.emitBinding);\n if (!fn) return;\n while (pending.length) {\n const ev = pending.shift();\n try {\n fn(ev);\n } catch {\n }\n }\n if (drainTimer) {\n clearInterval(drainTimer);\n drainTimer = null;\n }\n }\n function emit(ev) {\n pending.push(ev);\n if (binding(config.emitBinding)) drain();\n else if (!drainTimer) drainTimer = setInterval(drain, DRAIN_INTERVAL_MS);\n }\n return { emit, binding };\n }\n\n // src/recorder/agent/page-agent.ts\n var INSTALLED_FLAG = \"__replayAgentInstalled\";\n function pageAgent(config) {\n const globals = window;\n if (globals[INSTALLED_FLAG]) return;\n if (window.top !== window) return;\n globals[INSTALLED_FLAG] = true;\n const transport = createTransport(config);\n const reveals = createRevealTracker(config);\n const dom = observeDomReactions({ config, transport, reveals });\n installCapture({ config, transport, reveals, beforeAction: () => dom.flushAppearances() });\n globals[FLUSH_GLOBAL] = () => dom.flushAppearances();\n globals[AGENT_READY_FLAG] = true;\n }\n\n // src/recorder/agent/main.ts\n window[INSTALL_GLOBAL] = (config) => {\n pageAgent(config);\n };\n})();\n";
2
+ export declare const AGENT_BUNDLE = "\"use strict\";\n(() => {\n // src/recorder/agent/config.ts\n var INSTALL_GLOBAL = \"__replayInstall\";\n var AGENT_READY_FLAG = \"__replayAgentReady\";\n var FLUSH_GLOBAL = \"__replayFlush\";\n\n // src/recorder/agent/text.ts\n function clean(s, max = 80) {\n if (!s) return \"\";\n const t = s.replace(/\\s+/g, \" \").trim();\n return t.length > max ? `${t.slice(0, max - 1)}\\u2026` : t;\n }\n function escAttr(v) {\n return v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n }\n function escId(v) {\n return typeof window.CSS?.escape === \"function\" ? window.CSS.escape(v) : v.replace(/([^\\w-])/g, \"\\\\$1\");\n }\n function isStableToken(t) {\n if (!t || t.length > 40) return false;\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t)) return false;\n if (/^(css|sc|emotion|styled|jsx|glamor|makeStyles)-/i.test(t)) return false;\n if (/[0-9a-f]{6,}/i.test(t)) return false;\n if ((t.match(/\\d/g) || []).length >= 3) return false;\n if (/[A-Z]/.test(t) && /\\d/.test(t)) return false;\n if (/^[a-z]{1,2}-[a-z0-9]{5,}$/i.test(t) && /\\d/.test(t)) return false;\n if (t.split(/[-_]/).some(looksGenerated)) return false;\n if (/-(aria|diff|live|announcer|portal)$/i.test(t)) return false;\n return true;\n }\n function looksGenerated(segment) {\n if (segment.length < 6) return false;\n if (!/^[a-z0-9]+$/i.test(segment)) return false;\n const vowels = (segment.match(/[aeiouy]/gi) || []).length;\n return /\\d/.test(segment) ? vowels <= 1 : vowels === 0;\n }\n function isStableClass(c) {\n return isStableToken(c) && !/^_/.test(c);\n }\n function renderedText(el, max = 80) {\n const inner = el.innerText;\n return clean(typeof inner === \"string\" ? inner : el.textContent, max);\n }\n\n // src/recorder/agent/roles.ts\n var TAG_ROLES = {\n button: \"button\",\n select: \"combobox\",\n textarea: \"textbox\",\n nav: \"navigation\",\n main: \"main\",\n header: \"banner\",\n footer: \"contentinfo\",\n aside: \"complementary\",\n form: \"form\",\n table: \"table\",\n tr: \"row\",\n td: \"cell\",\n th: \"columnheader\",\n ul: \"list\",\n ol: \"list\",\n li: \"listitem\",\n dialog: \"dialog\",\n option: \"option\",\n h1: \"heading\",\n h2: \"heading\",\n h3: \"heading\",\n h4: \"heading\",\n h5: \"heading\",\n h6: \"heading\"\n };\n var INPUT_ROLES = {\n button: \"button\",\n submit: \"button\",\n reset: \"button\",\n image: \"button\",\n checkbox: \"checkbox\",\n radio: \"radio\",\n range: \"slider\",\n number: \"spinbutton\",\n search: \"searchbox\",\n email: \"textbox\",\n tel: \"textbox\",\n text: \"textbox\",\n url: \"textbox\"\n };\n var NAME_FROM_CONTENT = [\n \"button\",\n \"link\",\n \"heading\",\n \"cell\",\n \"columnheader\",\n \"rowheader\",\n \"listitem\",\n \"option\",\n \"tab\",\n \"menuitem\",\n \"menuitemcheckbox\",\n \"menuitemradio\",\n \"treeitem\",\n \"switch\",\n \"checkbox\",\n \"radio\"\n ];\n var NON_EDITABLE_INPUTS = [\"checkbox\", \"radio\", \"button\", \"submit\", \"reset\", \"file\", \"image\"];\n function getRole(el) {\n const explicit = el.getAttribute(\"role\");\n if (explicit) return explicit.trim().split(/\\s+/)[0] || null;\n const tag = el.tagName.toLowerCase();\n if (tag === \"a\") return el.hasAttribute(\"href\") ? \"link\" : null;\n if (tag === \"img\") return el.getAttribute(\"alt\") === \"\" ? null : \"img\";\n if (tag === \"input\") {\n const type = (el.getAttribute(\"type\") || \"text\").toLowerCase();\n return INPUT_ROLES[type] ?? null;\n }\n return TAG_ROLES[tag] ?? null;\n }\n function isEditable(el) {\n if (!el || el.nodeType !== 1) return false;\n const tag = el.tagName.toLowerCase();\n if (tag === \"textarea\") return true;\n if (el.isContentEditable) return true;\n if (tag !== \"input\") return false;\n const type = (el.getAttribute(\"type\") || \"text\").toLowerCase();\n return !NON_EDITABLE_INPUTS.includes(type);\n }\n function accessibleName(el) {\n const aria = el.getAttribute(\"aria-label\");\n if (aria && aria.trim()) return clean(aria);\n const labelledby = el.getAttribute(\"aria-labelledby\");\n if (labelledby) {\n const parts = labelledby.split(/\\s+/).map((id) => document.getElementById(id)).filter((n) => !!n).map((n) => clean(n.textContent));\n const joined = clean(parts.filter(Boolean).join(\" \"));\n if (joined) return joined;\n }\n const tag = el.tagName.toLowerCase();\n if ([\"input\", \"select\", \"textarea\"].includes(tag)) {\n const id = el.getAttribute(\"id\");\n if (id) {\n const forLabel = document.querySelector(`label[for=\"${escAttr(id)}\"]`);\n if (forLabel) {\n const t = clean(forLabel.textContent);\n if (t) return t;\n }\n }\n const wrapping = el.closest(\"label\");\n if (wrapping) {\n const t = clean(wrapping.textContent);\n if (t) return t;\n }\n }\n for (const attr of [\"alt\", \"title\", \"placeholder\"]) {\n const v = el.getAttribute(attr);\n if (v && v.trim()) return clean(v);\n }\n const role = getRole(el);\n if (role && NAME_FROM_CONTENT.includes(role)) {\n const t = clean(el.textContent);\n if (t) return t;\n }\n return \"\";\n }\n function ownText(el) {\n return clean(el.textContent, 60);\n }\n\n // src/recorder/agent/selectors.ts\n var TEST_ID_ATTRS = [\n \"data-testid\",\n \"data-test\",\n \"data-test-id\",\n \"data-cy\",\n \"data-sentry-label\",\n \"data-testid-label\"\n ];\n var MAX_CANDIDATES = 6;\n var INTERACTIVE = 'button, a[href], [role=\"button\"], [role=\"link\"], [role=\"menuitem\"], [role=\"menuitemradio\"], [role=\"option\"], [role=\"tab\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"], [tabindex], [data-focusable=\"true\"], [onclick]';\n var MAX_CSS_PATH_DEPTH = 6;\n function testIdSelector(el) {\n for (const attr of TEST_ID_ATTRS) {\n const v = el.getAttribute(attr);\n if (v) return `[${attr}=\"${escAttr(v)}\"]`;\n }\n return null;\n }\n function idSelector(el) {\n const id = el.getAttribute(\"id\");\n return id && isStableToken(id) ? `#${escId(id)}` : null;\n }\n function roleNameSelector(el) {\n const role = getRole(el);\n if (!role) return null;\n const name = accessibleName(el);\n if (!name) return null;\n return `role=${role}[name=\"${escAttr(name)}\"]`;\n }\n function appearedSelector(el) {\n return testIdSelector(el) ?? idSelector(el) ?? roleNameSelector(el);\n }\n function goneSelector(el) {\n return testIdSelector(el) ?? idSelector(el);\n }\n function cssSelectorFor(el) {\n return testIdSelector(el) ?? idSelector(el) ?? buildCssPath(el);\n }\n function allElements() {\n return Array.from(document.querySelectorAll(\"*\"));\n }\n function withNth(base, el, matches) {\n if (matches.length <= 1) return base;\n const idx = matches.indexOf(el);\n return idx < 0 ? base : `${base} >> nth=${idx}`;\n }\n function buildCssPath(el) {\n const segs = [];\n let cur = el;\n let depth = 0;\n while (cur && cur.nodeType === 1 && cur !== document.documentElement && depth < MAX_CSS_PATH_DEPTH) {\n const node = cur;\n if (depth > 0) {\n const anchor = testIdSelector(node) ?? idSelector(node);\n if (anchor) {\n segs.unshift(anchor);\n break;\n }\n }\n let seg = node.tagName.toLowerCase();\n const classes = Array.from(node.classList).filter(isStableClass).slice(0, 2);\n if (classes.length) seg += `.${classes.map(escId).join(\".\")}`;\n const parent = node.parentElement;\n if (parent) {\n const sameTag = Array.from(parent.children).filter((c) => c.tagName === node.tagName);\n if (sameTag.length > 1) seg += `:nth-of-type(${sameTag.indexOf(node) + 1})`;\n }\n segs.unshift(seg);\n const partial = segs.join(\" > \");\n try {\n if (document.querySelectorAll(partial).length === 1) return partial;\n } catch {\n return null;\n }\n cur = parent;\n depth++;\n }\n if (!segs.length) return null;\n const sel = segs.join(\" > \");\n try {\n return document.querySelectorAll(sel).length ? sel : null;\n } catch {\n return null;\n }\n }\n function buildCandidates(el) {\n const out = [];\n const push = (s) => {\n if (s && out.indexOf(s) === -1) out.push(s);\n };\n push(testIdSelector(el));\n push(idSelector(el));\n const nameAttr = el.getAttribute(\"name\");\n if (nameAttr && isStableToken(nameAttr)) {\n push(`${el.tagName.toLowerCase()}[name=\"${escAttr(nameAttr)}\"]`);\n }\n const roleName = roleNameSelector(el);\n if (roleName) {\n const role = getRole(el);\n const name = accessibleName(el);\n const matches = allElements().filter((c) => getRole(c) === role && accessibleName(c) === name);\n push(withNth(roleName, el, matches));\n }\n for (const selector of interactiveHostSelectors(el)) push(selector);\n push(labelledAncestorSelector(el));\n const cssPath = buildCssPath(el);\n const text = ownText(el);\n const textSelector = text && isSmallestWithText(el, text) ? withNth(`text=\"${escAttr(text)}\"`, el, allElements().filter((c) => isSmallestWithText(c, text))) : null;\n if (isPositionalOnly(cssPath)) {\n push(textSelector);\n push(cssPath);\n } else {\n push(cssPath);\n push(textSelector);\n }\n return out.slice(0, MAX_CANDIDATES);\n }\n function interactiveHostSelectors(el) {\n let host = null;\n try {\n const found = el.closest(INTERACTIVE);\n host = found && found !== el ? found : null;\n } catch {\n host = null;\n }\n if (!host) return [];\n const out = [];\n const push = (s) => {\n if (s && out.indexOf(s) === -1) out.push(s);\n };\n push(testIdSelector(host));\n push(idSelector(host));\n push(roleNameSelector(host));\n const label = renderedText(host, 60);\n if (label) {\n const tag = host.tagName.toLowerCase();\n const role = host.getAttribute(\"role\");\n const qualifier = host.getAttribute(\"data-focusable\") ? '[data-focusable=\"true\"]' : role ? `[role=\"${escAttr(role)}\"]` : host.hasAttribute(\"tabindex\") ? `[tabindex=\"${escAttr(host.getAttribute(\"tabindex\") ?? \"0\")}\"]` : \"\";\n if (qualifier) push(`${tag}${qualifier}:has-text(\"${escAttr(label)}\")`);\n }\n return out;\n }\n function isPositionalOnly(selector) {\n if (!selector) return false;\n if (!selector.includes(\":nth-of-type(\")) return false;\n return !selector.includes(\"[\") && !selector.includes(\".\");\n }\n function labelledAncestorSelector(el) {\n if (testIdSelector(el) ?? idSelector(el)) return null;\n let cur = el.parentElement;\n for (let depth = 0; cur && depth < MAX_CSS_PATH_DEPTH; depth++) {\n const anchor = testIdSelector(cur);\n if (anchor) {\n try {\n return document.querySelectorAll(anchor).length === 1 ? anchor : null;\n } catch {\n return null;\n }\n }\n cur = cur.parentElement;\n }\n return null;\n }\n function isSmallestWithText(el, text) {\n if (ownText(el) !== text) return false;\n return !Array.from(el.children).some((child) => ownText(child) === text);\n }\n function contextLabel(el) {\n const row = el.closest('tr, [role=\"row\"], li, [role=\"listitem\"], [data-testid*=\"row\"]');\n if (row && row !== el) {\n const own = renderedText(el, 60);\n const full = renderedText(row, 120);\n const t = clean(own ? full.split(own).join(\" \") : full, 60);\n if (t) return ` in the row containing \"${t}\"`;\n }\n const section = el.closest('form, section, dialog, [role=\"dialog\"], nav, main, aside');\n if (section && section !== el) {\n const label = section.getAttribute(\"aria-label\") || clean(section.querySelector(\"h1, h2, h3, h4, legend\")?.textContent) || \"\";\n if (label) return ` in the ${section.tagName.toLowerCase()} labelled \"${clean(label, 40)}\"`;\n }\n return \"\";\n }\n function semanticOf(el) {\n const role = getRole(el) || el.tagName.toLowerCase();\n const name = accessibleName(el) || ownText(el);\n const head = name ? `${name} ${role}` : role;\n return head + contextLabel(el);\n }\n function describe(el) {\n const candidates = buildCandidates(el);\n if (!candidates.length) return null;\n return { candidates, semantic: semanticOf(el) };\n }\n\n // src/recorder/agent/capture.ts\n var ECHO_WINDOW_MS = 30;\n var GESTURE_WINDOW_MS = 1e3;\n var FOCUS_SETTLE_MS = 120;\n var MEANINGFUL_KEYS = [\n \"Enter\",\n \"Escape\",\n \"Tab\",\n \"ArrowUp\",\n \"ArrowDown\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Backspace\",\n \"Delete\",\n \"Home\",\n \"End\",\n \"PageUp\",\n \"PageDown\",\n \" \"\n ];\n function targetOf(e) {\n const t = e.target;\n return t && t.nodeType === 1 ? t : null;\n }\n function playwrightKey(e) {\n const mods = [];\n if (e.ctrlKey) mods.push(\"Control\");\n if (e.altKey) mods.push(\"Alt\");\n if (e.metaKey) mods.push(\"Meta\");\n if (e.shiftKey && e.key.length > 1) mods.push(\"Shift\");\n const key = e.key === \" \" ? \"Space\" : e.key;\n return [...mods, key].join(\"+\");\n }\n function isStopHotkey(e) {\n return (e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === \"x\";\n }\n function installCapture(ctx) {\n const { config, transport, reveals } = ctx;\n const focusValue = /* @__PURE__ */ new WeakMap();\n const committed = /* @__PURE__ */ new WeakMap();\n let lastRecorded = null;\n let lastPointerDown = null;\n function resolveGestureTarget(clicked) {\n const pd = lastPointerDown;\n if (!pd || pd.el === clicked) return clicked;\n if (Date.now() - pd.t > GESTURE_WINDOW_MS) return clicked;\n const retargeted = clicked === document.documentElement || clicked === document.body;\n if (retargeted && pd.el.isConnected && describe(pd.el)) return pd.el;\n if (!describe(clicked) && pd.el.isConnected && describe(pd.el)) return pd.el;\n return clicked;\n }\n function isEchoOfRecordedClick(el) {\n const prev = lastRecorded;\n if (!prev) return false;\n if (Date.now() - prev.t > ECHO_WINDOW_MS) return false;\n if (prev.el === el) return true;\n if (prev.el.contains(el) || el.contains(prev.el)) return true;\n const label = el.closest(\"label\");\n return Boolean(label && label === prev.el.closest(\"label\"));\n }\n function emitAction(action, el, value) {\n ctx.beforeAction?.();\n const target = el ? describe(el) : null;\n if (el && !target) return false;\n const t = Date.now();\n reveals.noteAction(t);\n const ev = { kind: \"action\", action, value, target, t, author: \"human\" };\n transport.emit(ev);\n return true;\n }\n let focusTimer = null;\n let lastFocus = null;\n function noteFocusChange() {\n if (focusTimer) clearTimeout(focusTimer);\n focusTimer = setTimeout(() => {\n const el = document.activeElement;\n const selector = el ? cssSelectorFor(el) : null;\n if (!selector || selector === lastFocus) return;\n lastFocus = selector;\n transport.emit({ kind: \"focus\", selector, t: Date.now() });\n }, FOCUS_SETTLE_MS);\n }\n function flushRevealingHover(actionTarget) {\n const hovered = reveals.takeLoadBearingHover(actionTarget);\n if (hovered) emitAction(\"hover\", hovered, null);\n }\n function valueOf(el) {\n if (el.isContentEditable) return clean(el.innerText, 500);\n return el.value ?? \"\";\n }\n function commitFill(el) {\n if (!isEditable(el)) return;\n const value = valueOf(el);\n if (committed.get(el) === value) return;\n if (focusValue.get(el) === value && committed.get(el) === void 0) return;\n committed.set(el, value);\n flushRevealingHover(el);\n emitAction(\"fill\", el, value);\n }\n function flushPendingFill() {\n const active = document.activeElement;\n if (active && isEditable(active)) commitFill(active);\n }\n const on = (type, handler) => {\n document.addEventListener(type, handler, true);\n };\n on(\"focusin\", (e) => {\n const el = targetOf(e);\n if (el && isEditable(el)) focusValue.set(el, valueOf(el));\n noteFocusChange();\n });\n on(\"focusout\", () => noteFocusChange());\n on(\"mouseover\", (e) => {\n const el = targetOf(e);\n if (el) reveals.noteHover(el);\n });\n on(\"pointerdown\", (e) => {\n const el = targetOf(e);\n if (el) lastPointerDown = { el, t: Date.now() };\n });\n on(\"click\", (e) => {\n const clicked = targetOf(e);\n if (!clicked) return;\n const el = resolveGestureTarget(clicked);\n if (isEchoOfRecordedClick(el)) return;\n flushPendingFill();\n flushRevealingHover(el);\n if (emitAction(\"click\", el, null)) lastRecorded = { el, t: Date.now() };\n });\n on(\"dblclick\", (e) => {\n const el = targetOf(e);\n if (!el) return;\n flushPendingFill();\n emitAction(\"dblclick\", el, null);\n });\n on(\"change\", (e) => {\n const el = targetOf(e);\n if (!el) return;\n if (el.tagName.toLowerCase() === \"select\") {\n const sel = el;\n const opt = sel.selectedOptions[0];\n flushPendingFill();\n flushRevealingHover(el);\n emitAction(\"select\", el, opt ? opt.value || clean(opt.textContent) : sel.value);\n return;\n }\n if (isEditable(el)) commitFill(el);\n });\n on(\"blur\", (e) => {\n const el = targetOf(e);\n if (el && isEditable(el)) commitFill(el);\n });\n on(\"keydown\", (e) => {\n if (isStopHotkey(e)) {\n e.preventDefault();\n e.stopPropagation();\n transport.binding(config.stopBinding)?.({ reason: \"hotkey\" });\n return;\n }\n const el = targetOf(e);\n if (isEditable(el)) {\n if (e.key !== \"Enter\" && e.key !== \"Escape\") return;\n commitFill(el);\n } else if (!MEANINGFUL_KEYS.includes(e.key) && !(e.ctrlKey || e.metaKey || e.altKey)) {\n return;\n } else {\n flushPendingFill();\n }\n emitAction(\"press\", el, playwrightKey(e));\n });\n installScrollCapture(ctx, emitAction);\n }\n var RESIZE_PROBE = [\n \".erd_scroll_detection_container\",\n \".resize-sensor\",\n \".resize-triggers\",\n \"[data-resize-sensor]\"\n ];\n function isResizeProbe(el) {\n if (!el) return false;\n return RESIZE_PROBE.some((sel) => {\n try {\n return el.matches(sel) || el.closest(sel) !== null;\n } catch {\n return false;\n }\n });\n }\n function installScrollCapture(ctx, emitAction) {\n const { config } = ctx;\n const lastPosition = /* @__PURE__ */ new WeakMap();\n const timers = /* @__PURE__ */ new WeakMap();\n document.addEventListener(\n \"scroll\",\n (e) => {\n const raw = e.target;\n if (raw && raw.nodeType === 1 && isResizeProbe(raw)) return;\n const isDocument = !raw || raw === document || raw === document.documentElement || raw === document.body;\n const key = isDocument ? document : raw;\n const el = isDocument ? null : raw;\n const x = el ? el.scrollLeft : window.scrollX;\n const y = el ? el.scrollTop : window.scrollY;\n const existing = timers.get(key);\n if (existing) clearTimeout(existing);\n timers.set(\n key,\n setTimeout(() => {\n const last = lastPosition.get(key) ?? { x: 0, y: 0 };\n const moved = Math.abs(x - last.x) >= config.scrollMinDeltaPx || Math.abs(y - last.y) >= config.scrollMinDeltaPx;\n if (!moved) return;\n lastPosition.set(key, { x, y });\n emitAction(\"scroll\", el, JSON.stringify({ x, y }));\n }, config.scrollDebounceMs)\n );\n },\n true\n );\n }\n\n // src/noise.ts\n var TRANSIENT_NAME = /\\b(loading|spinner|progress|skeleton|please wait|submitting|saving)\\b/i;\n function isTransientIndicator(role, name) {\n if (role === \"progressbar\") return true;\n return TRANSIENT_NAME.test(name);\n }\n\n // src/recorder/agent/visibility.ts\n function isVisible(el) {\n if (!el.isConnected) return false;\n const he = el;\n if (he.hidden || el.getAttribute(\"aria-hidden\") === \"true\") return false;\n const rect = he.getBoundingClientRect();\n if (rect.width === 0 && rect.height === 0) return false;\n const cs = window.getComputedStyle(he);\n return cs.visibility !== \"hidden\" && cs.display !== \"none\" && cs.opacity !== \"0\";\n }\n\n // src/recorder/agent/dom-reaction.ts\n var INTERESTING = \"[data-testid], [data-test], [data-test-id], [data-cy], [id], [role], button, a[href]\";\n var OBSERVED_ATTRS = [\"class\", \"style\", \"hidden\", \"aria-hidden\"];\n var APPEAR_CONFIRM_MS = 600;\n function toCssSelector(selector) {\n if (selector.startsWith(\"role=\") || selector.startsWith(\"text=\")) {\n throw new Error(\"not a CSS selector\");\n }\n return selector;\n }\n function harvest(nodes, into, pick, revealed, max) {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n if (!node || node.nodeType !== 1) continue;\n const el = node;\n revealed.push(el);\n const own = pick(el);\n if (own && into.size < max) into.add(own);\n const descendants = el.querySelectorAll(INTERESTING);\n for (let j = 0; j < descendants.length && into.size < max; j++) {\n const d = descendants[j];\n if (!d) continue;\n const sel = pick(d);\n if (sel) into.add(sel);\n }\n }\n }\n function observeDomReactions(ctx) {\n const { config, transport, reveals } = ctx;\n const visibility = /* @__PURE__ */ new WeakMap();\n let pending = [];\n const stillVisible = (sel) => {\n try {\n const el = document.querySelector(toCssSelector(sel));\n return el ? isVisible(el) : false;\n } catch {\n return true;\n }\n };\n const confirm = (entry) => {\n const survived = entry.selectors.filter(stillVisible);\n if (survived.length) {\n transport.emit({ kind: \"dom\", appeared: survived, gone: [], t: entry.at });\n }\n };\n const flushAppearances = () => {\n const due = pending;\n pending = [];\n for (const entry of due) {\n clearTimeout(entry.timer);\n confirm(entry);\n }\n };\n const visibleAppearedSelector = (el) => {\n if (!isVisible(el)) return null;\n if (isTransientIndicator(getRole(el), accessibleName(el))) return null;\n return appearedSelector(el);\n };\n const start = () => {\n const observer = new MutationObserver((records) => {\n const appeared = /* @__PURE__ */ new Set();\n const gone = /* @__PURE__ */ new Set();\n const revealed = [];\n for (const rec of records) {\n if (rec.type === \"childList\") {\n harvest(rec.addedNodes, appeared, visibleAppearedSelector, revealed, config.maxSelectorsPerMutation);\n harvest(rec.removedNodes, gone, goneSelector, [], config.maxSelectorsPerMutation);\n continue;\n }\n const el = rec.target;\n const sel = appearedSelector(el);\n if (!sel) continue;\n const now = isVisible(el);\n const was = visibility.get(el);\n visibility.set(el, now);\n if (was === void 0 || was === now) continue;\n if (now) {\n appeared.add(sel);\n revealed.push(el);\n } else {\n const g = goneSelector(el);\n if (g) gone.add(g);\n }\n }\n const at = Date.now();\n reveals.noteRevealed(revealed, at);\n if (gone.size) {\n transport.emit({ kind: \"dom\", appeared: [], gone: Array.from(gone), t: at });\n }\n if (appeared.size) {\n const entry = {\n selectors: Array.from(appeared),\n at,\n timer: setTimeout(() => {\n pending = pending.filter((e) => e !== entry);\n confirm(entry);\n }, APPEAR_CONFIRM_MS)\n };\n pending.push(entry);\n }\n });\n observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: OBSERVED_ATTRS\n });\n };\n if (document.documentElement) start();\n else document.addEventListener(\"DOMContentLoaded\", start, { once: true });\n return { flushAppearances };\n }\n\n // src/recorder/agent/reveal-tracker.ts\n var ACTION_COOLDOWN_MS = 600;\n function createRevealTracker(config) {\n let pending = null;\n let lastHovered = null;\n let lastActionAt = 0;\n return {\n noteHover(el) {\n lastHovered = el;\n },\n noteAction(at) {\n lastActionAt = at;\n },\n noteRevealed(nodes, at) {\n if (!nodes.length || !lastHovered) return;\n if (at - lastActionAt <= ACTION_COOLDOWN_MS) return;\n pending = { el: lastHovered, nodes, at };\n },\n takeLoadBearingHover(actionTarget) {\n const reveal = pending;\n pending = null;\n if (!reveal || !actionTarget) return null;\n if (Date.now() - reveal.at > config.hoverRevealWindowMs) return null;\n if (!reveal.el.isConnected) return null;\n const inside = reveal.nodes.some((n) => n === actionTarget || n.contains(actionTarget));\n return inside ? reveal.el : null;\n }\n };\n }\n\n // src/recorder/agent/transport.ts\n var DRAIN_INTERVAL_MS = 25;\n function createTransport(config) {\n const globals = window;\n const pending = [];\n let drainTimer = null;\n function binding(name) {\n const fn = globals[name];\n return typeof fn === \"function\" ? fn : null;\n }\n function drain() {\n const fn = binding(config.emitBinding);\n if (!fn) return;\n while (pending.length) {\n const ev = pending.shift();\n try {\n fn(ev);\n } catch {\n }\n }\n if (drainTimer) {\n clearInterval(drainTimer);\n drainTimer = null;\n }\n }\n function emit(ev) {\n pending.push(ev);\n if (binding(config.emitBinding)) drain();\n else if (!drainTimer) drainTimer = setInterval(drain, DRAIN_INTERVAL_MS);\n }\n return { emit, binding };\n }\n\n // src/recorder/agent/page-agent.ts\n var INSTALLED_FLAG = \"__replayAgentInstalled\";\n function pageAgent(config) {\n const globals = window;\n if (globals[INSTALLED_FLAG]) return;\n if (window.top !== window) return;\n globals[INSTALLED_FLAG] = true;\n const transport = createTransport(config);\n const reveals = createRevealTracker(config);\n const dom = observeDomReactions({ config, transport, reveals });\n installCapture({ config, transport, reveals, beforeAction: () => dom.flushAppearances() });\n globals[FLUSH_GLOBAL] = () => dom.flushAppearances();\n globals[AGENT_READY_FLAG] = true;\n }\n\n // src/recorder/agent/main.ts\n window[INSTALL_GLOBAL] = (config) => {\n pageAgent(config);\n };\n})();\n";
@@ -2,5 +2,5 @@
2
2
  // Source: src/recorder/agent/main.ts. Regenerate with `npm run build:agent`.
3
3
  /* eslint-disable */
4
4
  /** The in-page capture agent, bundled as a standalone IIFE. */
5
- export const AGENT_BUNDLE = "\"use strict\";\n(() => {\n // src/recorder/agent/config.ts\n var INSTALL_GLOBAL = \"__replayInstall\";\n var AGENT_READY_FLAG = \"__replayAgentReady\";\n var FLUSH_GLOBAL = \"__replayFlush\";\n\n // src/recorder/agent/text.ts\n function clean(s, max = 80) {\n if (!s) return \"\";\n const t = s.replace(/\\s+/g, \" \").trim();\n return t.length > max ? `${t.slice(0, max - 1)}\\u2026` : t;\n }\n function escAttr(v) {\n return v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n }\n function escId(v) {\n return typeof window.CSS?.escape === \"function\" ? window.CSS.escape(v) : v.replace(/([^\\w-])/g, \"\\\\$1\");\n }\n function isStableToken(t) {\n if (!t || t.length > 40) return false;\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t)) return false;\n if (/^(css|sc|emotion|styled|jsx|glamor|makeStyles)-/i.test(t)) return false;\n if (/[0-9a-f]{6,}/i.test(t)) return false;\n if ((t.match(/\\d/g) || []).length >= 3) return false;\n if (/[A-Z]/.test(t) && /\\d/.test(t)) return false;\n if (/^[a-z]{1,2}-[a-z0-9]{5,}$/i.test(t) && /\\d/.test(t)) return false;\n if (t.split(/[-_]/).some(looksGenerated)) return false;\n if (/-(aria|diff|live|announcer|portal)$/i.test(t)) return false;\n return true;\n }\n function looksGenerated(segment) {\n if (segment.length < 6) return false;\n if (!/^[a-z0-9]+$/i.test(segment)) return false;\n const vowels = (segment.match(/[aeiouy]/gi) || []).length;\n return /\\d/.test(segment) ? vowels <= 1 : vowels === 0;\n }\n function isStableClass(c) {\n return isStableToken(c) && !/^_/.test(c);\n }\n function renderedText(el, max = 80) {\n const inner = el.innerText;\n return clean(typeof inner === \"string\" ? inner : el.textContent, max);\n }\n\n // src/recorder/agent/roles.ts\n var TAG_ROLES = {\n button: \"button\",\n select: \"combobox\",\n textarea: \"textbox\",\n nav: \"navigation\",\n main: \"main\",\n header: \"banner\",\n footer: \"contentinfo\",\n aside: \"complementary\",\n form: \"form\",\n table: \"table\",\n tr: \"row\",\n td: \"cell\",\n th: \"columnheader\",\n ul: \"list\",\n ol: \"list\",\n li: \"listitem\",\n dialog: \"dialog\",\n option: \"option\",\n h1: \"heading\",\n h2: \"heading\",\n h3: \"heading\",\n h4: \"heading\",\n h5: \"heading\",\n h6: \"heading\"\n };\n var INPUT_ROLES = {\n button: \"button\",\n submit: \"button\",\n reset: \"button\",\n image: \"button\",\n checkbox: \"checkbox\",\n radio: \"radio\",\n range: \"slider\",\n number: \"spinbutton\",\n search: \"searchbox\",\n email: \"textbox\",\n tel: \"textbox\",\n text: \"textbox\",\n url: \"textbox\"\n };\n var NAME_FROM_CONTENT = [\n \"button\",\n \"link\",\n \"heading\",\n \"cell\",\n \"columnheader\",\n \"rowheader\",\n \"listitem\",\n \"option\",\n \"tab\",\n \"menuitem\",\n \"menuitemcheckbox\",\n \"menuitemradio\",\n \"treeitem\",\n \"switch\",\n \"checkbox\",\n \"radio\"\n ];\n var NON_EDITABLE_INPUTS = [\"checkbox\", \"radio\", \"button\", \"submit\", \"reset\", \"file\", \"image\"];\n function getRole(el) {\n const explicit = el.getAttribute(\"role\");\n if (explicit) return explicit.trim().split(/\\s+/)[0] || null;\n const tag = el.tagName.toLowerCase();\n if (tag === \"a\") return el.hasAttribute(\"href\") ? \"link\" : null;\n if (tag === \"img\") return el.getAttribute(\"alt\") === \"\" ? null : \"img\";\n if (tag === \"input\") {\n const type = (el.getAttribute(\"type\") || \"text\").toLowerCase();\n return INPUT_ROLES[type] ?? null;\n }\n return TAG_ROLES[tag] ?? null;\n }\n function isEditable(el) {\n if (!el || el.nodeType !== 1) return false;\n const tag = el.tagName.toLowerCase();\n if (tag === \"textarea\") return true;\n if (el.isContentEditable) return true;\n if (tag !== \"input\") return false;\n const type = (el.getAttribute(\"type\") || \"text\").toLowerCase();\n return !NON_EDITABLE_INPUTS.includes(type);\n }\n function accessibleName(el) {\n const aria = el.getAttribute(\"aria-label\");\n if (aria && aria.trim()) return clean(aria);\n const labelledby = el.getAttribute(\"aria-labelledby\");\n if (labelledby) {\n const parts = labelledby.split(/\\s+/).map((id) => document.getElementById(id)).filter((n) => !!n).map((n) => clean(n.textContent));\n const joined = clean(parts.filter(Boolean).join(\" \"));\n if (joined) return joined;\n }\n const tag = el.tagName.toLowerCase();\n if ([\"input\", \"select\", \"textarea\"].includes(tag)) {\n const id = el.getAttribute(\"id\");\n if (id) {\n const forLabel = document.querySelector(`label[for=\"${escAttr(id)}\"]`);\n if (forLabel) {\n const t = clean(forLabel.textContent);\n if (t) return t;\n }\n }\n const wrapping = el.closest(\"label\");\n if (wrapping) {\n const t = clean(wrapping.textContent);\n if (t) return t;\n }\n }\n for (const attr of [\"alt\", \"title\", \"placeholder\"]) {\n const v = el.getAttribute(attr);\n if (v && v.trim()) return clean(v);\n }\n const role = getRole(el);\n if (role && NAME_FROM_CONTENT.includes(role)) {\n const t = clean(el.textContent);\n if (t) return t;\n }\n return \"\";\n }\n function ownText(el) {\n return clean(el.textContent, 60);\n }\n\n // src/recorder/agent/selectors.ts\n var TEST_ID_ATTRS = [\n \"data-testid\",\n \"data-test\",\n \"data-test-id\",\n \"data-cy\",\n \"data-sentry-label\",\n \"data-testid-label\"\n ];\n var MAX_CANDIDATES = 5;\n var MAX_CSS_PATH_DEPTH = 6;\n function testIdSelector(el) {\n for (const attr of TEST_ID_ATTRS) {\n const v = el.getAttribute(attr);\n if (v) return `[${attr}=\"${escAttr(v)}\"]`;\n }\n return null;\n }\n function idSelector(el) {\n const id = el.getAttribute(\"id\");\n return id && isStableToken(id) ? `#${escId(id)}` : null;\n }\n function roleNameSelector(el) {\n const role = getRole(el);\n if (!role) return null;\n const name = accessibleName(el);\n if (!name) return null;\n return `role=${role}[name=\"${escAttr(name)}\"]`;\n }\n function appearedSelector(el) {\n return testIdSelector(el) ?? idSelector(el) ?? roleNameSelector(el);\n }\n function goneSelector(el) {\n return testIdSelector(el) ?? idSelector(el);\n }\n function cssSelectorFor(el) {\n return testIdSelector(el) ?? idSelector(el) ?? buildCssPath(el);\n }\n function allElements() {\n return Array.from(document.querySelectorAll(\"*\"));\n }\n function withNth(base, el, matches) {\n if (matches.length <= 1) return base;\n const idx = matches.indexOf(el);\n return idx < 0 ? base : `${base} >> nth=${idx}`;\n }\n function buildCssPath(el) {\n const segs = [];\n let cur = el;\n let depth = 0;\n while (cur && cur.nodeType === 1 && cur !== document.documentElement && depth < MAX_CSS_PATH_DEPTH) {\n const node = cur;\n if (depth > 0) {\n const anchor = testIdSelector(node) ?? idSelector(node);\n if (anchor) {\n segs.unshift(anchor);\n break;\n }\n }\n let seg = node.tagName.toLowerCase();\n const classes = Array.from(node.classList).filter(isStableClass).slice(0, 2);\n if (classes.length) seg += `.${classes.map(escId).join(\".\")}`;\n const parent = node.parentElement;\n if (parent) {\n const sameTag = Array.from(parent.children).filter((c) => c.tagName === node.tagName);\n if (sameTag.length > 1) seg += `:nth-of-type(${sameTag.indexOf(node) + 1})`;\n }\n segs.unshift(seg);\n const partial = segs.join(\" > \");\n try {\n if (document.querySelectorAll(partial).length === 1) return partial;\n } catch {\n return null;\n }\n cur = parent;\n depth++;\n }\n if (!segs.length) return null;\n const sel = segs.join(\" > \");\n try {\n return document.querySelectorAll(sel).length ? sel : null;\n } catch {\n return null;\n }\n }\n function buildCandidates(el) {\n const out = [];\n const push = (s) => {\n if (s && out.indexOf(s) === -1) out.push(s);\n };\n push(testIdSelector(el));\n push(idSelector(el));\n const nameAttr = el.getAttribute(\"name\");\n if (nameAttr && isStableToken(nameAttr)) {\n push(`${el.tagName.toLowerCase()}[name=\"${escAttr(nameAttr)}\"]`);\n }\n const roleName = roleNameSelector(el);\n if (roleName) {\n const role = getRole(el);\n const name = accessibleName(el);\n const matches = allElements().filter((c) => getRole(c) === role && accessibleName(c) === name);\n push(withNth(roleName, el, matches));\n }\n push(labelledAncestorSelector(el));\n const cssPath = buildCssPath(el);\n const text = ownText(el);\n const textSelector = text && isSmallestWithText(el, text) ? withNth(`text=\"${escAttr(text)}\"`, el, allElements().filter((c) => isSmallestWithText(c, text))) : null;\n if (isPositionalOnly(cssPath)) {\n push(textSelector);\n push(cssPath);\n } else {\n push(cssPath);\n push(textSelector);\n }\n return out.slice(0, MAX_CANDIDATES);\n }\n function isPositionalOnly(selector) {\n if (!selector) return false;\n if (!selector.includes(\":nth-of-type(\")) return false;\n return !selector.includes(\"[\") && !selector.includes(\".\");\n }\n function labelledAncestorSelector(el) {\n if (testIdSelector(el) ?? idSelector(el)) return null;\n let cur = el.parentElement;\n for (let depth = 0; cur && depth < MAX_CSS_PATH_DEPTH; depth++) {\n const anchor = testIdSelector(cur);\n if (anchor) {\n try {\n return document.querySelectorAll(anchor).length === 1 ? anchor : null;\n } catch {\n return null;\n }\n }\n cur = cur.parentElement;\n }\n return null;\n }\n function isSmallestWithText(el, text) {\n if (ownText(el) !== text) return false;\n return !Array.from(el.children).some((child) => ownText(child) === text);\n }\n function contextLabel(el) {\n const row = el.closest('tr, [role=\"row\"], li, [role=\"listitem\"], [data-testid*=\"row\"]');\n if (row && row !== el) {\n const own = renderedText(el, 60);\n const full = renderedText(row, 120);\n const t = clean(own ? full.split(own).join(\" \") : full, 60);\n if (t) return ` in the row containing \"${t}\"`;\n }\n const section = el.closest('form, section, dialog, [role=\"dialog\"], nav, main, aside');\n if (section && section !== el) {\n const label = section.getAttribute(\"aria-label\") || clean(section.querySelector(\"h1, h2, h3, h4, legend\")?.textContent) || \"\";\n if (label) return ` in the ${section.tagName.toLowerCase()} labelled \"${clean(label, 40)}\"`;\n }\n return \"\";\n }\n function semanticOf(el) {\n const role = getRole(el) || el.tagName.toLowerCase();\n const name = accessibleName(el) || ownText(el);\n const head = name ? `${name} ${role}` : role;\n return head + contextLabel(el);\n }\n function describe(el) {\n const candidates = buildCandidates(el);\n if (!candidates.length) return null;\n return { candidates, semantic: semanticOf(el) };\n }\n\n // src/recorder/agent/capture.ts\n var ECHO_WINDOW_MS = 30;\n var GESTURE_WINDOW_MS = 1e3;\n var FOCUS_SETTLE_MS = 120;\n var MEANINGFUL_KEYS = [\n \"Enter\",\n \"Escape\",\n \"Tab\",\n \"ArrowUp\",\n \"ArrowDown\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Backspace\",\n \"Delete\",\n \"Home\",\n \"End\",\n \"PageUp\",\n \"PageDown\",\n \" \"\n ];\n function targetOf(e) {\n const t = e.target;\n return t && t.nodeType === 1 ? t : null;\n }\n function playwrightKey(e) {\n const mods = [];\n if (e.ctrlKey) mods.push(\"Control\");\n if (e.altKey) mods.push(\"Alt\");\n if (e.metaKey) mods.push(\"Meta\");\n if (e.shiftKey && e.key.length > 1) mods.push(\"Shift\");\n const key = e.key === \" \" ? \"Space\" : e.key;\n return [...mods, key].join(\"+\");\n }\n function isStopHotkey(e) {\n return (e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === \"x\";\n }\n function installCapture(ctx) {\n const { config, transport, reveals } = ctx;\n const focusValue = /* @__PURE__ */ new WeakMap();\n const committed = /* @__PURE__ */ new WeakMap();\n let lastRecorded = null;\n let lastPointerDown = null;\n function resolveGestureTarget(clicked) {\n const pd = lastPointerDown;\n if (!pd || pd.el === clicked) return clicked;\n if (Date.now() - pd.t > GESTURE_WINDOW_MS) return clicked;\n const retargeted = clicked === document.documentElement || clicked === document.body;\n if (retargeted && pd.el.isConnected && describe(pd.el)) return pd.el;\n if (!describe(clicked) && pd.el.isConnected && describe(pd.el)) return pd.el;\n return clicked;\n }\n function isEchoOfRecordedClick(el) {\n const prev = lastRecorded;\n if (!prev) return false;\n if (Date.now() - prev.t > ECHO_WINDOW_MS) return false;\n if (prev.el === el) return true;\n if (prev.el.contains(el) || el.contains(prev.el)) return true;\n const label = el.closest(\"label\");\n return Boolean(label && label === prev.el.closest(\"label\"));\n }\n function emitAction(action, el, value) {\n ctx.beforeAction?.();\n const target = el ? describe(el) : null;\n if (el && !target) return false;\n const t = Date.now();\n reveals.noteAction(t);\n const ev = { kind: \"action\", action, value, target, t, author: \"human\" };\n transport.emit(ev);\n return true;\n }\n let focusTimer = null;\n let lastFocus = null;\n function noteFocusChange() {\n if (focusTimer) clearTimeout(focusTimer);\n focusTimer = setTimeout(() => {\n const el = document.activeElement;\n const selector = el ? cssSelectorFor(el) : null;\n if (!selector || selector === lastFocus) return;\n lastFocus = selector;\n transport.emit({ kind: \"focus\", selector, t: Date.now() });\n }, FOCUS_SETTLE_MS);\n }\n function flushRevealingHover(actionTarget) {\n const hovered = reveals.takeLoadBearingHover(actionTarget);\n if (hovered) emitAction(\"hover\", hovered, null);\n }\n function valueOf(el) {\n if (el.isContentEditable) return clean(el.innerText, 500);\n return el.value ?? \"\";\n }\n function commitFill(el) {\n if (!isEditable(el)) return;\n const value = valueOf(el);\n if (committed.get(el) === value) return;\n if (focusValue.get(el) === value && committed.get(el) === void 0) return;\n committed.set(el, value);\n flushRevealingHover(el);\n emitAction(\"fill\", el, value);\n }\n function flushPendingFill() {\n const active = document.activeElement;\n if (active && isEditable(active)) commitFill(active);\n }\n const on = (type, handler) => {\n document.addEventListener(type, handler, true);\n };\n on(\"focusin\", (e) => {\n const el = targetOf(e);\n if (el && isEditable(el)) focusValue.set(el, valueOf(el));\n noteFocusChange();\n });\n on(\"focusout\", () => noteFocusChange());\n on(\"mouseover\", (e) => {\n const el = targetOf(e);\n if (el) reveals.noteHover(el);\n });\n on(\"pointerdown\", (e) => {\n const el = targetOf(e);\n if (el) lastPointerDown = { el, t: Date.now() };\n });\n on(\"click\", (e) => {\n const clicked = targetOf(e);\n if (!clicked) return;\n const el = resolveGestureTarget(clicked);\n if (isEchoOfRecordedClick(el)) return;\n flushPendingFill();\n flushRevealingHover(el);\n if (emitAction(\"click\", el, null)) lastRecorded = { el, t: Date.now() };\n });\n on(\"dblclick\", (e) => {\n const el = targetOf(e);\n if (!el) return;\n flushPendingFill();\n emitAction(\"dblclick\", el, null);\n });\n on(\"change\", (e) => {\n const el = targetOf(e);\n if (!el) return;\n if (el.tagName.toLowerCase() === \"select\") {\n const sel = el;\n const opt = sel.selectedOptions[0];\n flushPendingFill();\n flushRevealingHover(el);\n emitAction(\"select\", el, opt ? opt.value || clean(opt.textContent) : sel.value);\n return;\n }\n if (isEditable(el)) commitFill(el);\n });\n on(\"blur\", (e) => {\n const el = targetOf(e);\n if (el && isEditable(el)) commitFill(el);\n });\n on(\"keydown\", (e) => {\n if (isStopHotkey(e)) {\n e.preventDefault();\n e.stopPropagation();\n transport.binding(config.stopBinding)?.({ reason: \"hotkey\" });\n return;\n }\n const el = targetOf(e);\n if (isEditable(el)) {\n if (e.key !== \"Enter\" && e.key !== \"Escape\") return;\n commitFill(el);\n } else if (!MEANINGFUL_KEYS.includes(e.key) && !(e.ctrlKey || e.metaKey || e.altKey)) {\n return;\n } else {\n flushPendingFill();\n }\n emitAction(\"press\", el, playwrightKey(e));\n });\n installScrollCapture(ctx, emitAction);\n }\n var RESIZE_PROBE = [\n \".erd_scroll_detection_container\",\n \".resize-sensor\",\n \".resize-triggers\",\n \"[data-resize-sensor]\"\n ];\n function isResizeProbe(el) {\n if (!el) return false;\n return RESIZE_PROBE.some((sel) => {\n try {\n return el.matches(sel) || el.closest(sel) !== null;\n } catch {\n return false;\n }\n });\n }\n function installScrollCapture(ctx, emitAction) {\n const { config } = ctx;\n const lastPosition = /* @__PURE__ */ new WeakMap();\n const timers = /* @__PURE__ */ new WeakMap();\n document.addEventListener(\n \"scroll\",\n (e) => {\n const raw = e.target;\n if (raw && raw.nodeType === 1 && isResizeProbe(raw)) return;\n const isDocument = !raw || raw === document || raw === document.documentElement || raw === document.body;\n const key = isDocument ? document : raw;\n const el = isDocument ? null : raw;\n const x = el ? el.scrollLeft : window.scrollX;\n const y = el ? el.scrollTop : window.scrollY;\n const existing = timers.get(key);\n if (existing) clearTimeout(existing);\n timers.set(\n key,\n setTimeout(() => {\n const last = lastPosition.get(key) ?? { x: 0, y: 0 };\n const moved = Math.abs(x - last.x) >= config.scrollMinDeltaPx || Math.abs(y - last.y) >= config.scrollMinDeltaPx;\n if (!moved) return;\n lastPosition.set(key, { x, y });\n emitAction(\"scroll\", el, JSON.stringify({ x, y }));\n }, config.scrollDebounceMs)\n );\n },\n true\n );\n }\n\n // src/noise.ts\n var TRANSIENT_NAME = /\\b(loading|spinner|progress|skeleton|please wait|submitting|saving)\\b/i;\n function isTransientIndicator(role, name) {\n if (role === \"progressbar\") return true;\n return TRANSIENT_NAME.test(name);\n }\n\n // src/recorder/agent/visibility.ts\n function isVisible(el) {\n if (!el.isConnected) return false;\n const he = el;\n if (he.hidden || el.getAttribute(\"aria-hidden\") === \"true\") return false;\n const rect = he.getBoundingClientRect();\n if (rect.width === 0 && rect.height === 0) return false;\n const cs = window.getComputedStyle(he);\n return cs.visibility !== \"hidden\" && cs.display !== \"none\" && cs.opacity !== \"0\";\n }\n\n // src/recorder/agent/dom-reaction.ts\n var INTERESTING = \"[data-testid], [data-test], [data-test-id], [data-cy], [id], [role], button, a[href]\";\n var OBSERVED_ATTRS = [\"class\", \"style\", \"hidden\", \"aria-hidden\"];\n var APPEAR_CONFIRM_MS = 600;\n function toCssSelector(selector) {\n if (selector.startsWith(\"role=\") || selector.startsWith(\"text=\")) {\n throw new Error(\"not a CSS selector\");\n }\n return selector;\n }\n function harvest(nodes, into, pick, revealed, max) {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n if (!node || node.nodeType !== 1) continue;\n const el = node;\n revealed.push(el);\n const own = pick(el);\n if (own && into.size < max) into.add(own);\n const descendants = el.querySelectorAll(INTERESTING);\n for (let j = 0; j < descendants.length && into.size < max; j++) {\n const d = descendants[j];\n if (!d) continue;\n const sel = pick(d);\n if (sel) into.add(sel);\n }\n }\n }\n function observeDomReactions(ctx) {\n const { config, transport, reveals } = ctx;\n const visibility = /* @__PURE__ */ new WeakMap();\n let pending = [];\n const stillVisible = (sel) => {\n try {\n const el = document.querySelector(toCssSelector(sel));\n return el ? isVisible(el) : false;\n } catch {\n return true;\n }\n };\n const confirm = (entry) => {\n const survived = entry.selectors.filter(stillVisible);\n if (survived.length) {\n transport.emit({ kind: \"dom\", appeared: survived, gone: [], t: entry.at });\n }\n };\n const flushAppearances = () => {\n const due = pending;\n pending = [];\n for (const entry of due) {\n clearTimeout(entry.timer);\n confirm(entry);\n }\n };\n const visibleAppearedSelector = (el) => {\n if (!isVisible(el)) return null;\n if (isTransientIndicator(getRole(el), accessibleName(el))) return null;\n return appearedSelector(el);\n };\n const start = () => {\n const observer = new MutationObserver((records) => {\n const appeared = /* @__PURE__ */ new Set();\n const gone = /* @__PURE__ */ new Set();\n const revealed = [];\n for (const rec of records) {\n if (rec.type === \"childList\") {\n harvest(rec.addedNodes, appeared, visibleAppearedSelector, revealed, config.maxSelectorsPerMutation);\n harvest(rec.removedNodes, gone, goneSelector, [], config.maxSelectorsPerMutation);\n continue;\n }\n const el = rec.target;\n const sel = appearedSelector(el);\n if (!sel) continue;\n const now = isVisible(el);\n const was = visibility.get(el);\n visibility.set(el, now);\n if (was === void 0 || was === now) continue;\n if (now) {\n appeared.add(sel);\n revealed.push(el);\n } else {\n const g = goneSelector(el);\n if (g) gone.add(g);\n }\n }\n const at = Date.now();\n reveals.noteRevealed(revealed, at);\n if (gone.size) {\n transport.emit({ kind: \"dom\", appeared: [], gone: Array.from(gone), t: at });\n }\n if (appeared.size) {\n const entry = {\n selectors: Array.from(appeared),\n at,\n timer: setTimeout(() => {\n pending = pending.filter((e) => e !== entry);\n confirm(entry);\n }, APPEAR_CONFIRM_MS)\n };\n pending.push(entry);\n }\n });\n observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: OBSERVED_ATTRS\n });\n };\n if (document.documentElement) start();\n else document.addEventListener(\"DOMContentLoaded\", start, { once: true });\n return { flushAppearances };\n }\n\n // src/recorder/agent/reveal-tracker.ts\n var ACTION_COOLDOWN_MS = 600;\n function createRevealTracker(config) {\n let pending = null;\n let lastHovered = null;\n let lastActionAt = 0;\n return {\n noteHover(el) {\n lastHovered = el;\n },\n noteAction(at) {\n lastActionAt = at;\n },\n noteRevealed(nodes, at) {\n if (!nodes.length || !lastHovered) return;\n if (at - lastActionAt <= ACTION_COOLDOWN_MS) return;\n pending = { el: lastHovered, nodes, at };\n },\n takeLoadBearingHover(actionTarget) {\n const reveal = pending;\n pending = null;\n if (!reveal || !actionTarget) return null;\n if (Date.now() - reveal.at > config.hoverRevealWindowMs) return null;\n if (!reveal.el.isConnected) return null;\n const inside = reveal.nodes.some((n) => n === actionTarget || n.contains(actionTarget));\n return inside ? reveal.el : null;\n }\n };\n }\n\n // src/recorder/agent/transport.ts\n var DRAIN_INTERVAL_MS = 25;\n function createTransport(config) {\n const globals = window;\n const pending = [];\n let drainTimer = null;\n function binding(name) {\n const fn = globals[name];\n return typeof fn === \"function\" ? fn : null;\n }\n function drain() {\n const fn = binding(config.emitBinding);\n if (!fn) return;\n while (pending.length) {\n const ev = pending.shift();\n try {\n fn(ev);\n } catch {\n }\n }\n if (drainTimer) {\n clearInterval(drainTimer);\n drainTimer = null;\n }\n }\n function emit(ev) {\n pending.push(ev);\n if (binding(config.emitBinding)) drain();\n else if (!drainTimer) drainTimer = setInterval(drain, DRAIN_INTERVAL_MS);\n }\n return { emit, binding };\n }\n\n // src/recorder/agent/page-agent.ts\n var INSTALLED_FLAG = \"__replayAgentInstalled\";\n function pageAgent(config) {\n const globals = window;\n if (globals[INSTALLED_FLAG]) return;\n if (window.top !== window) return;\n globals[INSTALLED_FLAG] = true;\n const transport = createTransport(config);\n const reveals = createRevealTracker(config);\n const dom = observeDomReactions({ config, transport, reveals });\n installCapture({ config, transport, reveals, beforeAction: () => dom.flushAppearances() });\n globals[FLUSH_GLOBAL] = () => dom.flushAppearances();\n globals[AGENT_READY_FLAG] = true;\n }\n\n // src/recorder/agent/main.ts\n window[INSTALL_GLOBAL] = (config) => {\n pageAgent(config);\n };\n})();\n";
5
+ export const AGENT_BUNDLE = "\"use strict\";\n(() => {\n // src/recorder/agent/config.ts\n var INSTALL_GLOBAL = \"__replayInstall\";\n var AGENT_READY_FLAG = \"__replayAgentReady\";\n var FLUSH_GLOBAL = \"__replayFlush\";\n\n // src/recorder/agent/text.ts\n function clean(s, max = 80) {\n if (!s) return \"\";\n const t = s.replace(/\\s+/g, \" \").trim();\n return t.length > max ? `${t.slice(0, max - 1)}\\u2026` : t;\n }\n function escAttr(v) {\n return v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n }\n function escId(v) {\n return typeof window.CSS?.escape === \"function\" ? window.CSS.escape(v) : v.replace(/([^\\w-])/g, \"\\\\$1\");\n }\n function isStableToken(t) {\n if (!t || t.length > 40) return false;\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(t)) return false;\n if (/^(css|sc|emotion|styled|jsx|glamor|makeStyles)-/i.test(t)) return false;\n if (/[0-9a-f]{6,}/i.test(t)) return false;\n if ((t.match(/\\d/g) || []).length >= 3) return false;\n if (/[A-Z]/.test(t) && /\\d/.test(t)) return false;\n if (/^[a-z]{1,2}-[a-z0-9]{5,}$/i.test(t) && /\\d/.test(t)) return false;\n if (t.split(/[-_]/).some(looksGenerated)) return false;\n if (/-(aria|diff|live|announcer|portal)$/i.test(t)) return false;\n return true;\n }\n function looksGenerated(segment) {\n if (segment.length < 6) return false;\n if (!/^[a-z0-9]+$/i.test(segment)) return false;\n const vowels = (segment.match(/[aeiouy]/gi) || []).length;\n return /\\d/.test(segment) ? vowels <= 1 : vowels === 0;\n }\n function isStableClass(c) {\n return isStableToken(c) && !/^_/.test(c);\n }\n function renderedText(el, max = 80) {\n const inner = el.innerText;\n return clean(typeof inner === \"string\" ? inner : el.textContent, max);\n }\n\n // src/recorder/agent/roles.ts\n var TAG_ROLES = {\n button: \"button\",\n select: \"combobox\",\n textarea: \"textbox\",\n nav: \"navigation\",\n main: \"main\",\n header: \"banner\",\n footer: \"contentinfo\",\n aside: \"complementary\",\n form: \"form\",\n table: \"table\",\n tr: \"row\",\n td: \"cell\",\n th: \"columnheader\",\n ul: \"list\",\n ol: \"list\",\n li: \"listitem\",\n dialog: \"dialog\",\n option: \"option\",\n h1: \"heading\",\n h2: \"heading\",\n h3: \"heading\",\n h4: \"heading\",\n h5: \"heading\",\n h6: \"heading\"\n };\n var INPUT_ROLES = {\n button: \"button\",\n submit: \"button\",\n reset: \"button\",\n image: \"button\",\n checkbox: \"checkbox\",\n radio: \"radio\",\n range: \"slider\",\n number: \"spinbutton\",\n search: \"searchbox\",\n email: \"textbox\",\n tel: \"textbox\",\n text: \"textbox\",\n url: \"textbox\"\n };\n var NAME_FROM_CONTENT = [\n \"button\",\n \"link\",\n \"heading\",\n \"cell\",\n \"columnheader\",\n \"rowheader\",\n \"listitem\",\n \"option\",\n \"tab\",\n \"menuitem\",\n \"menuitemcheckbox\",\n \"menuitemradio\",\n \"treeitem\",\n \"switch\",\n \"checkbox\",\n \"radio\"\n ];\n var NON_EDITABLE_INPUTS = [\"checkbox\", \"radio\", \"button\", \"submit\", \"reset\", \"file\", \"image\"];\n function getRole(el) {\n const explicit = el.getAttribute(\"role\");\n if (explicit) return explicit.trim().split(/\\s+/)[0] || null;\n const tag = el.tagName.toLowerCase();\n if (tag === \"a\") return el.hasAttribute(\"href\") ? \"link\" : null;\n if (tag === \"img\") return el.getAttribute(\"alt\") === \"\" ? null : \"img\";\n if (tag === \"input\") {\n const type = (el.getAttribute(\"type\") || \"text\").toLowerCase();\n return INPUT_ROLES[type] ?? null;\n }\n return TAG_ROLES[tag] ?? null;\n }\n function isEditable(el) {\n if (!el || el.nodeType !== 1) return false;\n const tag = el.tagName.toLowerCase();\n if (tag === \"textarea\") return true;\n if (el.isContentEditable) return true;\n if (tag !== \"input\") return false;\n const type = (el.getAttribute(\"type\") || \"text\").toLowerCase();\n return !NON_EDITABLE_INPUTS.includes(type);\n }\n function accessibleName(el) {\n const aria = el.getAttribute(\"aria-label\");\n if (aria && aria.trim()) return clean(aria);\n const labelledby = el.getAttribute(\"aria-labelledby\");\n if (labelledby) {\n const parts = labelledby.split(/\\s+/).map((id) => document.getElementById(id)).filter((n) => !!n).map((n) => clean(n.textContent));\n const joined = clean(parts.filter(Boolean).join(\" \"));\n if (joined) return joined;\n }\n const tag = el.tagName.toLowerCase();\n if ([\"input\", \"select\", \"textarea\"].includes(tag)) {\n const id = el.getAttribute(\"id\");\n if (id) {\n const forLabel = document.querySelector(`label[for=\"${escAttr(id)}\"]`);\n if (forLabel) {\n const t = clean(forLabel.textContent);\n if (t) return t;\n }\n }\n const wrapping = el.closest(\"label\");\n if (wrapping) {\n const t = clean(wrapping.textContent);\n if (t) return t;\n }\n }\n for (const attr of [\"alt\", \"title\", \"placeholder\"]) {\n const v = el.getAttribute(attr);\n if (v && v.trim()) return clean(v);\n }\n const role = getRole(el);\n if (role && NAME_FROM_CONTENT.includes(role)) {\n const t = clean(el.textContent);\n if (t) return t;\n }\n return \"\";\n }\n function ownText(el) {\n return clean(el.textContent, 60);\n }\n\n // src/recorder/agent/selectors.ts\n var TEST_ID_ATTRS = [\n \"data-testid\",\n \"data-test\",\n \"data-test-id\",\n \"data-cy\",\n \"data-sentry-label\",\n \"data-testid-label\"\n ];\n var MAX_CANDIDATES = 6;\n var INTERACTIVE = 'button, a[href], [role=\"button\"], [role=\"link\"], [role=\"menuitem\"], [role=\"menuitemradio\"], [role=\"option\"], [role=\"tab\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"], [tabindex], [data-focusable=\"true\"], [onclick]';\n var MAX_CSS_PATH_DEPTH = 6;\n function testIdSelector(el) {\n for (const attr of TEST_ID_ATTRS) {\n const v = el.getAttribute(attr);\n if (v) return `[${attr}=\"${escAttr(v)}\"]`;\n }\n return null;\n }\n function idSelector(el) {\n const id = el.getAttribute(\"id\");\n return id && isStableToken(id) ? `#${escId(id)}` : null;\n }\n function roleNameSelector(el) {\n const role = getRole(el);\n if (!role) return null;\n const name = accessibleName(el);\n if (!name) return null;\n return `role=${role}[name=\"${escAttr(name)}\"]`;\n }\n function appearedSelector(el) {\n return testIdSelector(el) ?? idSelector(el) ?? roleNameSelector(el);\n }\n function goneSelector(el) {\n return testIdSelector(el) ?? idSelector(el);\n }\n function cssSelectorFor(el) {\n return testIdSelector(el) ?? idSelector(el) ?? buildCssPath(el);\n }\n function allElements() {\n return Array.from(document.querySelectorAll(\"*\"));\n }\n function withNth(base, el, matches) {\n if (matches.length <= 1) return base;\n const idx = matches.indexOf(el);\n return idx < 0 ? base : `${base} >> nth=${idx}`;\n }\n function buildCssPath(el) {\n const segs = [];\n let cur = el;\n let depth = 0;\n while (cur && cur.nodeType === 1 && cur !== document.documentElement && depth < MAX_CSS_PATH_DEPTH) {\n const node = cur;\n if (depth > 0) {\n const anchor = testIdSelector(node) ?? idSelector(node);\n if (anchor) {\n segs.unshift(anchor);\n break;\n }\n }\n let seg = node.tagName.toLowerCase();\n const classes = Array.from(node.classList).filter(isStableClass).slice(0, 2);\n if (classes.length) seg += `.${classes.map(escId).join(\".\")}`;\n const parent = node.parentElement;\n if (parent) {\n const sameTag = Array.from(parent.children).filter((c) => c.tagName === node.tagName);\n if (sameTag.length > 1) seg += `:nth-of-type(${sameTag.indexOf(node) + 1})`;\n }\n segs.unshift(seg);\n const partial = segs.join(\" > \");\n try {\n if (document.querySelectorAll(partial).length === 1) return partial;\n } catch {\n return null;\n }\n cur = parent;\n depth++;\n }\n if (!segs.length) return null;\n const sel = segs.join(\" > \");\n try {\n return document.querySelectorAll(sel).length ? sel : null;\n } catch {\n return null;\n }\n }\n function buildCandidates(el) {\n const out = [];\n const push = (s) => {\n if (s && out.indexOf(s) === -1) out.push(s);\n };\n push(testIdSelector(el));\n push(idSelector(el));\n const nameAttr = el.getAttribute(\"name\");\n if (nameAttr && isStableToken(nameAttr)) {\n push(`${el.tagName.toLowerCase()}[name=\"${escAttr(nameAttr)}\"]`);\n }\n const roleName = roleNameSelector(el);\n if (roleName) {\n const role = getRole(el);\n const name = accessibleName(el);\n const matches = allElements().filter((c) => getRole(c) === role && accessibleName(c) === name);\n push(withNth(roleName, el, matches));\n }\n for (const selector of interactiveHostSelectors(el)) push(selector);\n push(labelledAncestorSelector(el));\n const cssPath = buildCssPath(el);\n const text = ownText(el);\n const textSelector = text && isSmallestWithText(el, text) ? withNth(`text=\"${escAttr(text)}\"`, el, allElements().filter((c) => isSmallestWithText(c, text))) : null;\n if (isPositionalOnly(cssPath)) {\n push(textSelector);\n push(cssPath);\n } else {\n push(cssPath);\n push(textSelector);\n }\n return out.slice(0, MAX_CANDIDATES);\n }\n function interactiveHostSelectors(el) {\n let host = null;\n try {\n const found = el.closest(INTERACTIVE);\n host = found && found !== el ? found : null;\n } catch {\n host = null;\n }\n if (!host) return [];\n const out = [];\n const push = (s) => {\n if (s && out.indexOf(s) === -1) out.push(s);\n };\n push(testIdSelector(host));\n push(idSelector(host));\n push(roleNameSelector(host));\n const label = renderedText(host, 60);\n if (label) {\n const tag = host.tagName.toLowerCase();\n const role = host.getAttribute(\"role\");\n const qualifier = host.getAttribute(\"data-focusable\") ? '[data-focusable=\"true\"]' : role ? `[role=\"${escAttr(role)}\"]` : host.hasAttribute(\"tabindex\") ? `[tabindex=\"${escAttr(host.getAttribute(\"tabindex\") ?? \"0\")}\"]` : \"\";\n if (qualifier) push(`${tag}${qualifier}:has-text(\"${escAttr(label)}\")`);\n }\n return out;\n }\n function isPositionalOnly(selector) {\n if (!selector) return false;\n if (!selector.includes(\":nth-of-type(\")) return false;\n return !selector.includes(\"[\") && !selector.includes(\".\");\n }\n function labelledAncestorSelector(el) {\n if (testIdSelector(el) ?? idSelector(el)) return null;\n let cur = el.parentElement;\n for (let depth = 0; cur && depth < MAX_CSS_PATH_DEPTH; depth++) {\n const anchor = testIdSelector(cur);\n if (anchor) {\n try {\n return document.querySelectorAll(anchor).length === 1 ? anchor : null;\n } catch {\n return null;\n }\n }\n cur = cur.parentElement;\n }\n return null;\n }\n function isSmallestWithText(el, text) {\n if (ownText(el) !== text) return false;\n return !Array.from(el.children).some((child) => ownText(child) === text);\n }\n function contextLabel(el) {\n const row = el.closest('tr, [role=\"row\"], li, [role=\"listitem\"], [data-testid*=\"row\"]');\n if (row && row !== el) {\n const own = renderedText(el, 60);\n const full = renderedText(row, 120);\n const t = clean(own ? full.split(own).join(\" \") : full, 60);\n if (t) return ` in the row containing \"${t}\"`;\n }\n const section = el.closest('form, section, dialog, [role=\"dialog\"], nav, main, aside');\n if (section && section !== el) {\n const label = section.getAttribute(\"aria-label\") || clean(section.querySelector(\"h1, h2, h3, h4, legend\")?.textContent) || \"\";\n if (label) return ` in the ${section.tagName.toLowerCase()} labelled \"${clean(label, 40)}\"`;\n }\n return \"\";\n }\n function semanticOf(el) {\n const role = getRole(el) || el.tagName.toLowerCase();\n const name = accessibleName(el) || ownText(el);\n const head = name ? `${name} ${role}` : role;\n return head + contextLabel(el);\n }\n function describe(el) {\n const candidates = buildCandidates(el);\n if (!candidates.length) return null;\n return { candidates, semantic: semanticOf(el) };\n }\n\n // src/recorder/agent/capture.ts\n var ECHO_WINDOW_MS = 30;\n var GESTURE_WINDOW_MS = 1e3;\n var FOCUS_SETTLE_MS = 120;\n var MEANINGFUL_KEYS = [\n \"Enter\",\n \"Escape\",\n \"Tab\",\n \"ArrowUp\",\n \"ArrowDown\",\n \"ArrowLeft\",\n \"ArrowRight\",\n \"Backspace\",\n \"Delete\",\n \"Home\",\n \"End\",\n \"PageUp\",\n \"PageDown\",\n \" \"\n ];\n function targetOf(e) {\n const t = e.target;\n return t && t.nodeType === 1 ? t : null;\n }\n function playwrightKey(e) {\n const mods = [];\n if (e.ctrlKey) mods.push(\"Control\");\n if (e.altKey) mods.push(\"Alt\");\n if (e.metaKey) mods.push(\"Meta\");\n if (e.shiftKey && e.key.length > 1) mods.push(\"Shift\");\n const key = e.key === \" \" ? \"Space\" : e.key;\n return [...mods, key].join(\"+\");\n }\n function isStopHotkey(e) {\n return (e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === \"x\";\n }\n function installCapture(ctx) {\n const { config, transport, reveals } = ctx;\n const focusValue = /* @__PURE__ */ new WeakMap();\n const committed = /* @__PURE__ */ new WeakMap();\n let lastRecorded = null;\n let lastPointerDown = null;\n function resolveGestureTarget(clicked) {\n const pd = lastPointerDown;\n if (!pd || pd.el === clicked) return clicked;\n if (Date.now() - pd.t > GESTURE_WINDOW_MS) return clicked;\n const retargeted = clicked === document.documentElement || clicked === document.body;\n if (retargeted && pd.el.isConnected && describe(pd.el)) return pd.el;\n if (!describe(clicked) && pd.el.isConnected && describe(pd.el)) return pd.el;\n return clicked;\n }\n function isEchoOfRecordedClick(el) {\n const prev = lastRecorded;\n if (!prev) return false;\n if (Date.now() - prev.t > ECHO_WINDOW_MS) return false;\n if (prev.el === el) return true;\n if (prev.el.contains(el) || el.contains(prev.el)) return true;\n const label = el.closest(\"label\");\n return Boolean(label && label === prev.el.closest(\"label\"));\n }\n function emitAction(action, el, value) {\n ctx.beforeAction?.();\n const target = el ? describe(el) : null;\n if (el && !target) return false;\n const t = Date.now();\n reveals.noteAction(t);\n const ev = { kind: \"action\", action, value, target, t, author: \"human\" };\n transport.emit(ev);\n return true;\n }\n let focusTimer = null;\n let lastFocus = null;\n function noteFocusChange() {\n if (focusTimer) clearTimeout(focusTimer);\n focusTimer = setTimeout(() => {\n const el = document.activeElement;\n const selector = el ? cssSelectorFor(el) : null;\n if (!selector || selector === lastFocus) return;\n lastFocus = selector;\n transport.emit({ kind: \"focus\", selector, t: Date.now() });\n }, FOCUS_SETTLE_MS);\n }\n function flushRevealingHover(actionTarget) {\n const hovered = reveals.takeLoadBearingHover(actionTarget);\n if (hovered) emitAction(\"hover\", hovered, null);\n }\n function valueOf(el) {\n if (el.isContentEditable) return clean(el.innerText, 500);\n return el.value ?? \"\";\n }\n function commitFill(el) {\n if (!isEditable(el)) return;\n const value = valueOf(el);\n if (committed.get(el) === value) return;\n if (focusValue.get(el) === value && committed.get(el) === void 0) return;\n committed.set(el, value);\n flushRevealingHover(el);\n emitAction(\"fill\", el, value);\n }\n function flushPendingFill() {\n const active = document.activeElement;\n if (active && isEditable(active)) commitFill(active);\n }\n const on = (type, handler) => {\n document.addEventListener(type, handler, true);\n };\n on(\"focusin\", (e) => {\n const el = targetOf(e);\n if (el && isEditable(el)) focusValue.set(el, valueOf(el));\n noteFocusChange();\n });\n on(\"focusout\", () => noteFocusChange());\n on(\"mouseover\", (e) => {\n const el = targetOf(e);\n if (el) reveals.noteHover(el);\n });\n on(\"pointerdown\", (e) => {\n const el = targetOf(e);\n if (el) lastPointerDown = { el, t: Date.now() };\n });\n on(\"click\", (e) => {\n const clicked = targetOf(e);\n if (!clicked) return;\n const el = resolveGestureTarget(clicked);\n if (isEchoOfRecordedClick(el)) return;\n flushPendingFill();\n flushRevealingHover(el);\n if (emitAction(\"click\", el, null)) lastRecorded = { el, t: Date.now() };\n });\n on(\"dblclick\", (e) => {\n const el = targetOf(e);\n if (!el) return;\n flushPendingFill();\n emitAction(\"dblclick\", el, null);\n });\n on(\"change\", (e) => {\n const el = targetOf(e);\n if (!el) return;\n if (el.tagName.toLowerCase() === \"select\") {\n const sel = el;\n const opt = sel.selectedOptions[0];\n flushPendingFill();\n flushRevealingHover(el);\n emitAction(\"select\", el, opt ? opt.value || clean(opt.textContent) : sel.value);\n return;\n }\n if (isEditable(el)) commitFill(el);\n });\n on(\"blur\", (e) => {\n const el = targetOf(e);\n if (el && isEditable(el)) commitFill(el);\n });\n on(\"keydown\", (e) => {\n if (isStopHotkey(e)) {\n e.preventDefault();\n e.stopPropagation();\n transport.binding(config.stopBinding)?.({ reason: \"hotkey\" });\n return;\n }\n const el = targetOf(e);\n if (isEditable(el)) {\n if (e.key !== \"Enter\" && e.key !== \"Escape\") return;\n commitFill(el);\n } else if (!MEANINGFUL_KEYS.includes(e.key) && !(e.ctrlKey || e.metaKey || e.altKey)) {\n return;\n } else {\n flushPendingFill();\n }\n emitAction(\"press\", el, playwrightKey(e));\n });\n installScrollCapture(ctx, emitAction);\n }\n var RESIZE_PROBE = [\n \".erd_scroll_detection_container\",\n \".resize-sensor\",\n \".resize-triggers\",\n \"[data-resize-sensor]\"\n ];\n function isResizeProbe(el) {\n if (!el) return false;\n return RESIZE_PROBE.some((sel) => {\n try {\n return el.matches(sel) || el.closest(sel) !== null;\n } catch {\n return false;\n }\n });\n }\n function installScrollCapture(ctx, emitAction) {\n const { config } = ctx;\n const lastPosition = /* @__PURE__ */ new WeakMap();\n const timers = /* @__PURE__ */ new WeakMap();\n document.addEventListener(\n \"scroll\",\n (e) => {\n const raw = e.target;\n if (raw && raw.nodeType === 1 && isResizeProbe(raw)) return;\n const isDocument = !raw || raw === document || raw === document.documentElement || raw === document.body;\n const key = isDocument ? document : raw;\n const el = isDocument ? null : raw;\n const x = el ? el.scrollLeft : window.scrollX;\n const y = el ? el.scrollTop : window.scrollY;\n const existing = timers.get(key);\n if (existing) clearTimeout(existing);\n timers.set(\n key,\n setTimeout(() => {\n const last = lastPosition.get(key) ?? { x: 0, y: 0 };\n const moved = Math.abs(x - last.x) >= config.scrollMinDeltaPx || Math.abs(y - last.y) >= config.scrollMinDeltaPx;\n if (!moved) return;\n lastPosition.set(key, { x, y });\n emitAction(\"scroll\", el, JSON.stringify({ x, y }));\n }, config.scrollDebounceMs)\n );\n },\n true\n );\n }\n\n // src/noise.ts\n var TRANSIENT_NAME = /\\b(loading|spinner|progress|skeleton|please wait|submitting|saving)\\b/i;\n function isTransientIndicator(role, name) {\n if (role === \"progressbar\") return true;\n return TRANSIENT_NAME.test(name);\n }\n\n // src/recorder/agent/visibility.ts\n function isVisible(el) {\n if (!el.isConnected) return false;\n const he = el;\n if (he.hidden || el.getAttribute(\"aria-hidden\") === \"true\") return false;\n const rect = he.getBoundingClientRect();\n if (rect.width === 0 && rect.height === 0) return false;\n const cs = window.getComputedStyle(he);\n return cs.visibility !== \"hidden\" && cs.display !== \"none\" && cs.opacity !== \"0\";\n }\n\n // src/recorder/agent/dom-reaction.ts\n var INTERESTING = \"[data-testid], [data-test], [data-test-id], [data-cy], [id], [role], button, a[href]\";\n var OBSERVED_ATTRS = [\"class\", \"style\", \"hidden\", \"aria-hidden\"];\n var APPEAR_CONFIRM_MS = 600;\n function toCssSelector(selector) {\n if (selector.startsWith(\"role=\") || selector.startsWith(\"text=\")) {\n throw new Error(\"not a CSS selector\");\n }\n return selector;\n }\n function harvest(nodes, into, pick, revealed, max) {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n if (!node || node.nodeType !== 1) continue;\n const el = node;\n revealed.push(el);\n const own = pick(el);\n if (own && into.size < max) into.add(own);\n const descendants = el.querySelectorAll(INTERESTING);\n for (let j = 0; j < descendants.length && into.size < max; j++) {\n const d = descendants[j];\n if (!d) continue;\n const sel = pick(d);\n if (sel) into.add(sel);\n }\n }\n }\n function observeDomReactions(ctx) {\n const { config, transport, reveals } = ctx;\n const visibility = /* @__PURE__ */ new WeakMap();\n let pending = [];\n const stillVisible = (sel) => {\n try {\n const el = document.querySelector(toCssSelector(sel));\n return el ? isVisible(el) : false;\n } catch {\n return true;\n }\n };\n const confirm = (entry) => {\n const survived = entry.selectors.filter(stillVisible);\n if (survived.length) {\n transport.emit({ kind: \"dom\", appeared: survived, gone: [], t: entry.at });\n }\n };\n const flushAppearances = () => {\n const due = pending;\n pending = [];\n for (const entry of due) {\n clearTimeout(entry.timer);\n confirm(entry);\n }\n };\n const visibleAppearedSelector = (el) => {\n if (!isVisible(el)) return null;\n if (isTransientIndicator(getRole(el), accessibleName(el))) return null;\n return appearedSelector(el);\n };\n const start = () => {\n const observer = new MutationObserver((records) => {\n const appeared = /* @__PURE__ */ new Set();\n const gone = /* @__PURE__ */ new Set();\n const revealed = [];\n for (const rec of records) {\n if (rec.type === \"childList\") {\n harvest(rec.addedNodes, appeared, visibleAppearedSelector, revealed, config.maxSelectorsPerMutation);\n harvest(rec.removedNodes, gone, goneSelector, [], config.maxSelectorsPerMutation);\n continue;\n }\n const el = rec.target;\n const sel = appearedSelector(el);\n if (!sel) continue;\n const now = isVisible(el);\n const was = visibility.get(el);\n visibility.set(el, now);\n if (was === void 0 || was === now) continue;\n if (now) {\n appeared.add(sel);\n revealed.push(el);\n } else {\n const g = goneSelector(el);\n if (g) gone.add(g);\n }\n }\n const at = Date.now();\n reveals.noteRevealed(revealed, at);\n if (gone.size) {\n transport.emit({ kind: \"dom\", appeared: [], gone: Array.from(gone), t: at });\n }\n if (appeared.size) {\n const entry = {\n selectors: Array.from(appeared),\n at,\n timer: setTimeout(() => {\n pending = pending.filter((e) => e !== entry);\n confirm(entry);\n }, APPEAR_CONFIRM_MS)\n };\n pending.push(entry);\n }\n });\n observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: OBSERVED_ATTRS\n });\n };\n if (document.documentElement) start();\n else document.addEventListener(\"DOMContentLoaded\", start, { once: true });\n return { flushAppearances };\n }\n\n // src/recorder/agent/reveal-tracker.ts\n var ACTION_COOLDOWN_MS = 600;\n function createRevealTracker(config) {\n let pending = null;\n let lastHovered = null;\n let lastActionAt = 0;\n return {\n noteHover(el) {\n lastHovered = el;\n },\n noteAction(at) {\n lastActionAt = at;\n },\n noteRevealed(nodes, at) {\n if (!nodes.length || !lastHovered) return;\n if (at - lastActionAt <= ACTION_COOLDOWN_MS) return;\n pending = { el: lastHovered, nodes, at };\n },\n takeLoadBearingHover(actionTarget) {\n const reveal = pending;\n pending = null;\n if (!reveal || !actionTarget) return null;\n if (Date.now() - reveal.at > config.hoverRevealWindowMs) return null;\n if (!reveal.el.isConnected) return null;\n const inside = reveal.nodes.some((n) => n === actionTarget || n.contains(actionTarget));\n return inside ? reveal.el : null;\n }\n };\n }\n\n // src/recorder/agent/transport.ts\n var DRAIN_INTERVAL_MS = 25;\n function createTransport(config) {\n const globals = window;\n const pending = [];\n let drainTimer = null;\n function binding(name) {\n const fn = globals[name];\n return typeof fn === \"function\" ? fn : null;\n }\n function drain() {\n const fn = binding(config.emitBinding);\n if (!fn) return;\n while (pending.length) {\n const ev = pending.shift();\n try {\n fn(ev);\n } catch {\n }\n }\n if (drainTimer) {\n clearInterval(drainTimer);\n drainTimer = null;\n }\n }\n function emit(ev) {\n pending.push(ev);\n if (binding(config.emitBinding)) drain();\n else if (!drainTimer) drainTimer = setInterval(drain, DRAIN_INTERVAL_MS);\n }\n return { emit, binding };\n }\n\n // src/recorder/agent/page-agent.ts\n var INSTALLED_FLAG = \"__replayAgentInstalled\";\n function pageAgent(config) {\n const globals = window;\n if (globals[INSTALLED_FLAG]) return;\n if (window.top !== window) return;\n globals[INSTALLED_FLAG] = true;\n const transport = createTransport(config);\n const reveals = createRevealTracker(config);\n const dom = observeDomReactions({ config, transport, reveals });\n installCapture({ config, transport, reveals, beforeAction: () => dom.flushAppearances() });\n globals[FLUSH_GLOBAL] = () => dom.flushAppearances();\n globals[AGENT_READY_FLAG] = true;\n }\n\n // src/recorder/agent/main.ts\n window[INSTALL_GLOBAL] = (config) => {\n pageAgent(config);\n };\n})();\n";
6
6
  //# sourceMappingURL=agent-bundle.generated.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-bundle.generated.js","sourceRoot":"","sources":["../../src/recorder/agent-bundle.generated.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,6EAA6E;AAC7E,oBAAoB;AAEpB,+DAA+D;AAC/D,MAAM,CAAC,MAAM,YAAY,GAAG,w3zBAAw3zB,CAAC"}
1
+ {"version":3,"file":"agent-bundle.generated.js","sourceRoot":"","sources":["../../src/recorder/agent-bundle.generated.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,6EAA6E;AAC7E,oBAAoB;AAEpB,+DAA+D;AAC/D,MAAM,CAAC,MAAM,YAAY,GAAG,8o2BAA8o2B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-replay",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Record a browser bug once, verify the fix in seconds. A deterministic eye for coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",