Skip to content

fix(flow): wait for iOS instrumentation before capturing a recorded tap - #723

Draft
hubgan wants to merge 5 commits into
fix/recorder-tap-replay-fidelityfrom
fix/recorder-ios-instrumentation-wait
Draft

fix(flow): wait for iOS instrumentation before capturing a recorded tap#723
hubgan wants to merge 5 commits into
fix/recorder-tap-replay-fidelityfrom
fix/recorder-ios-instrumentation-wait

Conversation

@hubgan

@hubgan hubgan commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #722.

The problem

Replay has a launch readiness gate. Recording had no counterpart.

A live restart-app returns before the injected dylib dials back, so a tap recorded right after one read the tree before the app had connected, found nothing, and silently kept coordinates — with only the generic capture warning to show for it. That is the first tap of a walkthrough, and the one most likely to be repeated across takes.

What changes

The recorder rides out that window before reading the tree, on the same budget replay uses: a cold start the replay gate would absorb, recording absorbs too.

  • With a leading launch, it polls the exact bundle's connection bit — the same synchronous check replay makes.
  • For a fragment, it polls until auto-targeting finds one connected, foreground-like app.

When the budget lapses it stops and lets the single tree read report whatever is really there, so the capture warning stays accurate rather than being replaced by a timeout.

Three cases that had to stay cheap or correct

An app started outside Argent can never connect during this recording. One exhausted probe is remembered per device and per recording session — otherwise a 20-tap walkthrough pays the full budget twenty times. A successful tree read, or a successful launch-app/restart-app, clears the entry, so instrumentation that becomes available later still gets picked up.

An Apple system app can never inject at all (library validation), so it is not polled.

The fragment path inspects app state over an RPC that can itself wedge. It is raced against the remaining budget, so the gate stays a hard cap rather than becoming 8 seconds between potentially multi-second calls.

Cancellation

A disconnect during the readiness poll now aborts the call. Previously it stopped the poll and then executed the tap anyway — moving the device after the caller had given up.

Cost

When the app was never Argent-launched, this adds one budget's worth of latency before the (accurate) capture warning. That is the deliberate trade: it beats silently downgrading a post-launch tap to coordinates.

@hubgan
hubgan force-pushed the fix/recorder-ios-instrumentation-wait branch from 4b59f84 to e88a71a Compare August 4, 2026 07:23
@hubgan
hubgan force-pushed the fix/recorder-ios-instrumentation-wait branch from 5926b40 to 32aacef Compare August 5, 2026 10:15
@hubgan
hubgan force-pushed the fix/recorder-ios-instrumentation-wait branch from 32aacef to 638aa11 Compare August 5, 2026 10:38
@hubgan
hubgan force-pushed the fix/recorder-ios-instrumentation-wait branch from 638aa11 to 7f882cd Compare August 6, 2026 09:58
@hubgan
hubgan force-pushed the fix/recorder-ios-instrumentation-wait branch 2 times, most recently from 64bdebe to f592c53 Compare August 10, 2026 08:47
hubgan added 3 commits August 10, 2026 10:55
Recording had no counterpart to replay's launch readiness gate. A live
`restart-app` returns before the injected dylib dials back, so a tap recorded
right after one read the tree before the app had connected, found nothing, and
silently kept coordinates — the very first tap of a walkthrough, and the one
most likely to be repeated.

The recorder now rides out that window before the tree read, on the same budget
replay uses: a cold start the replay gate would absorb, recording absorbs too.
With a leading `launch` it polls the exact bundle's connection bit, the same
check replay makes; a fragment polls until auto-targeting finds one connected,
foreground-like app.

Three cases had to stay cheap or correct:

- An app started OUTSIDE Argent can never connect during this recording, so one
  exhausted probe is remembered per device and session — otherwise a 20-tap
  walkthrough pays the full budget 20 times. A successful tree read, or a
  successful launch/restart, clears it so instrumentation arriving later still
  works.
- An Apple system app can never inject at all, so it is not polled.
- The fragment path inspects app state over an RPC that can itself wedge, so it
  is raced against the remaining budget — the gate stays a hard cap rather than
  becoming 8 seconds BETWEEN multi-second calls.

Cancellation now propagates: a disconnect during the poll aborts the call
instead of merely stopping the poll and then executing the tap anyway.
…ding one

The recorder's iOS readiness gate resolved which launch to wait on from the
LEADING launch step, but fetchFlowTree reads the frontmost connected app. A
recording that relaunches a second app mid-flow (restart A … restart B … tap)
polled A's connection bit — still set, since restart-app B never touched A —
and returned ready at once, reading B's tree before its dylib had dialed back
and downgrading the tap to coordinates: the exact failure the gate exists to
prevent. Replay gates each launch step on its own bundle.

Resolve the launch app from the most recent launch instead; it reduces to the
leading launch for the common single-launch flow. Add tests covering the
exact-bundle isConnected ready path (previously uncovered) and the mid-flow
app switch.
…de-out

The two exact-bundle tests ("polls the leading launch's exact bundle" and
"gates on the most recent launch") set the tree unconditionally, so their
message/step assertions passed even with the gate's wait removed — only the
isConnected keying was load-bearing. Throw until connected (as the auto-target
ride-out test already does) so a gate that reads the tree before the app
connects downgrades to coordinates and the assertions catch it.

Also cover two reachable branches that had no test: launch-app's launched:true
clearing the readiness miss-cache (symmetric with restart-app), and the
auto-target settleWithin error result (a rejecting getAppState) keeping the
poll alive to the budget rather than aborting early.
@hubgan
hubgan force-pushed the fix/recorder-ios-instrumentation-wait branch from f592c53 to 690c5fe Compare August 10, 2026 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant