Skip to content

perf(scroll-until): answer every candidate from one visibility index - #2596

Merged
thymikee merged 3 commits into
mainfrom
perf/scroll-until-shared-visibility-index
Sep 14, 2026
Merged

thymikee merged 3 commits into
mainfrom
perf/scroll-until-shared-visibility-index

Conversation

@thymikee

@thymikee thymikee commented Sep 14, 2026

Copy link
Copy Markdown
Member

Summary

evaluateIsPredicate derived its visibility index from the nodes it was handed, so one scroll --until pass rebuilt the node map and viewport-rect scan once per candidate its selector matched. It now takes the capture's SnapshotVisibility and holds no tree of its own: the caller that owns the capture owns the index, which is #1970's rule that N candidates share one whole-tree pass. The --until pass builds one index and asks every candidate of the selector against it.

No behavior change for is <predicate> <selector> or scroll <dir> --until <selector>. @agent-device/selectors is private, so no CLI, MCP, help or wire surface moves. 6 files.

Closes #2528, correcting its premise: the wait row is first-match, so it resolves through the existence-only scan and never builds the index the issue pointed at. Nothing was left to thread on this path; is (readUnique) is where two indexes meet.

Validation

pnpm check:affected --run passes at bd319ae7: format, lint, typecheck, layering, check:fallow --base origin/main, build, and 568 related test files / 4129 tests including provider integration. The first run failed only in android-lifecycle.test.ts frame-action correlation; that file passes alone and on the rerun, at load average 40+ on 12 cores.

Evidence: the probe test failed on unmodified predicates.ts (TypeError … reading 'map'); the two twin tests were green on unmodified code first; and a pass materializes one visibility index for the candidates it asks counts index builds through the contracts facade — 1 for a selector matching two rows, 2 and red when that build moves back inside the candidate loop.

Device lanes and Coverage stay GitHub-authoritative for this diff.

`evaluateIsPredicate` derived its visibility index from the `nodes` it was
given, so `scroll --until` rebuilt the node map and viewport-rect scan once per
candidate a selector matched. It now takes the capture's `SnapshotVisibility`
and has no tree of its own, which is the #1970 rule that N candidates share one
whole-tree pass: the caller that owns the capture owns the index.

The `--until` pass builds that index only once a candidate exists, so a pass
whose selector matches nothing indexes nothing. The pipeline still holds its own
index while ranking ambiguous matches; sharing that one means widening
`SelectorPipelineOutcome`, which is the capture-kit move and not this.
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 4.53 MB 4.53 MB -1.8 kB
Package (unpacked) 4.53 MB 4.53 MB -1.8 kB
Package (download) 1.34 MB 1.34 MB -636 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.5 ms 26.6 ms -0.9 ms
CLI --help 76.7 ms 78.2 ms +1.6 ms

…s on

`visibility` is not "the array `node` came from": `findSnapshotAncestor` resolves `parentIndex`
positionally, so what the predicate needs is the index of the tree `node` belongs to, in the same
`index`/`parentIndex` space. Its doc says that now.

`createSnapshotVisibility` materializes nothing until consulted, so the no-match early return guarded
a cost that does not exist; it is gone. The probe test claims only what a probe can see — the caller's
index is the one consulted — keeps `snapshot-visibility.test.ts`'s counter names instead of a parallel
vocabulary, and drops the fallback counter this fixture cannot move. The twin test's docstring says
what its counts prove.
@thymikee

Copy link
Copy Markdown
Member Author

The shared index gives the same verdicts at 3dbf9dc. createSnapshotVisibility depends only on the nodes of the current pass, decideUntilPass builds it from each new capture, and evaluateIsPredicate has no callers outside this repo.

The tests do not lock in the change, though. If createSnapshotVisibility(nodes) in src/daemon/scroll-until.ts moves back inside the candidate loop, every test still passes: the twin tests check only verdicts, and the probe test in predicates.test.ts failed on the old code only because of the signature. Please add a scroll-until test with two matching candidates that counts index builds in one pass and expects one, or say in the PR that this shape does not need a test.

The failing Smoke Tests job is likely unrelated. The failure is the native runner test testAlertCannotProveAnIdenticalReplacementAndDoesNotActivateIt, this diff does not touch the Apple runner, and a sibling alert test failed the same way on #2592 today. There are no conflicts.

Verdicts cannot tell one index from one per candidate, so the shape #2528 changed was unproven: moving
`createSnapshotVisibility` back inside the candidate loop leaves every earlier test green. A pass now
counts the indexes built through the contracts facade and expects one for a selector that matched two
rows — two when the build moves back inside the loop.
@thymikee

Copy link
Copy Markdown
Member Author

Pushed bd319ae7 with the counting test, since the shape is what the child is about and prose would have rotted.

a pass materializes one visibility index for the candidates it asks wraps createSnapshotVisibility on the contracts facade (the vi.hoisted + importOriginal idiom platform-runtime-app-log-process.test.ts uses), runs one pass over twinTree(-400, 200) — two candidates, verdict visible on the second — and expects one build. Red proof: moving createSnapshotVisibility back inside the .some() makes it read 2 and fail; the twin verdict tests stay green either way, exactly as you found. 1 is also the true baseline: wait is first-match and occlusion: 'ignore', and the pass returns before the edge analyzer, so nothing else indexes that tree.

Thanks for the alert-runner triage: agrees with the log. RunnerTests+AlertObservationTests.swift:75 fails on First actions: 0; replacement actions: 0, one of four alert tests in that job, and no Swift or Apple path is in this diff.

pnpm check:affected --run green at bd319ae7 — format, lint, typecheck, layering, check:fallow --base origin/main, build, 568 related files / 4129 tests.

@thymikee

Copy link
Copy Markdown
Member Author

The counting test at bd319ae locks in the change. It wraps the same @agent-device/contracts/snapshot import that scroll-until.ts uses, and twinTree(-400, 200) makes .some() reach the second candidate, so a build inside the loop would count 2 and fail. No code findings remain, CI is green, and there are no conflicts.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 14, 2026
@thymikee
thymikee merged commit ad9b906 into main Sep 14, 2026
18 checks passed
@thymikee
thymikee deleted the perf/scroll-until-shared-visibility-index branch September 14, 2026 16:29
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-14 16:29 UTC

thymikee added a commit that referenced this pull request Sep 14, 2026
…tle-on-exit-2522

* origin/main:
  perf(scroll-until): answer every candidate from one visibility index (#2596)
  refactor(daemon): resolve device interactors through a composed capability (#2593)
  refactor(daemon): stop the client value-importing daemon internals (#2559) (#2594)
  refactor(shell-quote): one implementation, reached through the runner host port (#2595)
  docs(agents): delete the file-size tiers nobody enforces (#2597)
  fix(daemon): key the implicit session by workspace and platform (#2587)
  refactor(cli-schema): orient the schema layer above commands (#2543 step 3) (#2590)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(scroll-until): build the snapshot visibility index once per pass

1 participant