Skip to content

fix(ui-ios): pause UI mutations during touch scrolling - #8609

Merged
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/7763-ios-stackview-mutation
Aug 22, 2026
Merged

fix(ui-ios): pause UI mutations during touch scrolling#8609
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/7763-ios-stackview-mutation

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Prevent UIKit from laying out a scrolling UIStackView while Perry is concurrently applying JavaScript-driven label or arranged-subview mutations. The iOS runtime pump now stays quiescent for the full touch drag/deceleration and two pump turns after scrolling settles.

Changes

  • Install a lifecycle-managed UIScrollView delegate when each Perry scroll view is created, while preserving the existing onScrollEnd behavior.
  • Track simultaneous touch-scroll interactions and defer timer, frame, microtask, stdlib, GC, geisterhand, and root-health pump work until UIKit has committed its final scroll layout transaction.
  • Add host-runnable state-machine tests for single and overlapping scroll views.
  • Add a bounded physical-device repro that combines 50 label updates per frame with periodic 100-row stack rebuilds.
  • Replace the removed delegate-map root-holder inventory entry with the new numeric-only mutation gate entry.

Related issue

Fixes #7763

Test plan

  • cargo fmt --all -- --check
  • cargo test -p perry-ui-ios --test scroll_mutation_gate (2 passed)
  • cargo build --release -p perry-runtime-static -p perry-stdlib-static -p perry-ui-ios --target aarch64-apple-ios
  • BASE_SHA=upstream/main SKIP_COMPILE_GATES=1 ./scripts/run_lint_gates.sh (all 53 script gates passed; compile tier covered by the focused iOS build above)
  • Built, signed, installed, and launched the release repro on a physical iPhone 16 Pro.
  • Full host workspace test suite (not run; change is isolated to the iOS-only backend)

Screenshots / output

Unpatched physical-device control aborted immediately after frame 600 with:

NSInvalidArgumentException: -[__NSArrayM insertObject:atIndex:]: object cannot be nil
App terminated due to signal 6.

The patched device run passed frames 600 and 1200 without that exception, and the device produced no new issue_7763_touch_scroll crash report. A final install/launch smoke also succeeded after switching delegate ownership from a leak to an Objective-C association.

I also built an XCUITest rapid-swipe harness, but iOS canceled UI-test initialization at the device-local authentication prompt (LocalAuthentication Code=-2) before it sent any swipes, so that is not counted as a passing automated test.

Checklist

  • I have NOT bumped the workspace version or edited CLAUDE.md / CHANGELOG.md.
  • My commit follows the loose feat: / fix: / docs: / chore: prefix convention.
  • I have read CONTRIBUTING.md and agree to the Code of Conduct.

Summary by CodeRabbit

  • Bug Fixes

    • Improved iOS scroll performance and stability during dragging and deceleration.
    • Prevented UI updates from interrupting scrolling or causing layout failures.
    • Preserved scroll-end callbacks and improved callback validation.
  • Tests

    • Added coverage for single and overlapping scroll interactions, including post-scroll settling periods.
  • Documentation

    • Added a changelog entry describing the iOS scrolling fix.

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b69b7090-e2d4-47e8-8250-f0626ef3e8e8

📥 Commits

Reviewing files that changed from the base of the PR and between 970eab3 and 6a09b2f.

📒 Files selected for processing (8)
  • benchmarks/ios-ui/issue_7763_touch_scroll.ts
  • changelog.d/8609-ios-touch-scroll-mutation.md
  • crates/perry-ui-ios/src/app.rs
  • crates/perry-ui-ios/src/widgets/mod.rs
  • crates/perry-ui-ios/src/widgets/scroll_mutation_gate.rs
  • crates/perry-ui-ios/src/widgets/scrollview.rs
  • crates/perry-ui-ios/tests/scroll_mutation_gate.rs
  • scripts/gc_runtime_root_holders.json

Included review availability: Your plan provides up to 8 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

Adds iOS touch-scroll mutation protection. Scroll delegates gate runtime pumps during dragging, deceleration, and two quiet cycles afterward. Tests cover overlapping scrolls, and a benchmark reproduces label updates and stack rebuilding during touch scrolling.

Changes

iOS touch-scroll mutation handling

Layer / File(s) Summary
Mutation gate state machine
crates/perry-ui-ios/src/widgets/mod.rs, crates/perry-ui-ios/src/widgets/scroll_mutation_gate.rs, crates/perry-ui-ios/tests/scroll_mutation_gate.rs
Adds ScrollMutationGate, which defers pumps during active scrolls and for two quiet pump cycles after scrolling ends. Tests cover active, quiet, and overlapping scrolls.
Scroll delegate integration
crates/perry-ui-ios/src/widgets/scrollview.rs, scripts/gc_runtime_root_holders.json
Tracks dragging and deceleration through the gate. Retains delegates with associated objects and stores scroll handles directly.
Runtime pump wiring and reproduction
crates/perry-ui-ios/src/app.rs, benchmarks/ios-ui/issue_7763_touch_scroll.ts, changelog.d/8609-ios-touch-scroll-mutation.md
Skips runtime callbacks while touch-scroll deferral is active. Adds a 100-row mutation benchmark and documents the fix.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 6a09b

This PR pauses UI mutations during touch scrolling to prevent concurrent UIKit layout failures, with focused tests and device validation included; no actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UIScrollView
  participant ScrollMutationGate
  participant PerryPumpTarget
  User->>UIScrollView: Drag or scroll the list
  UIScrollView->>ScrollMutationGate: Begin or end scroll activity
  PerryPumpTarget->>ScrollMutationGate: Check pump deferral
  ScrollMutationGate-->>PerryPumpTarget: Defer during scrolling and quiet cycles
  PerryPumpTarget-->>PerryPumpTarget: Skip runtime callbacks
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 6 files. (2 skipped: 2 unsupported.) Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: pausing iOS UI mutations during touch scrolling.
Description check ✅ Passed The description follows the required template and documents the change, issue, tests, device validation, and known test limitation.
Linked Issues check ✅ Passed The changes address issue #7763 by deferring iOS runtime mutations during scrolling and validating the fix with focused tests and physical-device reproduction.
Out of Scope Changes check ✅ Passed The changes remain within issue #7763 scope and include only the fix, tests, reproduction, changelog, and related root-holder inventory update.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Merging as a validated pair on current main.

check result
cargo check --workspace --all-targets exit 0
perry-runtime --lib (RUST_TEST_THREADS=1) 2634 passed, 4 ignored
all seven ratchets 0
cargo fmt --all -- --check 0

Ratchets re-run against the current baseline immediately before merge.

These were staged alongside #8610, which is held: it adds +2 bare raw-handle reads in class_registry/construct.rs and +1 open-coded StringHeader payload offset. I confirmed by exclusion that the debt is entirely #8610's — with just these two the same gates report 925/925 and 436 inline offsets, both exit 0 — so neither of these is implicated and there was no reason to hold them behind it.

@proggeramlug
proggeramlug merged commit 084b244 into PerryTS:main Aug 22, 2026
22 of 33 checks passed
@proggeramlug
proggeramlug deleted the fix/7763-ios-stackview-mutation branch August 22, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS: scrolling a UIStackView in a UIScrollView while mutating labels aborts with 'insertObject:atIndex:: object cannot be nil'

1 participant