@enricai/barnacle 1.12.81 → 1.12.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/lib/llm/judges/verify-step-advanced.d.ts +1 -1
  2. package/dist/lib/llm/judges/verify-step-advanced.d.ts.map +1 -1
  3. package/dist/lib/llm/judges/verify-step-advanced.js +2 -1
  4. package/dist/lib/llm/judges/verify-step-advanced.js.map +1 -1
  5. package/dist/plugins/config-plugin.d.ts +1 -0
  6. package/dist/plugins/config-plugin.d.ts.map +1 -1
  7. package/dist/plugins/config-plugin.js +7 -1
  8. package/dist/plugins/config-plugin.js.map +1 -1
  9. package/dist/plugins/loader.d.ts +2 -2
  10. package/dist/plugins/loader.d.ts.map +1 -1
  11. package/dist/plugins/loader.js +6 -3
  12. package/dist/plugins/loader.js.map +1 -1
  13. package/dist/scraper/click-settle.d.ts +8 -2
  14. package/dist/scraper/click-settle.d.ts.map +1 -1
  15. package/dist/scraper/click-settle.js +27 -28
  16. package/dist/scraper/click-settle.js.map +1 -1
  17. package/dist/scraper/errors.d.ts +23 -2
  18. package/dist/scraper/errors.d.ts.map +1 -1
  19. package/dist/scraper/errors.js +32 -4
  20. package/dist/scraper/errors.js.map +1 -1
  21. package/dist/scraper/flow-runner.d.ts +38 -29
  22. package/dist/scraper/flow-runner.d.ts.map +1 -1
  23. package/dist/scraper/flow-runner.js +132 -74
  24. package/dist/scraper/flow-runner.js.map +1 -1
  25. package/dist/scraper/http-client.d.ts +7 -2
  26. package/dist/scraper/http-client.d.ts.map +1 -1
  27. package/dist/scraper/http-client.js +132 -19
  28. package/dist/scraper/http-client.js.map +1 -1
  29. package/dist/scraper/metrics.d.ts +2 -2
  30. package/dist/scraper/metrics.js +2 -2
  31. package/dist/scraper/phantom-click.d.ts +6 -0
  32. package/dist/scraper/phantom-click.d.ts.map +1 -1
  33. package/dist/scraper/phantom-click.js +62 -15
  34. package/dist/scraper/phantom-click.js.map +1 -1
  35. package/dist/site-plugin.d.ts +7 -4
  36. package/dist/site-plugin.d.ts.map +1 -1
  37. package/package.json +1 -1
@@ -78,6 +78,7 @@ exports.verifyFillReadback = verifyFillReadback;
78
78
  exports.verifyPromptSelectorCommitted = verifyPromptSelectorCommitted;
79
79
  exports.tailRetargetPhrasesForStep = tailRetargetPhrasesForStep;
80
80
  exports.stepQuotesValueNotControlName = stepQuotesValueNotControlName;
81
+ exports.waitForUploadNetworkSignal = waitForUploadNetworkSignal;
81
82
  exports.isUploadAffordanceLabel = isUploadAffordanceLabel;
82
83
  exports.writeFixtureToTempFile = writeFixtureToTempFile;
83
84
  exports.attachToSurfacedInput = attachToSurfacedInput;
@@ -393,6 +394,8 @@ function wireSignalCapture(page, params) {
393
394
  method: capture.method,
394
395
  status: capture.status,
395
396
  url: capture.url,
397
+ hasBody: (req.requestPostData ?? "").length > 0,
398
+ multipart: /^multipart\//i.test(requestContentType(req.requestHeaders)),
396
399
  });
397
400
  if (recentCaptureMeta.length > RECENT_CAPTURES_WINDOW) {
398
401
  recentCaptureMeta.splice(0, recentCaptureMeta.length - RECENT_CAPTURES_WINDOW);
@@ -1156,13 +1159,60 @@ function fieldValuesAtFailureExpr() {
1156
1159
  return out;
1157
1160
  })()`;
1158
1161
  }
1162
+ /**
1163
+ * Reads the in-page quiescence probe as a typed, non-null value. A detached
1164
+ * frame or malformed probe result reads as idle so settle consumes the reader
1165
+ * directly and needs no guard of its own.
1166
+ */
1167
+ async function readPageQuiescence(target) {
1168
+ const idle = { pending: 0, mutations: 0 };
1169
+ const result = await Promise.resolve(target.evaluate(click_settle_1.QUIESCENCE_PROBE_EXPR)).catch(() => idle);
1170
+ if (result === null || typeof result !== "object")
1171
+ return idle;
1172
+ const { pending, mutations } = result;
1173
+ return typeof pending === "number" && typeof mutations === "number"
1174
+ ? { pending, mutations }
1175
+ : idle;
1176
+ }
1177
+ /**
1178
+ * Reads only the visible-text signature settle polls on. Going through
1179
+ * {@link snapshotPage} also read the frame URL, which rejects once a submit
1180
+ * detaches the OOPIF and would abort the settle mid-poll; a failed read here
1181
+ * degrades to the empty signature like the snapshot's own text read does.
1182
+ */
1183
+ async function readSettleTextSignature(target) {
1184
+ const result = await Promise.resolve(target.evaluate(DOM_SNAPSHOT_EXPR)).catch(() => null);
1185
+ if (result === null || typeof result !== "object")
1186
+ return "";
1187
+ const { text } = result;
1188
+ return typeof text === "string" ? text : "";
1189
+ }
1159
1190
  /**
1160
1191
  * Settles after a click using the target's own text signature and in-page
1161
1192
  * quiescence reader, so every post-click site decides from the same settle
1162
1193
  * signal instead of hand-building the readers.
1163
1194
  */
1164
- async function settleWithPageQuiescence(page, target, signalCounter, preNetworkCount, submitShaped, preVisibleTextSignature) {
1165
- await (0, click_settle_1.settleAfterClick)(page, signalCounter, preNetworkCount, submitShaped, async () => (await snapshotPage(target, signalCounter)).visibleTextSignature, preVisibleTextSignature, async () => target.evaluate(click_settle_1.QUIESCENCE_PROBE_EXPR));
1195
+ async function settleWithPageQuiescence(page, target, signalCounter, preNetworkCount, submitShaped, preVisibleTextSignature, advanced) {
1196
+ await (0, click_settle_1.settleAfterClick)(page, signalCounter, preNetworkCount, submitShaped, () => readSettleTextSignature(target), preVisibleTextSignature, () => readPageQuiescence(target), advanced);
1197
+ }
1198
+ /**
1199
+ * Builds the "has the page finished advancing" test a mid-flow submit settles
1200
+ * against. It is the body-swap half of the {@link hasPageAdvanced} the phantom
1201
+ * classifier credits with, so a header that flipped before its body re-rendered
1202
+ * keeps settle polling while the classifier still credits whatever is left once
1203
+ * the wait is spent.
1204
+ * Undefined for any other step, which keeps the plain quiet-window settle.
1205
+ */
1206
+ function pageAdvancedSince(preVisibleTextSignature, midFlowSubmit) {
1207
+ if (!midFlowSubmit)
1208
+ return undefined;
1209
+ const pre = {
1210
+ networkCount: 0,
1211
+ url: "",
1212
+ bodyHtmlLength: 0,
1213
+ visibleTextSignature: preVisibleTextSignature,
1214
+ };
1215
+ return (visibleTextSignature) => (0, phantom_click_1.hasPageBodySwapped)(pre, { ...pre, visibleTextSignature });
1166
1216
  }
1167
1217
  /**
1168
1218
  * Captures the pre/post signal triple the submit-verify cascade diffs.
@@ -2915,10 +2965,18 @@ async function judgeStepOutcome(params) {
2915
2965
  captureFn,
2916
2966
  });
2917
2967
  }
2918
- await (0, click_settle_1.settleAfterClick)(params.page, params.signalCounter, params.preNetworkCount, isStepSettleWaitShaped({ step, submitStep: false, isFinalStep, flowHasSubmitSemantics: false }), async () => {
2919
- const text = await probeRawVisibleText(target);
2920
- return `${text.length}:${text.slice(0, 200)}`;
2921
- }, params.preVisibleTextSignature, async () => target.evaluate(click_settle_1.QUIESCENCE_PROBE_EXPR));
2968
+ await settleWithPageQuiescence(params.page, target, params.signalCounter, params.preNetworkCount, isStepSettleWaitShaped({
2969
+ step,
2970
+ submitStep: params.submitStep,
2971
+ isFinalStep,
2972
+ flowHasSubmitSemantics: params.flowHasSubmitSemantics,
2973
+ }) || params.elementSubmitShaped === true, params.preVisibleTextSignature, pageAdvancedSince(params.preVisibleTextSignature, isMidFlowSubmitShapedStep({
2974
+ step,
2975
+ submitStep: params.submitStep,
2976
+ isFinalStep,
2977
+ flowHasSubmitSemantics: params.flowHasSubmitSemantics,
2978
+ elementSubmitShaped: params.elementSubmitShaped,
2979
+ })));
2922
2980
  const colon = params.preVisibleTextSignature.indexOf(":");
2923
2981
  const beforeText = (colon === -1 ? params.preVisibleTextSignature : params.preVisibleTextSignature.slice(colon + 1))
2924
2982
  .replace(/\s+/g, " ")
@@ -4523,12 +4581,17 @@ const UPLOAD_URL_PATTERNS = [
4523
4581
  "/media",
4524
4582
  "/import",
4525
4583
  ];
4584
+ /** Case-insensitive Content-Type lookup; CDP preserves the page's header casing. */
4585
+ function requestContentType(headers) {
4586
+ const key = Object.keys(headers).find((k) => k.toLowerCase() === "content-type");
4587
+ return key ? (headers[key] ?? "") : "";
4588
+ }
4526
4589
  /**
4527
4590
  * Poll the recent-capture window for an upload-related POST after a file has
4528
4591
  * been attached. Extracted from `tryUploadPrimitive` so the raw-input path,
4529
4592
  * the click-to-surface path, and the CDP native-chooser path all verify the
4530
- * same way. Returns true as soon as a non-GET capture whose URL matches
4531
- * {@link UPLOAD_URL_PATTERNS} lands; false if the timeout elapses first.
4593
+ * same way. Returns true as soon as a non-GET capture that is multipart, or is not
4594
+ * known to be body-less and has a URL matching {@link UPLOAD_URL_PATTERNS}, lands; false if the timeout elapses first.
4532
4595
  */
4533
4596
  async function waitForUploadNetworkSignal(params) {
4534
4597
  const { page, fixture, logger, signalCounter, recentCaptureMeta, stopWhen } = params;
@@ -4541,6 +4604,10 @@ async function waitForUploadNetworkSignal(params) {
4541
4604
  const uploadCapture = newCaptures.find((cap) => {
4542
4605
  if (cap.method === "GET")
4543
4606
  return false;
4607
+ if (cap.multipart)
4608
+ return true;
4609
+ if (cap.hasBody === false)
4610
+ return false;
4544
4611
  const lowerUrl = cap.url.toLowerCase();
4545
4612
  return UPLOAD_URL_PATTERNS.some((p) => lowerUrl.includes(p));
4546
4613
  });
@@ -4672,8 +4739,9 @@ async function tryUploadPrimitive(params) {
4672
4739
  * Attach the fixture to an already-surfaced `<input type=file>` (raw or freshly
4673
4740
  * mounted after a click) and verify. Extracted verbatim from the original
4674
4741
  * `tryUploadPrimitive` body so the raw-input path and the click-to-surface
4675
- * path share one setInputFiles + framework-change-dispatch + network/DOM verify
4676
- * + drag-drop-fallback implementation.
4742
+ * path share one setInputFiles + framework-change-dispatch + network/DOM verify implementation.
4743
+ * Returns true only on a verified signal
4744
+ * (upload POST or populated `.files`).
4677
4745
  */
4678
4746
  async function attachToSurfacedInput(params) {
4679
4747
  const { page, target, fixture, logger, signalCounter, recentCaptureMeta } = params;
@@ -4740,18 +4808,10 @@ async function attachToSurfacedInput(params) {
4740
4808
  .evaluate("(() => { const els = document.querySelectorAll('input[type=file]'); for (const el of els) { if (el.files && el.files.length > 0) return el.files.length; } return 0; })()")
4741
4809
  .catch(() => 0);
4742
4810
  if (typeof attachedLength !== "number" || attachedLength === 0) {
4743
- logger.warn("upload primitive: no network activity within timeout and no file attached in DOM; attempting drag-drop fallback");
4744
- // K'4: fall back to simulated drag-drop on the most-likely drop zone.
4745
- // Some custom upload widgets (Material Dropzone, react-dropzone, custom
4746
- // <uapp-upload> wrappers) only register files when a `drop` event with
4747
- // a DataTransfer fires on the visible drop area — they don't observe
4748
- // the hidden input's `files[]` mutations even with synthetic `change`
4749
- // dispatches. This is the documented Playwright community workaround.
4750
- const dragDropOk = await simulateDragDropUpload(target, fixture, logger);
4751
- if (dragDropOk) {
4752
- logger.info(`upload primitive: drag-drop fallback succeeded (name=${fixture.name}, size=${fixture.buffer.length}b)`);
4753
- return true;
4754
- }
4811
+ // A dispatched drop is unverified (no network signal, no .files), so it is
4812
+ // not success here: return false and let surfaceAndUpload run the verified
4813
+ // drop and the trusted-click/chooser routes.
4814
+ logger.warn("upload primitive: no network activity within timeout and no file attached in DOM; deferring to surface-and-upload");
4755
4815
  return false;
4756
4816
  }
4757
4817
  logger.info(`upload primitive: file attached in DOM after setInputFiles (deferred-upload widget; name=${fixture.name}, filesLength=${attachedLength})`);
@@ -6220,9 +6280,9 @@ function isNavigatingEvaluateRejection(error) {
6220
6280
  * wait and the phantom classifier decide from the same reader and baseline.
6221
6281
  * Without a page there is nothing to wait on and the snapshot is taken as-is.
6222
6282
  */
6223
- async function settleAndSnapshotPostClick(target, signalCounter, page, pre) {
6283
+ async function settleAndSnapshotPostClick(target, signalCounter, page, pre, midFlowSubmit = false) {
6224
6284
  if (page)
6225
- await settleWithPageQuiescence(page, target, signalCounter, pre.networkCount, true, pre.visibleTextSignature);
6285
+ await settleWithPageQuiescence(page, target, signalCounter, pre.networkCount, true, pre.visibleTextSignature, pageAdvancedSince(pre.visibleTextSignature, midFlowSubmit));
6226
6286
  return snapshotPage(target, signalCounter, page);
6227
6287
  }
6228
6288
  /**
@@ -6532,7 +6592,7 @@ async function submitCaptchaGatedForm(target, responseField = "h-captcha-respons
6532
6592
  if (clickResult.clicked) {
6533
6593
  if (!pre || !verification)
6534
6594
  return true;
6535
- const post = await settleAndSnapshotPostClick(target, verification.signalCounter, verification.page, pre);
6595
+ const post = await settleAndSnapshotPostClick(target, verification.signalCounter, verification.page, pre, verification.isMidFlowSubmitStep);
6536
6596
  const verdict = (0, phantom_click_1.classifyPhantomClick)({
6537
6597
  actResultSuccess: true,
6538
6598
  pre,
@@ -6553,7 +6613,7 @@ async function submitCaptchaGatedForm(target, responseField = "h-captcha-respons
6553
6613
  throw err;
6554
6614
  });
6555
6615
  if (runnerUpClickResult.clicked) {
6556
- const runnerUpPost = await settleAndSnapshotPostClick(target, verification.signalCounter, verification.page, post);
6616
+ const runnerUpPost = await settleAndSnapshotPostClick(target, verification.signalCounter, verification.page, post, verification.isMidFlowSubmitStep);
6557
6617
  const runnerUpVerdict = (0, phantom_click_1.classifyPhantomClick)({
6558
6618
  actResultSuccess: true,
6559
6619
  pre: post,
@@ -8766,6 +8826,9 @@ async function hasUnfilledRequiredControlForStep(target, instruction) {
8766
8826
  * upload/dropzone/file-related class or tag names. Tries multiple
8767
8827
  * candidates in order. Returns true on first success.
8768
8828
  *
8829
+ * Dispatch-only: a true return means the drop events fired, not that the
8830
+ * widget accepted the file; callers must verify.
8831
+ *
8769
8832
  * Site-agnostic — DataTransfer + DragEvent dispatch is universal HTML5
8770
8833
  * drag-and-drop API. Works on react-dropzone, Material Dropzone, custom
8771
8834
  * <uapp-upload>/<app-upload>, and any other drop-zone-based upload UI.
@@ -9873,11 +9936,7 @@ async function probeChildFrameSubmitFallback(params) {
9873
9936
  const clickResult = (await Promise.resolve(target.evaluate((0, submit_control_1.buildClickByDeepIndexExpr)(top.deepIndex))).catch(() => ({ clicked: false })));
9874
9937
  if (!clickResult.clicked)
9875
9938
  continue;
9876
- await (0, click_settle_1.settleAfterClick)(page, signalCounter, pre.networkCount, true, async () => {
9877
- const snap = await snapshotPage(target, signalCounter);
9878
- return snap.visibleTextSignature;
9879
- }, pre.visibleTextSignature, async () => target.evaluate(click_settle_1.QUIESCENCE_PROBE_EXPR));
9880
- const post = await snapshotPage(target, signalCounter, page);
9939
+ const post = await settleAndSnapshotPostClick(target, signalCounter, page, pre, isMidFlowSubmitStep);
9881
9940
  const verdict = (0, phantom_click_1.classifyPhantomClick)({
9882
9941
  actResultSuccess: true,
9883
9942
  pre,
@@ -9894,11 +9953,7 @@ async function probeChildFrameSubmitFallback(params) {
9894
9953
  const runnerUpClickResult = (await Promise.resolve(target.evaluate((0, submit_control_1.buildClickByDeepIndexExpr)(runnerUp.deepIndex))).catch(() => ({ clicked: false })));
9895
9954
  if (!runnerUpClickResult.clicked)
9896
9955
  continue;
9897
- await (0, click_settle_1.settleAfterClick)(page, signalCounter, post.networkCount, true, async () => {
9898
- const snap = await snapshotPage(target, signalCounter);
9899
- return snap.visibleTextSignature;
9900
- }, post.visibleTextSignature, async () => target.evaluate(click_settle_1.QUIESCENCE_PROBE_EXPR));
9901
- const runnerUpPost = await snapshotPage(target, signalCounter, page);
9956
+ const runnerUpPost = await settleAndSnapshotPostClick(target, signalCounter, page, post, isMidFlowSubmitStep);
9902
9957
  const runnerUpVerdict = (0, phantom_click_1.classifyPhantomClick)({
9903
9958
  actResultSuccess: true,
9904
9959
  pre,
@@ -11308,14 +11363,14 @@ async function executeStepWithHealing(params) {
11308
11363
  const runnerUp = ranked[1];
11309
11364
  if (runnerUp) {
11310
11365
  const midTarget = frameTarget ?? (0, frame_target_1.mainFrameTarget)(page);
11311
- const midPost = await (0, click_settle_1.settleAndSnapshot)({
11312
- page,
11313
- counter: signalCounter,
11314
- pre,
11315
- submitShaped: true,
11316
- snapshot: (withPage) => snapshotPage(midTarget, signalCounter, withPage ? page : undefined),
11317
- readQuiescence: async () => midTarget.evaluate(click_settle_1.QUIESCENCE_PROBE_EXPR),
11366
+ const deepMidFlowSubmit = isMidFlowSubmitShapedStep({
11367
+ step,
11368
+ submitStep,
11369
+ isFinalStep,
11370
+ flowHasSubmitSemantics: flowHasSubmitSemanticsFlag,
11371
+ elementSubmitShaped: !stepDeclaredNonSubmit,
11318
11372
  });
11373
+ const midPost = await settleAndSnapshotPostClick(midTarget, signalCounter, page, pre, deepMidFlowSubmit);
11319
11374
  const topVerdict = (0, phantom_click_1.classifyPhantomClick)({
11320
11375
  actResultSuccess: true,
11321
11376
  pre,
@@ -11324,20 +11379,8 @@ async function executeStepWithHealing(params) {
11324
11379
  // stray selection flip must not mask a top-pick phantom and skip
11325
11380
  // the runner-up retry.
11326
11381
  isSubmitShapedStep: true,
11327
- isMidFlowSubmitStep: isMidFlowSubmitShapedStep({
11328
- step,
11329
- submitStep,
11330
- isFinalStep,
11331
- flowHasSubmitSemantics: flowHasSubmitSemanticsFlag,
11332
- }),
11333
- destinationPlausible: (0, phantom_click_1.isPlausibleStepDestination)(step, midPost.url, isMidFlowSubmitShapedStep({
11334
- step,
11335
- submitStep,
11336
- isFinalStep,
11337
- flowHasSubmitSemantics: flowHasSubmitSemanticsFlag,
11338
- })
11339
- ? pre.url
11340
- : undefined),
11382
+ isMidFlowSubmitStep: deepMidFlowSubmit,
11383
+ destinationPlausible: (0, phantom_click_1.isPlausibleStepDestination)(step, midPost.url, deepMidFlowSubmit ? pre.url : undefined),
11341
11384
  });
11342
11385
  if (topVerdict === "phantom") {
11343
11386
  logger.info(`${formatStepPrefix(stepIndex, totalSteps)} deep-submit-locator top pick (${top.tag} "${top.accessibleName}") phantom-clicked; retrying runner-up ${runnerUp.tag} "${runnerUp.accessibleName}" (tier ${runnerUp.tier})`);
@@ -12068,6 +12111,24 @@ async function executeStepWithHealing(params) {
12068
12111
  }
12069
12112
  // A fill/select/type action cannot open a tab, so skip the discovery wait for it.
12070
12113
  await adoptOpenedPage(resolvedAction && STATE_CLASS_METHODS.has(resolvedAction.method ?? "") ? 0 : undefined);
12114
+ const isClick = resolvedAction !== null && resolvedAction.method === "click";
12115
+ // Attribute-based submit-shape read of the resolved click target itself —
12116
+ // independent of the flow-authored `submitStep` flag. Read before the
12117
+ // settle: a view swap removes the clicked control, so a post-swap read
12118
+ // would blind the mid-flow exemption on exactly the case it exists for.
12119
+ // See resolvedClickTargetIsSubmitShaped's doc comment.
12120
+ const resolvedElementIsSubmitShaped = isClick && !stepDeclaredNonSubmit && resolvedAction?.selector
12121
+ ? await resolvedClickTargetIsSubmitShaped(frameTarget ?? (0, frame_target_1.mainFrameTarget)(page), resolvedAction.selector)
12122
+ : false;
12123
+ // Computed once: gates every credit signal that takes no post-click URL
12124
+ // (network, DOM read-back) and classifyPhantomClick's destinationPlausible.
12125
+ const stepIsMidFlowSubmit = isMidFlowSubmitShapedStep({
12126
+ step,
12127
+ submitStep,
12128
+ isFinalStep,
12129
+ flowHasSubmitSemantics: flowHasSubmitSemanticsFlag,
12130
+ elementSubmitShaped: resolvedElementIsSubmitShaped,
12131
+ });
12071
12132
  await page.waitForTimeout(STEP_PAUSE_MS);
12072
12133
  // A successful act that reported no actions still ran a click-class action;
12073
12134
  // judging it before its round-trip lands would cascade onto the advanced page.
@@ -12076,7 +12137,7 @@ async function executeStepWithHealing(params) {
12076
12137
  : !STATE_CLASS_METHODS.has(resolvedAction.method ?? "");
12077
12138
  if (mayTriggerRoundTrip) {
12078
12139
  const settleTarget = frameTarget ?? (0, frame_target_1.mainFrameTarget)(page);
12079
- await settleWithPageQuiescence(page, settleTarget, signalCounter, pre.networkCount, isSettleWaitStep, pre.visibleTextSignature);
12140
+ await settleWithPageQuiescence(page, settleTarget, signalCounter, pre.networkCount, isSettleWaitStep || resolvedElementIsSubmitShaped, pre.visibleTextSignature, pageAdvancedSince(pre.visibleTextSignature, stepIsMidFlowSubmit));
12080
12141
  }
12081
12142
  const post = await snapshotPage(frameTarget ?? (0, frame_target_1.mainFrameTarget)(page), signalCounter, page);
12082
12143
  record.post = post;
@@ -12085,18 +12146,9 @@ async function executeStepWithHealing(params) {
12085
12146
  record.resolvedArguments = (resolvedAction.arguments ?? []).map(String);
12086
12147
  }
12087
12148
  const networkFired = post.networkCount > pre.networkCount;
12088
- // Computed once: gates every credit signal that takes no post-click URL
12089
- // (network, DOM read-back) and classifyPhantomClick's destinationPlausible.
12090
- const stepIsMidFlowSubmit = isMidFlowSubmitShapedStep({
12091
- step,
12092
- submitStep,
12093
- isFinalStep,
12094
- flowHasSubmitSemantics: flowHasSubmitSemanticsFlag,
12095
- });
12096
12149
  const destinationPlausible = (0, phantom_click_1.isPlausibleStepDestination)(step, post.url, stepIsMidFlowSubmit ? pre.url : undefined);
12097
12150
  const urlChanged = (0, phantom_click_1.hasOriginOrPathChanged)(pre.url, post.url) && destinationPlausible;
12098
12151
  const isStateClass = resolvedAction !== null && STATE_CLASS_METHODS.has(resolvedAction.method ?? "");
12099
- const isClick = resolvedAction !== null && resolvedAction.method === "click";
12100
12152
  // State-class actions (fill/check/etc.) never move the network counter or URL,
12101
12153
  // so the legacy heuristic false-negatived every form fill. Re-read DOM state
12102
12154
  // for those; keep the navigation signal authoritative for clicks/links —
@@ -12193,12 +12245,6 @@ async function executeStepWithHealing(params) {
12193
12245
  const clickedElementStillPresent = isClick && resolvedAction?.selector
12194
12246
  ? await resolvedClickTargetStillPresent(frameTarget ?? (0, frame_target_1.mainFrameTarget)(page), resolvedAction.selector)
12195
12247
  : true;
12196
- // Attribute-based submit-shape read of the resolved click target itself —
12197
- // independent of the flow-authored `submitStep` flag. See
12198
- // resolvedClickTargetIsSubmitShaped's doc comment.
12199
- const resolvedElementIsSubmitShaped = isClick && !stepDeclaredNonSubmit && resolvedAction?.selector
12200
- ? await resolvedClickTargetIsSubmitShaped(frameTarget ?? (0, frame_target_1.mainFrameTarget)(page), resolvedAction.selector)
12201
- : false;
12202
12248
  // Client-side view-swap gate: credit a click that produces substantial
12203
12249
  // DOM growth (≥5KB) with zero network when it's NOT a submit/final step
12204
12250
  // and NOT an advance-pattern step. Fixes the top-window site "Manual Application"
@@ -12499,6 +12545,9 @@ async function executeStepWithHealing(params) {
12499
12545
  captureFn,
12500
12546
  step,
12501
12547
  isFinalStep,
12548
+ submitStep,
12549
+ flowHasSubmitSemantics: flowHasSubmitSemanticsFlag,
12550
+ elementSubmitShaped: resolvedElementIsSubmitShaped,
12502
12551
  page,
12503
12552
  target: frameTarget ?? (0, frame_target_1.mainFrameTarget)(page),
12504
12553
  signalCounter,
@@ -12740,7 +12789,13 @@ async function executeStepWithHealing(params) {
12740
12789
  probeResult.checked === true &&
12741
12790
  (probeResult.ariaManaged === true || !ancestorStillInvalid);
12742
12791
  await page.waitForTimeout(STEP_PAUSE_MS);
12743
- await settleWithPageQuiescence(page, frameTarget ?? (0, frame_target_1.mainFrameTarget)(page), signalCounter, pre.networkCount, isSettleWaitStep, pre.visibleTextSignature);
12792
+ await settleWithPageQuiescence(page, frameTarget ?? (0, frame_target_1.mainFrameTarget)(page), signalCounter, pre.networkCount, isSettleWaitStep || retryResolvedElementIsSubmitShaped, pre.visibleTextSignature, pageAdvancedSince(pre.visibleTextSignature, isMidFlowSubmitShapedStep({
12793
+ step,
12794
+ submitStep,
12795
+ isFinalStep,
12796
+ flowHasSubmitSemantics: flowHasSubmitSemanticsFlag,
12797
+ elementSubmitShaped: retryResolvedElementIsSubmitShaped,
12798
+ })));
12744
12799
  const retryPost = await snapshotPage(frameTarget ?? (0, frame_target_1.mainFrameTarget)(page), signalCounter, page);
12745
12800
  const retryNetworkFired = retryPost.networkCount > pre.networkCount;
12746
12801
  const retryMidFlowSubmit = isMidFlowSubmitShapedStep({
@@ -13092,6 +13147,9 @@ async function executeStepWithHealing(params) {
13092
13147
  captureFn,
13093
13148
  step,
13094
13149
  isFinalStep,
13150
+ submitStep,
13151
+ flowHasSubmitSemantics: flowHasSubmitSemanticsFlag,
13152
+ elementSubmitShaped: resolvedElementIsSubmitShaped,
13095
13153
  page,
13096
13154
  target: frameTarget ?? (0, frame_target_1.mainFrameTarget)(page),
13097
13155
  signalCounter,