Commit Graph

23 Commits

Author SHA1 Message Date
Jakub Piasecki 36e42bbd73 test(flow): pin the converged conjunct on both crop mapper arms
Dropping `&& settled.converged` from either arm of the crop settle
mapper left the suite green: without it a crop snapshot whose settle
never converged reports clean on the skipped arm, and swaps the
timed-out note for an unavailable one on the pixel-dark arm — blaming
a missing backend for a screen that simply never stopped moving. Both
shapes are now driven through the real settle and frame resolution — a
churning hierarchy that never matches two fingerprints, and a dark
capture whose sticky verdict survives a restart — with the full reason
strings, read counts, and capture counts asserted, so each mutation
fails exactly its own test.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 71668af14e test(flow): pin the degraded write when no hierarchy read completes
The settle-timeout catch's timed-out half — the only report that a
snapshot was captured while the hierarchy source never answered — was
pinned by nothing: collapsing the catch to settled left the suite
green while an update-baselines run adopted a screen never proven
still under a bare, healthy-looking write. The new case hangs every
hierarchy read, drives the real settle to its typed timeout at exactly
the action deadline, and pins the full degraded reason, the elapsed
budget, the untouched capture backend, and the written file — leaving
both halves of the ternary independently load-bearing.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 0e1d7f11d4 test(flow): pin the stale-settled latch against pixel-dark retries
The latch's timed-out override and its outage catch were pinned, but
the in-loop guard covering the remaining pixel-dark shortfalls —
unavailable, and skipped without convergence — was held by nothing:
widening it to every verdict left the suite green while a snapshot
whose stillness was already proven picked up a spurious unavailable
note. The new case drives the real machinery through a stale-but-
settled first round and four completed pixel-dark freshness retries,
asserting the exact undegraded write plus the 15-read/6-capture shape;
the mutant fails on the suffix and the counts alike.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 2918707394 test(flow): pin the settled-pixels acceptance on the raw-coordinate path
Deleting the visual === 'settled' disjunct left the suite green: the
tests named for it asserted only ok and coordinates, both satisfied by
the terminal best-effort dispatch at deadline exhaustion. The existing
pair now pins the dispatch instant (the first settled round at the 5s
combined phase window, not the 7.5s deadline), and a new pair covers
the transition the clause exists for — a source that stops hanging and
starts failing after pixels already settled taps the literal point it
never needed the tree for, pinned by ok, dispatch time, and a read
count proving the failing source is never consulted.
2026-08-04 10:35:54 +02:00
Jakub Piasecki f028e24d7c test(flow): join the settle machine's timed-out verdict to its consumer
settleSnapshot keys its stale-settled override on the exact string the
pixel loop emits at budget exhaustion, but every snapshot test scripted
settleTree wholesale — relabelling the sleep-clamp arm to unavailable
left the suite green while a stale-settled update-baselines run wrote a
bare baseline with the degradation silently dropped. The new test
drives the real settleTree and real settleSnapshot end to end: a
stale-but-settled first round, a freshness retry whose animator burns
the pixel budget through the sleep-clamp arm, and the degraded write
asserted as the full reason string, pinned by read and capture counts.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 16f7bfaba9 fix(flow): stop marking the tree fresh after a probe consumed the budget
The pixel phase's not-attempted arm hardcoded treeFresh: true on the
grounds that zero time passed since the converged pair — false whenever
the awaited capability probe itself spent the budget: a cold iOS
runtime-kind lookup is a real simctl round trip, so the pair could be
~7s old while the settle reported it safe, and the verdict flipped from
honest to dishonest on whether that probe resolved just before or just
after the deadline.

The arm now compares the pair's convergence instant against the pixel
window: a window already spent at convergence means the probe answered
from cache and staleness is bounded by the final-read reserve, while a
window the probe consumed marks the tree unsafe — the same reasoning as
the probe-deadline sibling.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 95f7ad95b0 fix(flow): mark the carried tree unsafe when a mid-phase read hangs
A tree-phase read outliving the source read budget returned the carried
treeFresh: after one successful read, a hung second read handed back a
tree up to the full 7.5s budget old while reporting it safe for selector
coordinates. scroll-to — the one deadline-less settleTree consumer — was
the visible casualty: its stale-round skip never fired for the case it
was written for, so one 8s hierarchy read dispatched a swipe off the
stale tree and misread the scroll as ended.

The hung-read exit now returns treeFresh: false. That is unconditional
because no path reaches it with a recent successful read in hand: every
phase window is capped by the read budget, so a non-first poll only
launches with budget open, and a first read starting past the budget
holds no successful read and throws instead. The treeFresh docstring
gains the fourth staleness source.
2026-08-04 10:35:54 +02:00
Jakub Piasecki e1ad277ae2 fix(flow): bound pixel polling by the ordinary phase window
One combined settle on a screen with perpetual tree-invisible motion
polled captures to the caller's hard deadline minus the 250ms reserve,
consuming the entire action budget: waitForFrameResult got a single
resolution attempt instead of retrying across the window, a late retry
whose no-read window closed threw FlowTreeSettleTimeoutError out of the
step, and every directive on such a screen paid ~6s of added wall clock.

Polling for a pixel pair now stops at the ordinary phase window, the
same boundary tree polling already honors; the hard ceiling bounds only
work in flight when it closes — a first capture's first-frame-aware
budget plus the one warm capture completing its pair. After a
phase-bounded pixel timeout the mandatory revalidation read runs on the
ordinary source read budget instead of being cut to the reserve, and
waitForFrameResult treats a late retry's settle timeout as deadline
exhaustion (best-effort resolve from the last settled tree) rather than
a step error, matching the throw taxonomy.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 687b3c00c7 test(flow): pin the deadline-less combined settle's hard budget
Collapsing COMBINED_HARD_TIMEOUT_MS to the 3s tree window left the
suite green while cutting the slow-first-frame capture the constant's
pixel term exists for — scroll-to's round-0 settle, the one combined
settle with no caller deadline, silently degraded instead of proving
stillness. Drive a 6.25s first capture plus a matching warm capture
through the deadline-less settle and assert full convergence, and pin
the constant's composition directly.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 3a96a8e0df test(flow): pin the phase-deadline arm of the post-pixel read window
Collapsing the finalTreeDeadline ternary to the 250ms reserve left the
suite green while cutting every ordinary-latency post-pixel read short:
a 400ms uiautomator revalidation after a fast pixel phase came back
non-fresh, the verdict selector resolution refuses, wasting a round per
settle. Drive the everyday scenario and assert full convergence on
exactly three reads and two captures.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 35376c0813 fix(flow): grant deadline-less settles the shared action read budget
scroll-to is the only settleTree caller that supplies no action
deadline, so its tree reads fell back to the 5s combined-phase cap —
below the budgets the sources themselves get (getHierarchy 15s,
uiautomator up to 20s) — and a healthy 5.2s hierarchy read failed only
scroll-to steps as a fabricated settle timeout. The unowned fallback now
matches DEFAULT_ACTION_TIMEOUT_MS, putting the timeout cliff at the same
7.5s every deadline-owning caller already has.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 74b1f0c88b fix(flow): report a never-started pixel phase as skipped, not timed-out
When the tree pair converges inside the final-read reserve of the
caller's deadline, the first capture was never launched — no backend was
consulted and zero time passed — yet the settle reported visual
"timed-out", the verdict snapshots degrade on. Align the code with the
SettleResult contract: a phase that never ran returns the sticky visual
verdict (skipped by default) with the still-fresh converged tree, and no
revalidation read is spent. Budget exhaustion after a capture ran stays
an honest pixel timeout.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 37c9b0ec80 fix(flow): return a best-effort settle on a capability-probe deadline
A combined settle whose healthy tree read consumed the caller's whole
budget threw an untyped Error from the capability-probe deadline check —
outside the settle error taxonomy, hard-stopping steps whose tree had
just converged. The budget check now distinguishes a probe that was
never consulted (no time passed — the converged tree is returned fresh)
from a probe that was awaited and outlived the wait (returned non-fresh),
so raw-coordinate gestures dispatch best-effort and snapshots degrade
honestly instead of erroring.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 1d9f8eb300 fix(flow): classify tvOS pixel capture support 2026-08-04 10:35:54 +02:00
Jakub Piasecki 1788f857da fix(flow): preserve crop snapshot settle degradation 2026-08-04 10:35:54 +02:00
Jakub Piasecki 7b2507e982 test(flow): pin tree freshness during pixel settling 2026-08-04 10:35:54 +02:00
Jakub Piasecki ab5ed1b085 fix(flow): budget pixel settling per capture 2026-08-04 10:35:54 +02:00
Jakub Piasecki ae24be42f9 fix(flow): distinguish slow tree reads from outages 2026-08-04 10:35:54 +02:00
Jakub Piasecki 50071350dc test(flow): pin the stale scroll round's gesture suppression 2026-08-04 10:35:54 +02:00
Jakub Piasecki ff6b09eab9 fix(flow): stop degrading Vega snapshots over the absent pixel backend 2026-08-04 10:35:54 +02:00
Jakub Piasecki 9e3f43d289 test(flow): cover the transient-error arm of the post-pixel revalidation
The existing rejection tests fail every read (an outage, thrown before
the post-pixel stage) and the restart tests move the tree via a
successful differing read, so a describe blip landing exactly on the
revalidation read exercised an untested arm: re-seed with the stable
fingerprint and restart. Two tests now pin it by read/capture counts —
the seeded restart converges on one extra read (5 total), the pixel
phase re-runs after the restart (4 captures), the blip surfaces nowhere,
and the settle returns fully converged and fresh.
2026-08-04 10:35:54 +02:00
Jakub Piasecki e71af3720d fix(flow): make settling best-effort instead of a failure gate
The post-pixel tree revalidation gets as little as a 250ms reserve, which
one slow uiautomator/full-hierarchy read can exceed on a healthy source,
and review showed settle outcomes leaking into step failures. Settling
now stabilizes best-effort and never fails a step by itself; the only
settle-caused failure left is a genuine tree-source outage (every read
in the window failed):

- Snapshots retry a settled-but-stale settle for freshness on the shared
  action deadline (the planned cropOn selector will consume the tree),
  accept the stale-but-settled screen at exhaustion, and no longer
  refuse --update-baselines on a timed-out settle — the write proceeds
  with a degradation note instead of failing.
- Raw-coordinate tap/long-press accept a fresh tree OR settled pixels,
  retry to the action deadline, then dispatch anyway: literal
  coordinates depend on nothing the settle failed to prove.
- Selector resolution prefers fresh trees while budget remains and falls
  back to the last valid settled tree at exhaustion, so a slow freshness
  read cannot masquerade as "element not found".
- scroll-to skips a stale round instead of failing the step.

So callers can trust visual:"settled", settleTree downgrades it to
"skipped" on a tree-phase restart: the tree moving after the pixels
matched proves those pixels stale.
2026-08-04 10:35:54 +02:00
Jakub Piasecki 1db46c190a fix(flow): add a visual settle step 2026-08-04 10:35:54 +02:00