Repository navigation
Conversation
Size Report
Startup median (7 runs, lower is better):
|
There was a problem hiding this comment.
All reported issues were addressed across 9 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
6ad9bf3 to
9bf62c7
Compare
|
The settle change looks correct at 6ad9bf3, and CI is green, including Smoke Tests, with no conflicts. Before merge, please decide who owns the quiet-window fact and fix the 300 ms scope text so it names the simulator snapshot bridge. This PR is also stacked on #3512, which should land first. Not blocking, and you can take or leave these: (1) the window is keyed on the baseline's producer at https://github.com/callstack/agent-device/blob/6ad9bf3/src/commands/interaction/runtime/settle.ts#L175, but the loop compares the loop's own captures. The baseline can come from an earlier stored snapshot, and the bridge can fall back to the Apple runner mid-session. A bridge baseline followed by runner captures then gets 300 ms, and a runner baseline followed by bridge captures misses the speedup. The rule should be that the default window follows the source of the captures being compared. (2) Help, grammar and client docs say 300 ms applies to an iOS simulator or healthy iOS interactive tree (https://github.com/callstack/agent-device/blob/6ad9bf3/packages/command-registry/src/flag-definitions-action.ts#L214), but the code applies it only to the simulator snapshot bridge. A simulator capture served by the Apple runner keeps 500 ms. Deriving that text from the same constant would keep it honest. (3) The roughly 17-line benchmark comment on SIMULATOR_BRIDGE_SETTLE_QUIET_MS could shrink to two lines, with the measurements moved to docs/evidence. (4) The two new scroll --settle cases in generic-settle.test.ts use the real settle clock, about 900 ms of sleeps. An injected clock would match settle-default-quiet.test.ts. Could settleAfterAction pass quietMs as optional, so that runStableCaptureLoop resolves the default from the first capture's provenance? The 300 ms value would sit beside the producer table in ios-snapshot-acquisition.ts, or be exported from platform-apple, instead of a literal in shared runtime. That would remove SurfaceScopedNodes.captureSource, both writers and the generic-route threading, and the loop could be tested directly. One owner would cover both routes and follow the source actually read, including after runner fallback. pollMs would then be derived after the first capture, as it already is from quietMs. Nothing blocks this, though the quietMs and pollMs setup would become lazy. The open inline threads on the 300 ms wording at #3522 (comment), #3522 (comment) and #3522 (comment), and on the baseline-keyed window at #3522 (comment), still stand. The two lower-priority threads on the benchmark comment and the real-clock tests (#3522 (comment) and #3522 (comment)) also still apply. The first two are marked resolved, but the text is unchanged at this commit. I did not rerun the live iPhone 17 A/B or the affected-checks run, so the PR body's measurements are unverified here. I also did not exercise a mid-loop fallback to the runner. Point 1 comes from reading the fallback paths in snapshot-route.ts. This review covers 6ad9bf3. The branch has since moved to 9bf62c7, a rebase onto the updated #3512 that only renames a test in the base. I will confirm the review against the new head on the next pass. |
There was a problem hiding this comment.
All reported issues were addressed across 10 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
70e3e8f to
356ebec
Compare
856999e to
2bcaa2b
Compare
thymikee
left a comment
There was a problem hiding this comment.
Fixed in 6f43f54e6, which the run predates — the block ran from 17 lines of benchmark data down to a rule statement, and no device, timing, or run count remains in the source:
/**
* The default quiet window a capture earns. The iOS simulator snapshot bridge answers well inside
* the general default, so holding the general window on that read spends a whole extra capture on a
* window its tree had already satisfied. Keyed on the producer, not the channel: the bridge and the
* Apple runner share the `xctest` channel and read at different costs, and only the bridge carries
* the measurement. Every other read — the runner's own tree read, the Android helper, a capture that
* declares no provenance — keeps {@link DEFAULT_STABLE_QUIET_MS}.
* (PR #3522 holds the interleaved before/after medians and the late-diff accuracy check.)
*/I kept the numbers in this PR's description rather than opening a file under docs/evidence/: that directory's existing entries are exact-head sweeps with raw artifacts, digests, and named corpus tags, and this is a six-sample manual A/B on one simulator. Pointing at the PR number does rot once this merges, so the pointer stays a parenthetical rather than a {@link} anyone is meant to resolve.
5e5842a to
88fea6f
Compare
|
The code in 88fea6f looks right to me, but I would like the latch commit covered by one live run before merge. The CI checks are green (14 reported, none failing), and there are no conflicts. The live simulator runs in the PR body predate the latch commit, so they do not show it working. Please run one live simulator settle that reaches a confirmed dismissal, then keeps polling on the 300 ms bridge window. The output should show the quiet window staying at 300 ms for the rest of the run. I read the code and the test fixtures by hand. I did not run the mutation checks, and I did not read every line of the fake-clock wiring in generic-settle.test.ts. Not blocking: Could the quiet window be one exhaustive Of the eight bot threads, all eight look fixed at this head, so you can resolve them: r4250825442, r4250825462, r4250825469, r4250825474, r4250825481, r4250825483, r4251997139 and r4251997145. Before merge, #3512 (layer 1) must land first, including its decision on the presented-surface fact, and this PR must then be retargeted. |
356ebec to
962b838
Compare
88fea6f to
7f9b305
Compare
962b838 to
6e22190
Compare
7f9b305 to
93e79cc
Compare
6e22190 to
bee9e1f
Compare
93e79cc to
b78640a
Compare
Gate —
|
|
This follow-up on 88fea6f covers b78640a. The earlier review items are fixed in the code, but one regression and the missing live run remain. Fixed: the quiet window now re-prices from each loop capture, the confirmation latch is present, and the doc and test comments are corrected. The earlier review asked for a live latch run, and none was posted for this head. The remaining problem is in broadTransitionConfirmation. It decides arming once and stores After that fix, please post one live iPhone simulator run of On the open inline threads, the latch on confirmedQuietMs is fixed at this head, but it depends on the arming change above. These threads are fixed at this head and can be resolved: simulator bridge naming, 300ms scope in client-gesture, re-pricing per capture, flag help naming the bridge, removed benchmark block, fakeClock in the settle test, and quietMs doc on every return. Smoke Tests is still running and no failure has been reported. This change touches the settle loop that the |
b78640a to
e8bcff3
Compare
…ays it `--settle` asked every capture the same 500 ms quiet window. The iOS simulator snapshot bridge answers well inside it, so the loop spends a whole extra capture holding a window the tree has already satisfied — and the window is paid twice per settled command: once as the settle's final window, then again as the next command's baseline capture. The loop now prices the window from the capture it performs rather than the one the action was performed against: an unrequested `--settle` resolves 300 ms when that read came from the simulator snapshot bridge, and 500 ms for the Apple runner's own tree read, the Android helper, and any capture that declares no provenance — only the bridge carries the measurement. It re-prices on every capture, so a mid-run fallback to a more expensive read widens the remaining wait, and it reports the window it ran on (`settle.quietMs`) so the response states which default it applied. Keying on the baseline's producer was the first cut and is wrong: the window governs the loop's own reads, and a stored ref frame or session tree can be several commands stale or served by another producer. Keying on the channel would be wrong too — the bridge and the Apple runner share `xctest` and read at different costs. `--settle-quiet` still outranks both, and `wait stable` keeps the general default of its own. Cadence follows the window in force, so the shorter default also samples on a grid that can measure it. The loop's window, quiet clock, and recovery budget moved to one owner each in the same pass, which is what makes the re-pricing a single line rather than a fourth piece of loop state. The trap this layer owes the layer under it is pinned by name: a 300 ms default sits under the 1.5 s confirmation floor, so the arming rule that layer removed must not come back. Mutation check: with `requestedQuietMs >= DEFAULT_STABLE_QUIET_MS` restored in the arming path, that case is the only one in the file that fails — i.e. the shorter default would have silently switched the #1831 guard off for every default settle the bridge answers.
e8bcff3 to
84f170e
Compare
|
|
The 300 ms default window is not safe yet, because one of the two problems from the earlier review at b78640a is still open at 84f170e. The thread fixes and the latch are in. Checks are still pending, and no conflicts are known. The arming decision is still made once, on the first capture. In stable-capture.ts, Could the arming question be split by input lifetime? Rule out once from the run-constant inputs, then check completeness and backend on each capture inside This change touches the device-facing iOS settle path, and the latch with per-capture re-pricing has not been run on a device. The live runs in the PR body predate the latch commit and used the earlier trigger. Once the fix above is in, please post one iPhone simulator Eight earlier inline threads from the other review are fixed at this commit and can be resolved: grammar description, client-gesture scope, per-capture price, flag description, doc block, injected clock, confirmedQuietMs latch and quietMs doc. The latch thread is fixed in form, but it only helps once arming succeeds, which is the first finding above. I read the base and head code paths and did not run the tests or reproduce the first finding. I have not seen on a device that a non-tiny modal dismissal gives a rootless or empty first capture; the base code's comment is the only source for that. I did not check the mutation results or the 302 ms pre-fix measurement. All 14 non-passing checks were still queued when I looked, and none had failed. The diff changes Before merge, please re-check per-capture arming on every capture, add the regression for a non-tiny baseline with a rootless first capture, and post the live default-window modal-dismissal run on that head. |
Part of #3470's follow-up work; does not close it. #3482 and #3512 have both merged, so this now targets
maindirectly and carries only its own 8 files.Summary
--settleasked every capture the same 500 ms quiet window. The iOS simulator snapshot bridge answers well inside that, so the loop spends a whole extra capture holding a window the tree has already satisfied — and the window is paid twice per settled command (the settle's final window, then again as the next command's baseline capture).The loop now prices the window from the capture it performs, not the one the action was performed against: an unrequested
--settleresolves 300 ms when that read came from the simulator snapshot bridge, and 500 ms for every other read — the Apple runner's own tree read, an Android capture from either producer (android-uiautomatorover ADB orappium-sourceover Appium), a webagent-browserread, an iOS tree from a provider acquisition, or one that declares no provenance at all — because only the bridge carries the measurement. It re-prices on every capture, so a mid-run fallback to a more expensive read widens the remaining wait, and it reports the window it ran on (settle.quietMs) so the response states which default it applied.Keying on the baseline's producer was the first cut here and is wrong: the window governs the loop's own reads, and a stored ref frame or session tree can be several commands stale or served by another producer. Keying on the channel would be wrong too — the bridge and the Apple runner share
xctestand read at different costs.--settle-quietstill outranks both, andwait stablekeeps the general default of its own.Evidence for 300 ms
iPhone 17 simulator, one run per window per rep, order alternating so host load falls on both arms alike:
Accuracy held:
diff snapshot -itwo seconds after the settled response found no further change for the text entry at either window (0/6 both), and one further change in three of six runs for the press at either window — that screen keeps animating after the press whichever window is asked for, so the shorter window changes the wait, not the verdict's accuracy. (These A/B runs, and the live runs below, were taken on the pre-split build that still carried the presented-surface trigger since lifted to #3570; both arms request their window explicitly, so the comparison is this layer's window arithmetic alone.)Validation
Tested at
84f170e31.settle-default-quiet.test.tspins the window per capture producer in both mismatch directions (bridge baseline + runner re-read widens to 500 ms; runner baseline + bridge re-read narrows to 300 ms) and pins flag precedence; mutation check, measured when the key moved (6f43f54e6) and unchanged in shape here: keying the rule back on the baseline fails three of those cases plus one daemon case.generic-settle.test.tspins the three default-window routes on an injected fake clock (a 25 ms poll, a 30 mssleep, deterministic elapsed arithmetic — no real-time budget races).The per-capture re-pricing briefly broke the layer beneath it:
requiredQuietMsreceived the freshly resolved default each poll instead of the previous requirement, so the 1.5 s dismissal floor survived only while each consecutive capture kept qualifying. The comparison refuses a node-less side, so one empty mid-animation projection dropped the floor and the next empty read settled the run — 302 ms after a dismissal that owed the 1.5 s floor. The floor is now latched once a capture proves the dismissal and maxed against the current window rather than replacing it (pinned bya dismissal confirmation is not dropped by a later empty projection; fails at 302 ms against the pre-fix head, passes against layer 1's source, which held the floor only by accumulating).Live on the same simulator after the rework: default
press --settlereportsquietMs: 300at 658–664 ms over two captures;--settle-quiet 700still wins at 1050–1076 ms; the layer-1 dismissal floor is intact (quietMs: 300withwaitedMs: 1875/1886, i.e. the 1500 ms floor on top of the narrower window);wait stableis unaffected. Two provenance notes on those runs. They predate the latch commit: they held the floor because their captures kept qualifying capture-to-capture, and the latch changes behaviour only where one stops qualifying mid-confirmation, which the unit case above covers. And they were taken on the pre-split build, which still carried the presented-surface trigger since lifted to #3570; at this head the trigger ismain's, which arms on a superset of transitions, so the floor these runs show can only hold at least as well here — the window arithmetic they measure (quietMs, flag precedence, capture counts) is this layer's and unchanged.Gate
pnpm check:affected --run --base origin/mainat84f170e31: all runnable checks passed (exit 0) — 514 related test files / 3,998 tests, Fallow clean on the 8 changed files — held under the machine-wide lock, started2026-10-13T10:56:14Z.format,lint,tsc -b,check:layering,buildandvitest relatedall pass; the1500→1500msunit fix in the MCP/Node field description is covered by the schema/parser/registry suites (47 files, 309 tests) with no text pin against it.Note on scope
An earlier revision of this stack carried, one layer down, a presented-surface arm that keyed on the baseline's node count — the defect found in #3512's review (#3512 (comment)), where a dismissal whose baseline is a full tree stops confirming. That arm is not in this stack anymore: #3512 is now the window decoupling alone and keeps
main's trigger, and the narrowing (with that rule and the measured route table) sits on top of this layer as the draft #3570, blocked by #3565. So this layer arms exactly wheremainarms, only at every requested window, and nothing here depends on how #3565 resolves the fact.