Skip to content

fix(macos-helper): scroll SwiftUI forms whose rows are scroll-area children - #3888

Merged
thymikee merged 2 commits into
callstack:mainfrom
janicduplessis:fix/macos-scroll-form-overflow
Oct 22, 2026
Merged

thymikee merged 2 commits into
callstack:mainfrom
janicduplessis:fix/macos-scroll-form-overflow

Conversation

@janicduplessis

@janicduplessis janicduplessis commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

With AGENT_DEVICE_MACOS_APP_BACKEND=native, every scroll in a SwiftUI Form (such as a Settings window) failed with no scroll area with a settable scroll bar under the window center.

The helper sized the content from the largest child of the AXScrollArea, but a Form lists each heading and row group as a direct child of the area. No child was taller than the area, so the overflow came out negative and the area was skipped. The overflow now comes from the span all non-scroll-bar children cover. One document view gives the same value as before.

Known limits:

  • A child far outside the content on the scroll axis, such as a detached header overlay, widens the span, so each step travels less. Filtering by position would also drop rows scrolled out of view. A test pins the value.
  • Lazily realized children (SwiftUI List and LazyVStack) only expose on-screen rows, so overflow can be underestimated. That is pre-existing, and not made worse by this PR.

2 files. Related: #3213.

Validation

Head 0878411bd, base c8b944ca0.

  • pnpm check:affected --base upstream/main --head 0878411bd --run: passed, exactCommit: true; selected checks are GitHub-authoritative (Apple / Smoke Tests).
  • swift test in apple/macos-helper: 20 passed.
  • Live at 50acc2d96 (head adds only a test and comment trims), macOS 27, Stim Desktop's Settings window, native backend: base refuses scroll down, bottom, --pixels and --until; head passes all, and --until reaches an off-screen switch that a press then toggles.

Top of the form, then after scroll --until and the press:

View guided diff

…ildren

A SwiftUI Form exposes each row and heading as a direct child of its AXScrollArea, with no single document view as tall as the content. The scroll-bar path sized the overflow from the largest child, found none taller than the area, and refused with no-scroll-bar. Size the overflow from the span all content children cover.
…nt adds

Trims the comments the change added to the comment rule.
@janicduplessis
janicduplessis marked this pull request as ready for review October 22, 2026 01:20
Copilot AI balanced review requested due to automatic review settings October 22, 2026 01:20

Copilot AI 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.

🔵 Needs a closer look

Smoke tests, coverage, and repository guards are still running, so validation remains incomplete.

No open issues 🎉

What changed in this PR

Fixes scrolling in SwiftUI forms under the opt-in native macOS backend by measuring content across all scroll-area children rather than selecting the largest child.

Changes:

  • Calculate overflow from the combined span of non-scroll-bar children.
  • Add regression tests for multiple rows, distant children, and empty frames.
File Description
apple/​macos-helper/​Tests/​AgentDeviceMacOSHelperTests/​BackgroundInteractionTests.swift Tests content-span calculations and boundary cases.
apple/​macos-helper/​Sources/​AgentDeviceMacOSHelper/​BackgroundInteraction.swift Uses combined child bounds to calculate scroll overflow.

Review effort: Balanced

@thymikee

Copy link
Copy Markdown
Member

This PR is ready for review at 0878411. I found no problems in the code, and CI is green: 14 checks reported and none failed. The diff only touches the macOS helper scroll-bar route, which the Apple and Smoke Tests jobs exercise. There are no conflicts.

Not blocking, and you can take or leave these: the test testScrollOverflowCountsAChildFarOutsideTheContent at https://github.com/callstack/agent-device/blob/0878411/apple/macos-helper/Tests/AgentDeviceMacOSHelperTests/BackgroundInteractionTests.swift#L93 asserts 5680, the inflated overflow from a detached header, so it pins a known limit as expected behavior. You could rename it to say so, or drop it once outlier filtering lands. The doc comment at https://github.com/callstack/agent-device/blob/0878411/apple/macos-helper/Sources/AgentDeviceMacOSHelper/BackgroundInteraction.swift#L262 could add one clause: lazily realized rows make the span a lower bound.

On evidence, the live Settings Form run was at 50acc2d, not at this commit. Since then the source only changed a doc comment, so that run still covers the scroll change. I did not run swift test or inspect a live AX tree. The lazy-row limit for SwiftUI List and LazyVStack was already there and is stated in the PR body. I did not test it.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 22, 2026
@thymikee
thymikee merged commit 1981483 into callstack:main Oct 22, 2026
14 checks passed
thymikee added a commit that referenced this pull request Oct 22, 2026
* origin/main:
  feat(test-app): add a Bluesky-depth heavy feed screen (#3884)
  fix(android): read a child that vanished mid-read as gone (#3901)
  feat(ios): route iPad Simulator snapshots through the host AX bridge (#3895)
  Prepare shared resource ownership and selection for #3325 (#3887)
  refactor(kernel): track held execution locks in keyed-lock (#3879)
  perf(ios): ask a ready runner about surface hosts before spawning probes (#3877)
  fix(ios): keep the bridge while a surface host only lingers (#3873)
  perf(ios): synthesize taps on resolved in-app back controls (#3880)
  fix(macos): open a running app by its own path instead of its bundle id (#3889)
  fix(macos): accept mixed-case bundle ids in open (#3867)
  fix(macos-helper): scroll SwiftUI forms whose rows are scroll-area children (#3888)
thymikee added a commit that referenced this pull request Oct 22, 2026
* origin/main:
  fix(android): read a child that vanished mid-read as gone (#3901)
  feat(ios): route iPad Simulator snapshots through the host AX bridge (#3895)
  Prepare shared resource ownership and selection for #3325 (#3887)
  refactor(kernel): track held execution locks in keyed-lock (#3879)
  perf(ios): ask a ready runner about surface hosts before spawning probes (#3877)
  fix(ios): keep the bridge while a surface host only lingers (#3873)
  perf(ios): synthesize taps on resolved in-app back controls (#3880)
  fix(macos): open a running app by its own path instead of its bundle id (#3889)
  fix(macos): accept mixed-case bundle ids in open (#3867)
  fix(macos-helper): scroll SwiftUI forms whose rows are scroll-area children (#3888)
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.

3 participants