@riddledc/riddle-proof 0.7.197 → 0.7.198

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.
package/dist/cli.cjs CHANGED
@@ -7002,6 +7002,7 @@ var RIDDLE_PROOF_PROFILE_CHECK_TYPES = [
7002
7002
  var RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES = [
7003
7003
  "click",
7004
7004
  "tap",
7005
+ "tap_until",
7005
7006
  "drag",
7006
7007
  "press",
7007
7008
  "key_down",
@@ -7572,6 +7573,28 @@ function profileSetupTapReceipts(results) {
7572
7573
  reason: result.reason ?? result.error ?? null
7573
7574
  }));
7574
7575
  }
7576
+ function profileSetupTapUntilReceipts(results) {
7577
+ return results.filter((result) => profileSetupResultAction(result) === "tap_until").map((result) => ({
7578
+ ordinal: result.ordinal ?? null,
7579
+ ok: result.ok !== false,
7580
+ selector: result.selector ?? null,
7581
+ frame_selector: result.frame_selector ?? null,
7582
+ pointer_type: result.pointer_type ?? null,
7583
+ input_dispatch: result.input_dispatch ?? null,
7584
+ coordinate_mode: result.coordinate_mode ?? null,
7585
+ x: result.x ?? null,
7586
+ y: result.y ?? null,
7587
+ duration_ms: result.duration_ms ?? null,
7588
+ until_path: result.until_path ?? null,
7589
+ until_value: result.until_value ?? null,
7590
+ until_expected_value: result.until_expected_value ?? null,
7591
+ tap_count: result.tap_count ?? null,
7592
+ max_taps: result.max_taps ?? result.max_calls ?? null,
7593
+ interval_ms: result.interval_ms ?? null,
7594
+ timeout_ms: result.timeout_ms ?? null,
7595
+ reason: result.reason ?? result.error ?? null
7596
+ }));
7597
+ }
7575
7598
  function profileSetupKeyboardReceipts(results) {
7576
7599
  return results.filter((result) => ["press", "key_down", "key_up"].includes(profileSetupResultAction(result))).map((result) => ({
7577
7600
  action: profileSetupResultAction(result),
@@ -7747,6 +7770,9 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountByViewpo
7747
7770
  const sampledDragReceipts = sampleProfileSetupSummaryItems(dragReceipts, 8);
7748
7771
  const tapReceipts = profileSetupTapReceipts(results);
7749
7772
  const sampledTapReceipts = sampleProfileSetupSummaryItems(tapReceipts, 8);
7773
+ const tapUntilReceipts = profileSetupTapUntilReceipts(results);
7774
+ const tapUntilTapCounts = tapUntilReceipts.map((result) => typeof result.tap_count === "number" && Number.isFinite(result.tap_count) ? result.tap_count : void 0).filter((value) => value !== void 0);
7775
+ const sampledTapUntilReceipts = sampleProfileSetupSummaryItems(tapUntilReceipts, 8);
7750
7776
  const keyboardReceipts = profileSetupKeyboardReceipts(results);
7751
7777
  const sampledKeyboardReceipts = sampleProfileSetupSummaryItems(keyboardReceipts, 8);
7752
7778
  const canvasSignatureReceipts = profileSetupCanvasSignatureReceipts(results);
@@ -7819,6 +7845,10 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountByViewpo
7819
7845
  tap_total: tapReceipts.length,
7820
7846
  tap_truncated: tapReceipts.length > sampledTapReceipts.length,
7821
7847
  tap: sampledTapReceipts,
7848
+ tap_until_total: tapUntilReceipts.length,
7849
+ tap_until_tap_total: tapUntilTapCounts.reduce((sum, value) => sum + value, 0),
7850
+ tap_until_truncated: tapUntilReceipts.length > sampledTapUntilReceipts.length,
7851
+ tap_until: sampledTapUntilReceipts,
7822
7852
  keyboard_total: keyboardReceipts.length,
7823
7853
  keyboard_truncated: keyboardReceipts.length > sampledKeyboardReceipts.length,
7824
7854
  keyboard: sampledKeyboardReceipts,
@@ -7880,7 +7910,7 @@ function isSupportedCheckType(value) {
7880
7910
  }
7881
7911
  function normalizeSetupActionType(value, index) {
7882
7912
  const normalizedInput = String(value || "").trim().replace(/-/g, "_");
7883
- const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "reset_console" || normalizedInput === "clear_browser_console" || normalizedInput === "reset_browser_console" ? "clear_console" : normalizedInput === "pointer_drag" || normalizedInput === "mouse_drag" || normalizedInput === "drag_to" ? "drag" : normalizedInput === "pointer_tap" || normalizedInput === "touch_tap" || normalizedInput === "canvas_tap" ? "tap" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput === "keyboard_down" || normalizedInput === "key_down" || normalizedInput === "keydown" || normalizedInput === "press_down" ? "key_down" : normalizedInput === "keyboard_up" || normalizedInput === "key_up" || normalizedInput === "keyup" || normalizedInput === "press_up" || normalizedInput === "release_key" || normalizedInput === "key_release" ? "key_up" : normalizedInput === "set_slider_value" || normalizedInput === "slider_value" || normalizedInput === "set_slider" || normalizedInput === "set_range" || normalizedInput === "range_value" || normalizedInput === "range_input" || normalizedInput === "set_range_input" ? "set_range_value" : normalizedInput === "deterministic_runtime" || normalizedInput === "mock_runtime" || normalizedInput === "mock_random" || normalizedInput === "mock_random_queue" || normalizedInput === "seed_random_queue" || normalizedInput === "set_random_queue" || normalizedInput === "mock_clock" || normalizedInput === "set_mock_clock" || normalizedInput === "set_runtime_determinism" || normalizedInput === "runtime_determinism" ? "deterministic_runtime" : normalizedInput === "canvas_hash" || normalizedInput === "capture_canvas_hash" || normalizedInput === "capture_canvas_signature" || normalizedInput === "canvas_state_signature" ? "canvas_signature" : normalizedInput === "capture_screenshot" || normalizedInput === "save_screenshot" || normalizedInput === "setup_screenshot" ? "screenshot" : normalizedInput === "accept_dialog" || normalizedInput === "accept_dialogs" || normalizedInput === "confirm_dialog" || normalizedInput === "set_dialog_response" ? "dialog_response" : normalizedInput === "dismiss_dialog" || normalizedInput === "dismiss_dialogs" || normalizedInput === "cancel_dialog" ? "dialog_response" : normalizedInput === "window_call_until" || normalizedInput === "call_until" || normalizedInput === "window_call_repeat_until" || normalizedInput === "repeat_window_call_until" ? "window_call_until" : normalizedInput === "window_evaluate" || normalizedInput === "browser_eval" || normalizedInput === "browser_evaluate" || normalizedInput === "evaluate_script" || normalizedInput === "profile_script" ? "window_eval" : normalizedInput;
7913
+ const normalized = normalizedInput === "clear_browser_storage" ? "clear_storage" : normalizedInput === "reset_console" || normalizedInput === "clear_browser_console" || normalizedInput === "reset_browser_console" ? "clear_console" : normalizedInput === "pointer_drag" || normalizedInput === "mouse_drag" || normalizedInput === "drag_to" ? "drag" : normalizedInput === "pointer_tap" || normalizedInput === "touch_tap" || normalizedInput === "canvas_tap" ? "tap" : normalizedInput === "tap_until" || normalizedInput === "pointer_tap_until" || normalizedInput === "touch_tap_until" || normalizedInput === "canvas_tap_until" || normalizedInput === "tap_repeat_until" || normalizedInput === "repeat_tap_until" ? "tap_until" : normalizedInput === "keyboard_press" || normalizedInput === "key_press" ? "press" : normalizedInput === "keyboard_down" || normalizedInput === "key_down" || normalizedInput === "keydown" || normalizedInput === "press_down" ? "key_down" : normalizedInput === "keyboard_up" || normalizedInput === "key_up" || normalizedInput === "keyup" || normalizedInput === "press_up" || normalizedInput === "release_key" || normalizedInput === "key_release" ? "key_up" : normalizedInput === "set_slider_value" || normalizedInput === "slider_value" || normalizedInput === "set_slider" || normalizedInput === "set_range" || normalizedInput === "range_value" || normalizedInput === "range_input" || normalizedInput === "set_range_input" ? "set_range_value" : normalizedInput === "deterministic_runtime" || normalizedInput === "mock_runtime" || normalizedInput === "mock_random" || normalizedInput === "mock_random_queue" || normalizedInput === "seed_random_queue" || normalizedInput === "set_random_queue" || normalizedInput === "mock_clock" || normalizedInput === "set_mock_clock" || normalizedInput === "set_runtime_determinism" || normalizedInput === "runtime_determinism" ? "deterministic_runtime" : normalizedInput === "canvas_hash" || normalizedInput === "capture_canvas_hash" || normalizedInput === "capture_canvas_signature" || normalizedInput === "canvas_state_signature" ? "canvas_signature" : normalizedInput === "capture_screenshot" || normalizedInput === "save_screenshot" || normalizedInput === "setup_screenshot" ? "screenshot" : normalizedInput === "accept_dialog" || normalizedInput === "accept_dialogs" || normalizedInput === "confirm_dialog" || normalizedInput === "set_dialog_response" ? "dialog_response" : normalizedInput === "dismiss_dialog" || normalizedInput === "dismiss_dialogs" || normalizedInput === "cancel_dialog" ? "dialog_response" : normalizedInput === "window_call_until" || normalizedInput === "call_until" || normalizedInput === "window_call_repeat_until" || normalizedInput === "repeat_window_call_until" ? "window_call_until" : normalizedInput === "window_evaluate" || normalizedInput === "browser_eval" || normalizedInput === "browser_evaluate" || normalizedInput === "evaluate_script" || normalizedInput === "profile_script" ? "window_eval" : normalizedInput;
7884
7914
  if (RIDDLE_PROOF_PROFILE_SETUP_ACTION_TYPES.includes(normalized)) {
7885
7915
  return normalized;
7886
7916
  }
@@ -7962,8 +7992,8 @@ function normalizeSetupActionCoordinateMode(value, index) {
7962
7992
  }
7963
7993
  function normalizeSetupActionPointerType(value, type, index) {
7964
7994
  if (value === void 0 || value === null || value === "") return void 0;
7965
- if (type !== "drag" && type !== "tap") {
7966
- throw new Error(`target.setup_actions[${index}].pointer_type is only supported for drag/tap actions.`);
7995
+ if (type !== "drag" && type !== "tap" && type !== "tap_until") {
7996
+ throw new Error(`target.setup_actions[${index}].pointer_type is only supported for drag/tap/tap_until actions.`);
7967
7997
  }
7968
7998
  const normalized = String(value).trim().replace(/-/g, "_").toLowerCase();
7969
7999
  if (normalized === "mouse") return "mouse";
@@ -8042,11 +8072,11 @@ function normalizeSetupAction(input, index) {
8042
8072
  if (frameIndex !== void 0 && (!Number.isInteger(frameIndex) || frameIndex < 0)) {
8043
8073
  throw new Error(`target.setup_actions[${index}].frame_index must be a non-negative integer.`);
8044
8074
  }
8045
- if ((type === "click" || type === "tap" || type === "drag" || type === "fill" || type === "set_input_value" || type === "set_range_value" || type === "canvas_signature" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
8075
+ if ((type === "click" || type === "tap" || type === "tap_until" || type === "drag" || type === "fill" || type === "set_input_value" || type === "set_range_value" || type === "canvas_signature" || type === "wait_for_selector" || type === "wait_for_text" || type === "assert_text_visible" || type === "assert_text_absent" || type === "assert_selector_count") && !selector) {
8046
8076
  throw new Error(`target.setup_actions[${index}] ${type} requires selector.`);
8047
8077
  }
8048
- const fromX = type === "click" || type === "tap" ? numberValue(valueFromOwn(input, "from_x", "fromX", "x", "click_x", "clickX", "start_x", "startX", "x1")) : numberValue(valueFromOwn(input, "from_x", "fromX", "start_x", "startX", "x1"));
8049
- const fromY = type === "click" || type === "tap" ? numberValue(valueFromOwn(input, "from_y", "fromY", "y", "click_y", "clickY", "start_y", "startY", "y1")) : numberValue(valueFromOwn(input, "from_y", "fromY", "start_y", "startY", "y1"));
8078
+ const fromX = type === "click" || type === "tap" || type === "tap_until" ? numberValue(valueFromOwn(input, "from_x", "fromX", "x", "click_x", "clickX", "start_x", "startX", "x1")) : numberValue(valueFromOwn(input, "from_x", "fromX", "start_x", "startX", "x1"));
8079
+ const fromY = type === "click" || type === "tap" || type === "tap_until" ? numberValue(valueFromOwn(input, "from_y", "fromY", "y", "click_y", "clickY", "start_y", "startY", "y1")) : numberValue(valueFromOwn(input, "from_y", "fromY", "start_y", "startY", "y1"));
8050
8080
  const toX = numberValue(valueFromOwn(input, "to_x", "toX", "end_x", "endX", "x2"));
8051
8081
  const toY = numberValue(valueFromOwn(input, "to_y", "toY", "end_y", "endY", "y2"));
8052
8082
  const coordinateMode = normalizeSetupActionCoordinateMode(valueFromOwn(input, "coordinate_mode", "coordinateMode", "coords", "units"), index);
@@ -8071,18 +8101,18 @@ function normalizeSetupAction(input, index) {
8071
8101
  }
8072
8102
  }
8073
8103
  }
8074
- if (type === "tap") {
8104
+ if (type === "tap" || type === "tap_until") {
8075
8105
  const hasTapCoordinate = fromX !== void 0 || fromY !== void 0;
8076
8106
  if (hasTapCoordinate && (fromX === void 0 || fromY === void 0)) {
8077
- throw new Error(`target.setup_actions[${index}] tap coordinates require both x and y.`);
8107
+ throw new Error(`target.setup_actions[${index}] ${type} coordinates require both x and y.`);
8078
8108
  }
8079
8109
  if (hasTapCoordinate && fromX !== void 0 && fromY !== void 0) {
8080
8110
  const tapCoordinates = [fromX, fromY];
8081
8111
  if (coordinateMode === "ratio" && tapCoordinates.some((value2) => value2 < 0 || value2 > 1)) {
8082
- throw new Error(`target.setup_actions[${index}] tap ratio coordinates must be between 0 and 1.`);
8112
+ throw new Error(`target.setup_actions[${index}] ${type} ratio coordinates must be between 0 and 1.`);
8083
8113
  }
8084
8114
  if ((coordinateMode === void 0 || coordinateMode === "pixels") && tapCoordinates.some((value2) => value2 < 0)) {
8085
- throw new Error(`target.setup_actions[${index}] tap pixel coordinates must be non-negative.`);
8115
+ throw new Error(`target.setup_actions[${index}] ${type} pixel coordinates must be non-negative.`);
8086
8116
  }
8087
8117
  }
8088
8118
  }
@@ -8188,7 +8218,7 @@ function normalizeSetupAction(input, index) {
8188
8218
  const captureReturn = input.capture_return === false || input.captureReturn === false || input.include_return === false || input.includeReturn === false || input.omit_return === true || input.omitReturn === true ? false : void 0;
8189
8219
  const untilPath = stringFromOwn(input, "until_path", "untilPath", "until_state_path", "untilStatePath", "until_window_path", "untilWindowPath", "until");
8190
8220
  const hasUntilExpectedValue = hasOwn(input, "until_expected_value") || hasOwn(input, "untilExpectedValue") || hasOwn(input, "until_expected") || hasOwn(input, "untilExpected") || hasOwn(input, "until_value") || hasOwn(input, "untilValue") || hasOwn(input, "expected_value") || hasOwn(input, "expectedValue") || hasOwn(input, "expected");
8191
- if (type === "window_call_until") {
8221
+ if (type === "window_call_until" || type === "tap_until") {
8192
8222
  if (!untilPath) {
8193
8223
  throw new Error(`target.setup_actions[${index}] ${type} requires until_path.`);
8194
8224
  }
@@ -8196,12 +8226,12 @@ function normalizeSetupAction(input, index) {
8196
8226
  throw new Error(`target.setup_actions[${index}] ${type} requires until_expected_value.`);
8197
8227
  }
8198
8228
  }
8199
- const maxCalls = numberValue(valueFromOwn(input, "max_calls", "maxCalls", "max_attempts", "maxAttempts", "attempts"));
8200
- if (type === "window_call_until" && (maxCalls === void 0 || !Number.isInteger(maxCalls) || maxCalls < 1 || maxCalls > 100)) {
8229
+ const maxCalls = numberValue(valueFromOwn(input, "max_calls", "maxCalls", "max_attempts", "maxAttempts", "attempts", "max_taps", "maxTaps", "tap_limit", "tapLimit"));
8230
+ if ((type === "window_call_until" || type === "tap_until") && (maxCalls === void 0 || !Number.isInteger(maxCalls) || maxCalls < 1 || maxCalls > 100)) {
8201
8231
  throw new Error(`target.setup_actions[${index}].max_calls must be an integer from 1 to 100.`);
8202
8232
  }
8203
8233
  const intervalMs = numberValue(valueFromOwn(input, "interval_ms", "intervalMs", "poll_ms", "pollMs", "call_interval_ms", "callIntervalMs"));
8204
- if (type === "window_call_until" && intervalMs !== void 0 && (!Number.isInteger(intervalMs) || intervalMs < 0 || intervalMs > 5e3)) {
8234
+ if ((type === "window_call_until" || type === "tap_until") && intervalMs !== void 0 && (!Number.isInteger(intervalMs) || intervalMs < 0 || intervalMs > 5e3)) {
8205
8235
  throw new Error(`target.setup_actions[${index}].interval_ms must be an integer from 0 to 5000.`);
8206
8236
  }
8207
8237
  const steps = numberValue(input.steps);
@@ -11451,6 +11481,30 @@ function profileSetupTapReceipts(results) {
11451
11481
  reason: result.reason || result.error || null,
11452
11482
  }));
11453
11483
  }
11484
+ function profileSetupTapUntilReceipts(results) {
11485
+ return (results || [])
11486
+ .filter((result) => result && profileSetupResultAction(result) === "tap_until")
11487
+ .map((result) => ({
11488
+ ordinal: result.ordinal ?? null,
11489
+ ok: result.ok !== false,
11490
+ selector: result.selector ?? null,
11491
+ frame_selector: result.frame_selector ?? null,
11492
+ pointer_type: result.pointer_type ?? null,
11493
+ input_dispatch: result.input_dispatch ?? null,
11494
+ coordinate_mode: result.coordinate_mode ?? null,
11495
+ x: result.x ?? null,
11496
+ y: result.y ?? null,
11497
+ duration_ms: result.duration_ms ?? null,
11498
+ until_path: result.until_path ?? null,
11499
+ until_value: result.until_value ?? null,
11500
+ until_expected_value: result.until_expected_value ?? null,
11501
+ tap_count: result.tap_count ?? null,
11502
+ max_taps: result.max_taps ?? result.max_calls ?? null,
11503
+ interval_ms: result.interval_ms ?? null,
11504
+ timeout_ms: result.timeout_ms ?? null,
11505
+ reason: result.reason || result.error || null,
11506
+ }));
11507
+ }
11454
11508
  function profileSetupKeyboardReceipts(results) {
11455
11509
  return (results || [])
11456
11510
  .filter((result) => result && ["press", "key_down", "key_up"].includes(profileSetupResultAction(result)))
@@ -11653,6 +11707,11 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewp
11653
11707
  const sampledDragReceipts = sampleProfileSetupSummaryItems(dragReceipts, 8);
11654
11708
  const tapReceipts = profileSetupTapReceipts(results);
11655
11709
  const sampledTapReceipts = sampleProfileSetupSummaryItems(tapReceipts, 8);
11710
+ const tapUntilReceipts = profileSetupTapUntilReceipts(results);
11711
+ const tapUntilTapCounts = tapUntilReceipts
11712
+ .map((result) => typeof result.tap_count === "number" && Number.isFinite(result.tap_count) ? result.tap_count : undefined)
11713
+ .filter((value) => value !== undefined);
11714
+ const sampledTapUntilReceipts = sampleProfileSetupSummaryItems(tapUntilReceipts, 8);
11656
11715
  const keyboardReceipts = profileSetupKeyboardReceipts(results);
11657
11716
  const sampledKeyboardReceipts = sampleProfileSetupSummaryItems(keyboardReceipts, 8);
11658
11717
  const canvasSignatureReceipts = profileSetupCanvasSignatureReceipts(results);
@@ -11735,6 +11794,10 @@ function profileSetupSummary(viewports, actionCount, expectedActionCountsByViewp
11735
11794
  tap_total: tapReceipts.length,
11736
11795
  tap_truncated: tapReceipts.length > sampledTapReceipts.length,
11737
11796
  tap: sampledTapReceipts,
11797
+ tap_until_total: tapUntilReceipts.length,
11798
+ tap_until_tap_total: tapUntilTapCounts.reduce((sum, value) => sum + value, 0),
11799
+ tap_until_truncated: tapUntilReceipts.length > sampledTapUntilReceipts.length,
11800
+ tap_until: sampledTapUntilReceipts,
11738
11801
  keyboard_total: keyboardReceipts.length,
11739
11802
  keyboard_truncated: keyboardReceipts.length > sampledKeyboardReceipts.length,
11740
11803
  keyboard: sampledKeyboardReceipts,
@@ -12681,6 +12744,108 @@ async function waitForAnyVisibleSelector(context, selector, timeout) {
12681
12744
  }
12682
12745
  throw new Error("No visible match for selector " + selector + ": " + lastReason);
12683
12746
  }
12747
+ async function dispatchSetupTapPoint(point, pointerType, durationMs) {
12748
+ if (pointerType === "touch" || pointerType === "pen") {
12749
+ const client = await page.context().newCDPSession(page);
12750
+ try {
12751
+ if (pointerType === "touch") {
12752
+ const touchPoint = {
12753
+ x: point.x,
12754
+ y: point.y,
12755
+ radiusX: 1,
12756
+ radiusY: 1,
12757
+ force: 1,
12758
+ id: 11,
12759
+ };
12760
+ await client.send("Input.dispatchTouchEvent", {
12761
+ type: "touchStart",
12762
+ touchPoints: [touchPoint],
12763
+ });
12764
+ if (durationMs) await page.waitForTimeout(durationMs);
12765
+ await client.send("Input.dispatchTouchEvent", {
12766
+ type: "touchEnd",
12767
+ touchPoints: [],
12768
+ });
12769
+ } else {
12770
+ await client.send("Input.dispatchMouseEvent", {
12771
+ type: "mousePressed",
12772
+ x: point.x,
12773
+ y: point.y,
12774
+ button: "left",
12775
+ buttons: 1,
12776
+ clickCount: 1,
12777
+ pointerType: "pen",
12778
+ });
12779
+ if (durationMs) await page.waitForTimeout(durationMs);
12780
+ await client.send("Input.dispatchMouseEvent", {
12781
+ type: "mouseReleased",
12782
+ x: point.x,
12783
+ y: point.y,
12784
+ button: "left",
12785
+ buttons: 0,
12786
+ clickCount: 1,
12787
+ pointerType: "pen",
12788
+ });
12789
+ }
12790
+ } finally {
12791
+ await client.detach().catch(() => {});
12792
+ }
12793
+ } else {
12794
+ await page.mouse.click(point.x, point.y);
12795
+ }
12796
+ }
12797
+ async function resolveSetupTapTarget(action, base, scope, timeout) {
12798
+ const locator = scope.context.locator(action.selector);
12799
+ const count = await locator.count();
12800
+ if (!count) return { result: { ...base, ...setupScopeEvidence(scope), reason: "selector_not_found", count } };
12801
+ const targetIndex = Number.isInteger(action.index) ? action.index : 0;
12802
+ if (targetIndex < 0 || targetIndex >= count) return { result: { ...base, ...setupScopeEvidence(scope), reason: "index_out_of_range", count, target_index: targetIndex } };
12803
+ const target = locator.nth(targetIndex);
12804
+ await target.waitFor({ state: "visible", timeout });
12805
+ const box = await target.boundingBox();
12806
+ if (!box) return { result: { ...base, ...setupScopeEvidence(scope), reason: "bounding_box_unavailable", count, target_index: targetIndex } };
12807
+ const fromX = setupFiniteNumber(action.from_x ?? action.fromX ?? action.x ?? action.click_x ?? action.clickX);
12808
+ const fromY = setupFiniteNumber(action.from_y ?? action.fromY ?? action.y ?? action.click_y ?? action.clickY);
12809
+ const hasTapPosition = fromX !== undefined || fromY !== undefined;
12810
+ if (hasTapPosition && (fromX === undefined || fromY === undefined)) return { result: { ...base, ...setupScopeEvidence(scope), reason: "missing_tap_coordinates", count, target_index: targetIndex } };
12811
+ const mode = String(action.coordinate_mode || action.coordinateMode || (hasTapPosition ? "pixels" : "ratio")).trim();
12812
+ if (hasTapPosition && mode === "ratio" && [fromX, fromY].some((value) => value < 0 || value > 1)) return { result: { ...base, ...setupScopeEvidence(scope), reason: "invalid_ratio_coordinates", count, target_index: targetIndex } };
12813
+ if (hasTapPosition && mode !== "ratio" && [fromX, fromY].some((value) => value < 0)) return { result: { ...base, ...setupScopeEvidence(scope), reason: "invalid_pixel_coordinates", count, target_index: targetIndex } };
12814
+ const coordinate = (value, size) => mode === "ratio" ? value * size : value;
12815
+ const localX = hasTapPosition && fromX !== undefined ? fromX : 0.5;
12816
+ const localY = hasTapPosition && fromY !== undefined ? fromY : 0.5;
12817
+ const point = {
12818
+ x: box.x + coordinate(localX, box.width),
12819
+ y: box.y + coordinate(localY, box.height),
12820
+ };
12821
+ const durationMs = setupNumber(action.duration_ms ?? action.durationMs, 0);
12822
+ const pointerType = String(action.pointer_type || action.pointerType || "touch").trim().toLowerCase();
12823
+ return {
12824
+ target: {
12825
+ count,
12826
+ targetIndex,
12827
+ point,
12828
+ mode,
12829
+ fromX,
12830
+ fromY,
12831
+ hasTapPosition,
12832
+ pointerType,
12833
+ durationMs,
12834
+ },
12835
+ };
12836
+ }
12837
+ function setupTapTargetEvidence(tapTarget) {
12838
+ return {
12839
+ count: tapTarget.count,
12840
+ target_index: tapTarget.targetIndex,
12841
+ coordinate_mode: tapTarget.hasTapPosition ? tapTarget.mode : undefined,
12842
+ x: tapTarget.hasTapPosition ? tapTarget.fromX : undefined,
12843
+ y: tapTarget.hasTapPosition ? tapTarget.fromY : undefined,
12844
+ pointer_type: tapTarget.pointerType,
12845
+ input_dispatch: tapTarget.pointerType === "touch" || tapTarget.pointerType === "pen" ? "cdp" : "playwright_mouse",
12846
+ duration_ms: tapTarget.durationMs || undefined,
12847
+ };
12848
+ }
12684
12849
  function setupHasOwn(action, key) {
12685
12850
  return Boolean(action) && Object.keys(action).includes(key);
12686
12851
  }
@@ -13283,91 +13448,108 @@ async function executeSetupAction(action, ordinal, viewport) {
13283
13448
  if (type === "tap") {
13284
13449
  const scope = await setupActionScope(action, timeout);
13285
13450
  if (!scope.ok) return setupScopeFailure(base, scope);
13286
- const locator = scope.context.locator(action.selector);
13287
- const count = await locator.count();
13288
- if (!count) return { ...base, ...setupScopeEvidence(scope), reason: "selector_not_found", count };
13289
- const targetIndex = Number.isInteger(action.index) ? action.index : 0;
13290
- if (targetIndex < 0 || targetIndex >= count) return { ...base, ...setupScopeEvidence(scope), reason: "index_out_of_range", count, target_index: targetIndex };
13291
- const target = locator.nth(targetIndex);
13292
- await target.waitFor({ state: "visible", timeout });
13293
- const box = await target.boundingBox();
13294
- if (!box) return { ...base, ...setupScopeEvidence(scope), reason: "bounding_box_unavailable", count, target_index: targetIndex };
13295
- const fromX = setupFiniteNumber(action.from_x ?? action.fromX ?? action.x ?? action.click_x ?? action.clickX);
13296
- const fromY = setupFiniteNumber(action.from_y ?? action.fromY ?? action.y ?? action.click_y ?? action.clickY);
13297
- const hasTapPosition = fromX !== undefined || fromY !== undefined;
13298
- if (hasTapPosition && (fromX === undefined || fromY === undefined)) return { ...base, ...setupScopeEvidence(scope), reason: "missing_tap_coordinates", count, target_index: targetIndex };
13299
- const mode = String(action.coordinate_mode || action.coordinateMode || (hasTapPosition ? "pixels" : "ratio")).trim();
13300
- if (hasTapPosition && mode === "ratio" && [fromX, fromY].some((value) => value < 0 || value > 1)) return { ...base, ...setupScopeEvidence(scope), reason: "invalid_ratio_coordinates", count, target_index: targetIndex };
13301
- if (hasTapPosition && mode !== "ratio" && [fromX, fromY].some((value) => value < 0)) return { ...base, ...setupScopeEvidence(scope), reason: "invalid_pixel_coordinates", count, target_index: targetIndex };
13302
- const coordinate = (value, size) => mode === "ratio" ? value * size : value;
13303
- const localX = hasTapPosition && fromX !== undefined ? fromX : 0.5;
13304
- const localY = hasTapPosition && fromY !== undefined ? fromY : 0.5;
13305
- const point = {
13306
- x: box.x + coordinate(localX, box.width),
13307
- y: box.y + coordinate(localY, box.height),
13451
+ const prepared = await resolveSetupTapTarget(action, base, scope, timeout);
13452
+ if (prepared.result) return prepared.result;
13453
+ await dispatchSetupTapPoint(prepared.target.point, prepared.target.pointerType, prepared.target.durationMs);
13454
+ return {
13455
+ ...base,
13456
+ ...setupScopeEvidence(scope),
13457
+ ok: true,
13458
+ ...setupTapTargetEvidence(prepared.target),
13308
13459
  };
13309
- const durationMs = setupNumber(action.duration_ms ?? action.durationMs, 0);
13310
- const pointerType = String(action.pointer_type || action.pointerType || "touch").trim().toLowerCase();
13311
- if (pointerType === "touch" || pointerType === "pen") {
13312
- const client = await page.context().newCDPSession(page);
13313
- try {
13314
- if (pointerType === "touch") {
13315
- const touchPoint = {
13316
- x: point.x,
13317
- y: point.y,
13318
- radiusX: 1,
13319
- radiusY: 1,
13320
- force: 1,
13321
- id: 11,
13322
- };
13323
- await client.send("Input.dispatchTouchEvent", {
13324
- type: "touchStart",
13325
- touchPoints: [touchPoint],
13326
- });
13327
- if (durationMs) await page.waitForTimeout(durationMs);
13328
- await client.send("Input.dispatchTouchEvent", {
13329
- type: "touchEnd",
13330
- touchPoints: [],
13331
- });
13332
- } else {
13333
- await client.send("Input.dispatchMouseEvent", {
13334
- type: "mousePressed",
13335
- x: point.x,
13336
- y: point.y,
13337
- button: "left",
13338
- buttons: 1,
13339
- clickCount: 1,
13340
- pointerType: "pen",
13341
- });
13342
- if (durationMs) await page.waitForTimeout(durationMs);
13343
- await client.send("Input.dispatchMouseEvent", {
13344
- type: "mouseReleased",
13345
- x: point.x,
13346
- y: point.y,
13347
- button: "left",
13348
- buttons: 0,
13349
- clickCount: 1,
13350
- pointerType: "pen",
13351
- });
13352
- }
13353
- } finally {
13354
- await client.detach().catch(() => {});
13460
+ }
13461
+ if (type === "tap_until") {
13462
+ const untilPath = String(action.until_path || action.untilPath || action.until_state_path || action.untilStatePath || action.until_window_path || action.untilWindowPath || action.until || "");
13463
+ const hasUntilExpected = setupHasOwn(action, "until_expected_value")
13464
+ || setupHasOwn(action, "untilExpectedValue")
13465
+ || setupHasOwn(action, "until_expected")
13466
+ || setupHasOwn(action, "untilExpected")
13467
+ || setupHasOwn(action, "until_value")
13468
+ || setupHasOwn(action, "untilValue")
13469
+ || setupHasOwn(action, "expected_value")
13470
+ || setupHasOwn(action, "expectedValue")
13471
+ || setupHasOwn(action, "expected");
13472
+ const untilExpected = setupHasOwn(action, "until_expected_value")
13473
+ ? action.until_expected_value
13474
+ : setupHasOwn(action, "untilExpectedValue")
13475
+ ? action.untilExpectedValue
13476
+ : setupHasOwn(action, "until_expected")
13477
+ ? action.until_expected
13478
+ : setupHasOwn(action, "untilExpected")
13479
+ ? action.untilExpected
13480
+ : setupHasOwn(action, "until_value")
13481
+ ? action.until_value
13482
+ : setupHasOwn(action, "untilValue")
13483
+ ? action.untilValue
13484
+ : setupHasOwn(action, "expected_value")
13485
+ ? action.expected_value
13486
+ : setupHasOwn(action, "expectedValue")
13487
+ ? action.expectedValue
13488
+ : action.expected;
13489
+ if (!untilPath) return { ...base, reason: "missing_until_path" };
13490
+ if (!hasUntilExpected) return { ...base, until_path: untilPath, reason: "missing_until_expected_value" };
13491
+ const maxTaps = Math.min(100, Math.max(1, Math.floor(setupNumber(action.max_taps ?? action.maxTaps ?? action.tap_limit ?? action.tapLimit ?? action.max_calls ?? action.maxCalls ?? action.max_attempts ?? action.maxAttempts ?? action.attempts, 1) || 1)));
13492
+ const intervalMs = Math.min(5000, Math.max(0, Math.floor(setupNumber(action.interval_ms ?? action.intervalMs ?? action.poll_ms ?? action.pollMs ?? action.tap_interval_ms ?? action.tapIntervalMs, 100) || 0)));
13493
+ const scope = await setupActionScope(action, timeout);
13494
+ if (!scope.ok) return setupScopeFailure(base, scope);
13495
+ const prepared = await resolveSetupTapTarget(action, base, scope, timeout);
13496
+ if (prepared.result) return prepared.result;
13497
+ const startedAt = Date.now();
13498
+ let tapCount = 0;
13499
+ let lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
13500
+ const targetEvidence = setupTapTargetEvidence(prepared.target);
13501
+ if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
13502
+ return {
13503
+ ...base,
13504
+ ...setupScopeEvidence(scope),
13505
+ ok: true,
13506
+ ...targetEvidence,
13507
+ until_path: untilPath,
13508
+ until_value: setupJsonValue(lastPredicateResult.value),
13509
+ until_expected_value: setupJsonValue(untilExpected),
13510
+ tap_count: tapCount,
13511
+ max_taps: maxTaps,
13512
+ max_calls: maxTaps,
13513
+ interval_ms: intervalMs,
13514
+ timeout_ms: timeout,
13515
+ };
13516
+ }
13517
+ while (tapCount < maxTaps && Date.now() - startedAt <= timeout) {
13518
+ await dispatchSetupTapPoint(prepared.target.point, prepared.target.pointerType, prepared.target.durationMs);
13519
+ tapCount += 1;
13520
+ lastPredicateResult = await setupReadWindowValue(scope.context, untilPath);
13521
+ if (lastPredicateResult.ok && setupValuesEqual(lastPredicateResult.value, untilExpected)) {
13522
+ return {
13523
+ ...base,
13524
+ ...setupScopeEvidence(scope),
13525
+ ok: true,
13526
+ ...targetEvidence,
13527
+ until_path: untilPath,
13528
+ until_value: setupJsonValue(lastPredicateResult.value),
13529
+ until_expected_value: setupJsonValue(untilExpected),
13530
+ tap_count: tapCount,
13531
+ max_taps: maxTaps,
13532
+ max_calls: maxTaps,
13533
+ interval_ms: intervalMs,
13534
+ timeout_ms: timeout,
13535
+ };
13355
13536
  }
13356
- } else {
13357
- await page.mouse.click(point.x, point.y);
13537
+ if (tapCount < maxTaps && intervalMs) await page.waitForTimeout(intervalMs);
13358
13538
  }
13359
13539
  return {
13360
13540
  ...base,
13361
13541
  ...setupScopeEvidence(scope),
13362
- ok: true,
13363
- count,
13364
- target_index: targetIndex,
13365
- coordinate_mode: hasTapPosition ? mode : undefined,
13366
- x: hasTapPosition ? fromX : undefined,
13367
- y: hasTapPosition ? fromY : undefined,
13368
- pointer_type: pointerType,
13369
- input_dispatch: pointerType === "touch" || pointerType === "pen" ? "cdp" : "playwright_mouse",
13370
- duration_ms: durationMs || undefined,
13542
+ ...targetEvidence,
13543
+ until_path: untilPath,
13544
+ until_value: setupJsonValue(lastPredicateResult?.value),
13545
+ until_expected_value: setupJsonValue(untilExpected),
13546
+ tap_count: tapCount,
13547
+ max_taps: maxTaps,
13548
+ max_calls: maxTaps,
13549
+ interval_ms: intervalMs,
13550
+ timeout_ms: timeout,
13551
+ reason: Date.now() - startedAt > timeout ? "timeout" : "until_condition_not_met",
13552
+ missing_part: lastPredicateResult?.missing_part || undefined,
13371
13553
  };
13372
13554
  }
13373
13555
  if (type === "drag") {
@@ -16799,10 +16981,11 @@ function profilePackReceiptStatus(result, metadata, receipt) {
16799
16981
  clickFallbackTapKeys.add(`${ordinal === void 0 ? `idx:${index}` : `ord:${ordinal}`}:${frameSelector}:${selector}`);
16800
16982
  });
16801
16983
  const clickFallbackTapCount = clickFallbackTapKeys.size;
16802
- const visibleUiActionCount = clickCount + profileSetupReceiptTotal(setupViewports, "tap");
16984
+ const tapUntilCount = profileSetupReceiptTotal(setupViewports, "tap_until");
16985
+ const visibleUiActionCount = clickCount + profileSetupReceiptTotal(setupViewports, "tap") + tapUntilCount;
16803
16986
  const setupFailureCount = profileSetupFailureCount(setupViewports);
16804
16987
  const setupObstructionCount = profileSetupObstructionCount(setupViewports);
16805
- const inputDispatchCount = profileSetupReceiptTotal(setupViewports, "drag") + profileSetupReceiptTotal(setupViewports, "tap") + profileSetupReceiptTotal(setupViewports, "press") + profileSetupReceiptTotal(setupViewports, "keyboard_sequence");
16988
+ const inputDispatchCount = profileSetupReceiptTotal(setupViewports, "drag") + profileSetupReceiptTotal(setupViewports, "tap") + tapUntilCount + profileSetupReceiptTotal(setupViewports, "press") + profileSetupReceiptTotal(setupViewports, "keyboard_sequence");
16806
16989
  const canvasReceipts = setupViewports.flatMap((viewport) => setupReceiptArray(viewport, "canvas_signature"));
16807
16990
  const hasCanvasChange = canvasReceipts.some((item) => item.ok !== false && item.changed === true);
16808
16991
  const canvasSignatureHashes = canvasReceipts.filter((item) => item.ok !== false).map((item) => cliString(item.hash)).filter(Boolean);
@@ -16869,6 +17052,13 @@ function profilePackReceiptStatus(result, metadata, receipt) {
16869
17052
  "click fallback tap evidence missing"
16870
17053
  );
16871
17054
  }
17055
+ if (text.includes("tap_until") || text.includes("tap until") || text.includes("tap-until")) {
17056
+ return profileReceiptSignalStatus(
17057
+ tapUntilCount > 0,
17058
+ `tap_until receipt present (${tapUntilCount})`,
17059
+ "tap_until receipt missing"
17060
+ );
17061
+ }
16872
17062
  if (text.includes("active") && (text.includes("route-local") || text.includes("route local")) && (text.includes("proof helper") || text.includes("proof api") || text.includes("proof state"))) {
16873
17063
  return profileReceiptSignalStatus(
16874
17064
  hasActiveRouteLocalProofReceipt,
@@ -17304,6 +17494,7 @@ function setupNaturalInputSummaryMarkdown(viewports) {
17304
17494
  const inputReceipts = [
17305
17495
  ...setupReceiptArray(viewport, "drag").map((receipt) => ({ kind: "drag", receipt })),
17306
17496
  ...setupReceiptArray(viewport, "tap").map((receipt) => ({ kind: "tap", receipt })),
17497
+ ...setupReceiptArray(viewport, "tap_until").map((receipt) => ({ kind: "tap_until", receipt })),
17307
17498
  ...setupReceiptArray(viewport, "press").map((receipt) => ({ kind: "press", receipt }))
17308
17499
  ].filter(({ receipt }) => receipt.ok !== false);
17309
17500
  if (!inputReceipts.length) continue;
@@ -17698,6 +17889,8 @@ function profileSetupSummaryMarkdown(result) {
17698
17889
  const rangeValueTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.set_range_value_total) || 0), 0);
17699
17890
  const dragTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.drag_total) || 0), 0);
17700
17891
  const tapTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.tap_total) || 0), 0);
17892
+ const tapUntilTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.tap_until_total) || 0), 0);
17893
+ const tapUntilTapTotal = viewports.reduce((sum, viewport) => sum + (cliFiniteNumber(viewport.tap_until_tap_total) || 0), 0);
17701
17894
  const keyboardTotal = viewports.reduce((sum, viewport) => {
17702
17895
  const total = cliFiniteNumber(viewport.keyboard_total);
17703
17896
  return sum + (total === void 0 ? cliFiniteNumber(viewport.press_total) || 0 : total);
@@ -17737,6 +17930,9 @@ function profileSetupSummaryMarkdown(result) {
17737
17930
  if (tapTotal) {
17738
17931
  lines.push(`- tap: ${tapTotal} action(s)`);
17739
17932
  }
17933
+ if (tapUntilTotal) {
17934
+ lines.push(`- tap_until: ${tapUntilTotal} action(s), tap_count total ${tapUntilTapTotal}`);
17935
+ }
17740
17936
  if (keyboardTotal) {
17741
17937
  lines.push(`- keyboard: ${keyboardTotal} action(s)`);
17742
17938
  }
@@ -17764,10 +17960,12 @@ function profileSetupSummaryMarkdown(result) {
17764
17960
  const rangeValueActions = cliFiniteNumber(viewport.set_range_value_total) || 0;
17765
17961
  const dragActions = cliFiniteNumber(viewport.drag_total) || 0;
17766
17962
  const tapActions = cliFiniteNumber(viewport.tap_total) || 0;
17963
+ const tapUntilActions = cliFiniteNumber(viewport.tap_until_total) || 0;
17964
+ const tapUntilTaps = cliFiniteNumber(viewport.tap_until_tap_total) || 0;
17767
17965
  const keyboardActions = cliFiniteNumber(viewport.keyboard_total) ?? cliFiniteNumber(viewport.press_total) ?? 0;
17768
17966
  const canvasSignatureActions = cliFiniteNumber(viewport.canvas_signature_total) || 0;
17769
17967
  const observedPath = cliString(viewport.observed_path);
17770
- lines.push(`- ${name}: ${ok}, ${resultCount} result(s), ${screenshotCount} setup screenshot(s), ${clicked} click(s)${clickSequenceCount ? `, ${clickSequenceCount} click sequence(s)` : ""}${clickCountActions ? `, ${clickCountActions} click_count action(s)` : ""}${rangeValueActions ? `, ${rangeValueActions} set_range_value action(s)` : ""}${dragActions ? `, ${dragActions} drag action(s)` : ""}${tapActions ? `, ${tapActions} tap action(s)` : ""}${keyboardActions ? `, ${keyboardActions} keyboard action(s)` : ""}${canvasSignatureActions ? `, ${canvasSignatureActions} canvas_signature action(s)` : ""}${deterministicRuntimeActions ? `, ${deterministicRuntimeActions} deterministic_runtime action(s)` : ""}${windowCallActions ? `, ${windowCallActions} window_call action(s), ${windowCallStored} stored return(s), ${windowCallCaptured} captured return(s)` : ""}${windowEvalActions ? `, ${windowEvalActions} window_eval action(s), ${windowEvalStored} stored return(s), ${windowEvalCaptured} captured return(s)` : ""}${windowCallUntilActions ? `, ${windowCallUntilActions} window_call_until action(s), ${windowCallUntilCalls} call(s)` : ""}${observedPath ? `, path ${observedPath}` : ""}`);
17968
+ lines.push(`- ${name}: ${ok}, ${resultCount} result(s), ${screenshotCount} setup screenshot(s), ${clicked} click(s)${clickSequenceCount ? `, ${clickSequenceCount} click sequence(s)` : ""}${clickCountActions ? `, ${clickCountActions} click_count action(s)` : ""}${rangeValueActions ? `, ${rangeValueActions} set_range_value action(s)` : ""}${dragActions ? `, ${dragActions} drag action(s)` : ""}${tapActions ? `, ${tapActions} tap action(s)` : ""}${tapUntilActions ? `, ${tapUntilActions} tap_until action(s), ${tapUntilTaps} tap(s)` : ""}${keyboardActions ? `, ${keyboardActions} keyboard action(s)` : ""}${canvasSignatureActions ? `, ${canvasSignatureActions} canvas_signature action(s)` : ""}${deterministicRuntimeActions ? `, ${deterministicRuntimeActions} deterministic_runtime action(s)` : ""}${windowCallActions ? `, ${windowCallActions} window_call action(s), ${windowCallStored} stored return(s), ${windowCallCaptured} captured return(s)` : ""}${windowEvalActions ? `, ${windowEvalActions} window_eval action(s), ${windowEvalStored} stored return(s), ${windowEvalCaptured} captured return(s)` : ""}${windowCallUntilActions ? `, ${windowCallUntilActions} window_call_until action(s), ${windowCallUntilCalls} call(s)` : ""}${observedPath ? `, path ${observedPath}` : ""}`);
17771
17969
  }
17772
17970
  const clickSequenceGroups = viewports.map((viewport) => {
17773
17971
  const name = cliString(viewport.name) || "viewport";
@@ -17834,6 +18032,33 @@ function profileSetupSummaryMarkdown(result) {
17834
18032
  lines.push(`- ${name} tap: ${ok}, ${markdownInlineCode(selector)}${pointerType ? ` ${markdownInlineCode(pointerType)}` : ""}${inputDispatch ? ` via ${markdownInlineCode(inputDispatch)}` : ""}${coordinateText}${durationMs === void 0 ? "" : `, duration ${durationMs}ms`}${reason ? `, reason ${markdownInlineCode(reason, 100)}` : ""}`);
17835
18033
  }
17836
18034
  if (tapDetails.length > sampledTapDetails.length) lines.push(`- ${tapDetails.length - sampledTapDetails.length} additional tap receipt(s) omitted.`);
18035
+ const tapUntilGroups = viewports.map((viewport) => {
18036
+ const name = cliString(viewport.name) || "viewport";
18037
+ const receipts = Array.isArray(viewport.tap_until) ? viewport.tap_until.map(cliRecord).filter((item) => Boolean(item)) : [];
18038
+ return receipts.map((receipt) => ({ name, receipt }));
18039
+ });
18040
+ const tapUntilDetails = tapUntilGroups.flat();
18041
+ const sampledTapUntilDetails = balancedSetupReceiptDetails(tapUntilGroups, 12);
18042
+ for (const { name, receipt } of sampledTapUntilDetails) {
18043
+ const selector = cliString(receipt.selector) || "target";
18044
+ const pointerType = cliString(receipt.pointer_type);
18045
+ const inputDispatch = cliString(receipt.input_dispatch);
18046
+ const coordinateMode = cliString(receipt.coordinate_mode);
18047
+ const x = cliValueLabel(receipt.x);
18048
+ const y = cliValueLabel(receipt.y);
18049
+ const durationMs = cliFiniteNumber(receipt.duration_ms);
18050
+ const untilPath = cliString(receipt.until_path) || "until_path";
18051
+ const expected = cliValueLabel(receipt.until_expected_value);
18052
+ const actual = cliValueLabel(receipt.until_value);
18053
+ const tapCount = cliFiniteNumber(receipt.tap_count);
18054
+ const maxTaps = cliFiniteNumber(receipt.max_taps) ?? cliFiniteNumber(receipt.max_calls);
18055
+ const ok = receipt.ok === false ? "failed" : "ok";
18056
+ const reason = cliString(receipt.reason);
18057
+ const coordinateText = x && y ? `, ${coordinateMode ? `${coordinateMode} ` : ""}${markdownInlineCode(`${x},${y}`)}` : "";
18058
+ const tapText = tapCount === void 0 ? "" : ` in ${tapCount}${maxTaps === void 0 ? "" : `/${maxTaps}`} tap(s)`;
18059
+ lines.push(`- ${name} tap_until: ${ok}, ${markdownInlineCode(selector)}${pointerType ? ` ${markdownInlineCode(pointerType)}` : ""}${inputDispatch ? ` via ${markdownInlineCode(inputDispatch)}` : ""}${coordinateText}${durationMs === void 0 ? "" : `, duration ${durationMs}ms`} until ${markdownInlineCode(untilPath)}${expected === void 0 ? "" : ` == ${markdownInlineCode(expected, 80)}`}${tapText}${actual === void 0 ? "" : `, observed ${markdownInlineCode(actual, 80)}`}${reason ? `, reason ${markdownInlineCode(reason, 100)}` : ""}`);
18060
+ }
18061
+ if (tapUntilDetails.length > sampledTapUntilDetails.length) lines.push(`- ${tapUntilDetails.length - sampledTapUntilDetails.length} additional tap_until receipt(s) omitted.`);
17837
18062
  const keyboardGroups = viewports.map((viewport) => {
17838
18063
  const name = cliString(viewport.name) || "viewport";
17839
18064
  const rawReceipts = Array.isArray(viewport.keyboard) ? viewport.keyboard : viewport.press;