Skip to content

fix(scroll): take edge hints from a same-rect scrollable ancestor - #3784

Merged
thymikee merged 6 commits into
callstack:mainfrom
airowe:fix/scroll-edge-same-rect-ancestor
Oct 17, 2026
Merged

thymikee merged 6 commits into
callstack:mainfrom
airowe:fix/scroll-edge-same-rect-ancestor

Conversation

@airowe

@airowe airowe commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

scroll top on the Android Settings home screen reached the top, then failed with scroll_edge_no_progress. The nested recycler_view reports hiddenContentAbove even at the top. The outer main_content_scrollable_container, with the identical rect, reports the real position.

As agreed in #3737 (option 1), scroll edge selection now stands a same-rect scrollable ancestor in for the scroller nested in it, so the chosen container, its scope and its verdict all come from the ancestor. Selecting the ancestor (not only reading its hints) keeps the nested list inside the scoped recapture runScrollEdgePasses makes. An ancestor stands in only when it reports a vertical edge hint, so a same-rect horizontal pager never replaces the vertical list on its page. Both ancestor walks guard against cyclic parentIndex chains. Snapshot hints are unchanged. Three files: scroll-edge-state/selection.ts, a regression fixture trimmed from the live Settings trees, and its tests.

Closes #3737

Validation

  • pnpm check:affected --run --head f4c2c2406 passes (exactCommit=true): format, lint, typecheck, layering, fallow, build, vitest-related.
  • Live, Pixel_7_API_36 emulator (API 36), android-helper 0.21.29-dev, same steps as the issue, main at 92db564 vs this branch (rerun at f4c2c24):
Step main This branch
scroll down Scrolled down Scrolled down
scroll top scroll_edge_no_progress Scrolled to top with 1 up pass
scroll bottom Scrolled to bottom Scrolled to bottom
scroll top scroll_edge_no_progress Scrolled to top with 1 up pass
scroll top again — Already at top

Also live at f4c2c24: scroll top on a freshly opened screen reports Already at top.

🤖 Generated with Claude Code

On Android Settings, the RecyclerView nested in main_content_scrollable_container
offers ACTION_SCROLL_BACKWARD even at the top, so `scroll top` kept swiping a list
that could not move and failed with scroll_edge_no_progress. When a scroller has
the same rect as its nearest scrollable ancestor, the edge verdict now reads the
ancestor's hints. The snapshot's own per-node hints are unchanged.

Closes callstack#3737

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files

You’re at about 97% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread packages/capture-kit/src/snapshot/scroll-edge-state/selection.ts Outdated
Comment thread packages/capture-kit/src/snapshot/scroll-edge-state/selection.ts Outdated
airowe and others added 2 commits October 17, 2026 09:50
Container selection reads each node's own hints again, as on main; only the
final canScroll verdict consults the same-rect scrollable ancestor. The
ancestor walk tracks visited indexes so a cyclic parentIndex chain in a
malformed tree cannot hang edge analysis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scoping the ancestor's hints to the verdict alone let selection pick the nested
RecyclerView at the top; the loop then scoped its recapture to that subtree,
lost the ancestor, and fell back to the stale hint (scroll_edge_no_progress on
a second `scroll top`). Scroll edge selection now stands a same-rect scrollable
ancestor in for the scroller nested in it, so the chosen container, its scope
and its verdict all come from the ancestor. The visited-index guard stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

You’re at about 97% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread packages/capture-kit/src/snapshot/scroll-edge-state/selection.ts
findNearestScrollableAncestor followed parentIndex without a visited set, so a
targeted scroll on a malformed tree whose parent chain loops with no scrollable
ancestor could hang edge analysis. It now stops on a revisited index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee

Copy link
Copy Markdown
Member

The PR is ready at 3818a90. Not blocking: the same-rect test in selection.ts ignores scroll axis, so a horizontal scroller with the same rect as a nested vertical list could replace it (I found no real screen that does this; consider requiring the ancestor to scroll on the requested axis, or name the heuristic in the doc comment), and the new same-rect ancestor test file could be folded into the existing selection-level test file, or subtreeFor could move to the shared fixtures. Take or leave both.

The three cycle and selection threads from cubic-dev-ai are fixed at this head, so please resolve them: visited set in the ancestor walk, selection and verdict now agree, and visited set in the nearest-ancestor walk.

CI is green: 12 checks, none failing. There are no conflicts. Nothing else must happen before merge.

On evidence: the live Android Settings table in the PR body was run at 273e341, and 3818a90 only adds cycle guards, so I did not re-run it. Only the ADB path was exercised live, and I did not inspect any Appium or iOS tree for same-rect scroller pairs. I did not run the tests locally.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 17, 2026
…ubtree fixture

The doc comment now states that the same-rect check is geometric and ignores
scroll axis. subtreeFor moves to the shared scroll-edge-state fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

You’re at about 97% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread packages/capture-kit/src/snapshot/scroll-edge-state/selection.ts Outdated
Comment thread packages/capture-kit/src/snapshot/__tests__/scroll-edge-state-fixtures.ts Outdated
Comment thread packages/capture-kit/src/snapshot/__tests__/scroll-edge-state-fixtures.ts Outdated
Geometry carries no scroll axis, so a horizontal pager framing the same pixels
as a vertical list on its page could stand in for that list and hide its edge
hints. The snapshot emits vertical edge hints only for vertical scrollers, so an
ancestor now stands in only when it reports one. subtreeFor matches scopes and
reindexes nodes the way an Android scoped snapshot does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee
thymikee merged commit 6f91f64 into callstack:main Oct 17, 2026
12 checks passed
thymikee added a commit that referenced this pull request Oct 17, 2026
…ment

* origin/main:
  build(package): stop shipping Swift test sources (#3785)
  fix(scroll): take edge hints from a same-rect scrollable ancestor (#3784)
  docs(agents): write agent docs in ASD-STE100 and remove misleading docs (#3723)
  fix(daemon): retain cleanup locks and preserve healed snapshots (#3760)

# Conflicts:
#	packages/daemon/src/sessions/__tests__/replay-repair/session-replay-repair-record-exclusion.test.ts
thymikee added a commit that referenced this pull request Oct 17, 2026
…refactor/daemon-runtime-no-process-globals

* origin/refactor/host-environment:
  build(package): stop shipping Swift test sources (#3785)
  fix(scroll): take edge hints from a same-rect scrollable ancestor (#3784)
  docs(agents): write agent docs in ASD-STE100 and remove misleading docs (#3723)
  fix(daemon): retain cleanup locks and preserve healed snapshots (#3760)
  test: say which package still reads PATH from the process in the recording scenario
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.

scroll top reaches the top of Android Settings, then fails with scroll_edge_no_progress

2 participants