Skip to content

fix(mobile): stop long iOS threads from jumping while scrolling up - #4867

Merged
t3dotgg merged 2 commits into
mainfrom
t3code/fix-ios-thread-scrolling
Jul 30, 2026
Merged

fix(mobile): stop long iOS threads from jumping while scrolling up#4867
t3dotgg merged 2 commits into
mainfrom
t3code/fix-ios-thread-scrolling

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Jul 29, 2026

Copy link
Copy Markdown
Member

Scrolling up through a long thread on iOS was jumpy to the point of feeling broken: content juddered as you dragged through history, and the list could yank you back to the bottom seconds after opening a thread.

Both problems live in @legendapp/list 3.2.0's handling of estimated vs. measured row sizes. Our rows are estimated at 180px but really measure anywhere from 49px (turn folds) to 4000px+ (long markdown messages), so:

  1. As each history row mounted during scroll-up, 3.2.0 issued an individual scroll compensation per row — traced bursts of ~10 adjusts in 20ms with deltas like +3974/-131, each racing the native momentum scroll. That's the judder.
  2. The initial scroll-to-end never registered as finished (content size kept changing as rows measured), so its retry loop kept firing seconds later — traced it teleporting the viewport 7,600px back to the end mid-drag.

Fix: upgrade mobile to @legendapp/list 3.3.3, which batches row measurements into a single layout pass and fixes the stale end-scroll retries, and rebase our transparent-header inset patch onto it. 19 of 20 hunks applied cleanly; the one reject (onScrollBeginDrag forwarding) exists natively in 3.3.3, so only the didUserDrag flag was folded into its handler. Web keeps its own unpatched 3.2.0.

Verified in the iOS simulator against a clone of real thread data (119-message thread): scroll offset traces went from ±7,600px mid-drag reversals to zero discontinuities, and the judder is gone. Mobile thread tests pass; mobile and web typecheck clean.


Change authored by Claude Fable 5 via Claude Code.

🤖 Generated with Claude Code


Note

Medium Risk
Touches core list scrolling, initial positioning, and MVCP compensation in a patched dependency used for thread UI; regressions could affect scroll position, keyboard insets, and header blur on iOS, though scope is mobile-only and web keeps 3.2.0.

Overview
Upgrades mobile @legendapp/list from 3.2.0 to 3.3.3 and retargets the pnpm patch to patches/@legendapp__list@3.3.3.patch, addressing iOS thread scroll judder and late snap-to-bottom on long, variably-sized message rows.

The rebased patch layers app-specific behavior on 3.3.3: contentInsetStartAdjustment for transparent-header / automatic UIKit insets, keyboard KeyboardAwareLegendList inset compensation props, contentInsetEndAdjustment on native (not web-only), an inset-end settle watchdog that holds first paint until scroll-to-end stabilizes, safer maintain-at-end when tail sizes are unknown, and Reanimated layout transitions gated off during MVCP adjustments and active scrolling. onScrollBeginDrag now sets didUserDrag for the watchdog. Lockfile/workspace still wire web to unpatched 3.2.0 while mobile uses patched 3.3.3.

Reviewed by Cursor Bugbot for commit 7a92d6b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix iOS thread list jumping while scrolling up by upgrading @legendapp/list

Upgrades @legendapp/list from 3.2.0 to 3.3.3 and updates the workspace patch to match. The new version fixes scroll position jumps in long threads by suppressing layout transitions in ReanimatedPositionView during MVCP adjustments and native scroll activity.

  • Adds onScrollBeginDrag handler to track user drag state and prepare edge handling
  • Defers setInitialRenderState readiness when insetEndRevealHold is active
  • Defers maintain-scroll-at-end logic when anchor item sizes are unknown
  • Records lastMVCPAdjustTime to coordinate transition suppression in animated rows
  • Behavioral Change: initial render is now gated on insetEndRevealHold state, which may delay first paint in some scroll configurations

Macroscope summarized 7a92d6b.

Upgrade @legendapp/list 3.2.0 -> 3.3.3 and rebase the transparent-header
inset patch onto it. 3.2.0 applied a separate scroll compensation for
every history row whose measured size differed from the estimate (judder
while scrolling up), and its unfinished scroll-to-end retry loop could
yank the viewport back to the bottom seconds after opening a thread.
3.3.3 batches row measurements into one layout pass and fixes the stale
end-scroll retries. Only mobile moves; web keeps its own unpatched 3.2.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 383288d0-18a0-4b43-9f8e-b3661ac3c839

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Jul 29, 2026
Comment thread patches/@legendapp__list@3.3.3.patch
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 29, 2026
@macroscopeapp

macroscopeapp Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR updates a third-party library patch to fix iOS scrolling behavior, but an unresolved review comment identifies that lastNativeScrollTime is checked but never assigned, meaning part of the animation-suppression logic may not function as intended.

You can customize Macroscope's approvability policy. Learn more.

The itemLayoutAnimation exists to smooth at-rest layout shifts (streaming
text, work-log folds), but it also animated repositions that happen while
the user scrolls history: MVCP compensation shifts (already offset by an
equal contentOffset change, so animating turns an invisible correction
into a lurch-and-settle) and container reuse for far-away rows (animating
the container's jump from the old row's position sends content flying
across the viewport). Gate the transition off during MVCP adjustments,
active scrolling, and container reassignment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@macroscopeapp
macroscopeapp Bot dismissed their stale review July 30, 2026 09:39

Dismissing prior approval to re-evaluate 7a92d6b

@t3dotgg

t3dotgg commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

[claude-fable-5] RESPONDING ON BEHALF OF THEO

Theo reported jank persisted on this branch (his recording). Root-caused and pushed a second commit (7a92d6b).

The remaining jank was not the library scroll math — it was our own itemLayoutAnimation (LinearTransition.duration(180)) animating repositions it should never touch. Two cases, confirmed by correlating per-frame scroll traces with instrumented requestAdjust calls in the simulator:

  1. MVCP compensation shifts. When a history row measures different from its estimate, the list shifts row positions and compensates with an equal contentOffset change — net visual movement should be zero. The layout transition animated the reposition half, so every estimated-to-measured correction became a visible lurch-and-settle (traced deltas up to ±1100px mid-drag).
  2. Container reuse. Containers get reassigned to far-away rows during fast scrolls even with recycleItems off. Animating the container tween from the old row's position sends content sliding across the viewport.

Fix extends the patch: the transition is skipped during MVCP adjustments, active scrolling (via the core's existing lastNativeScrollTime), and any container reassignment. At-rest shifts (streaming text, fold toggles) still animate.

Verified in the sim on the same 119-message thread with the same flick pattern: frame-level analysis went from 6 lurch events per pass to 0 after the fix. 📹 after recording

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7a92d6b. Configure here.

Comment thread patches/@legendapp__list@3.3.3.patch
@t3dotgg
t3dotgg merged commit 90f3913 into main Jul 30, 2026
16 checks passed
@t3dotgg
t3dotgg deleted the t3code/fix-ios-thread-scrolling branch July 30, 2026 10:05
tarik02 added a commit to tarik02-org/t3code that referenced this pull request Jul 30, 2026
* fix(desktop): restore T3 Connect sign-in (pingdotgg#4809)

Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* Simplify files panel header (pingdotgg#4828)

* build(desktop): reduce installed app size by ~300MB (pingdotgg#4824)

* Update model version from claude-opus-4-8 to claude-opus-5 (pingdotgg#4832)

* Preserve the thread shell while detail loads (pingdotgg#4830)

* Reduce idle work and disk churn with native resource diagnostics (pingdotgg#2679)

Co-authored-by: codex <codex@users.noreply.github.com>

* fix(server): detect repositories after initialization (pingdotgg#4848)

* perf(server): merge separate staged/unstaged numstat calls into single diff HEAD --numstat (pingdotgg#4843)

* fix(git): disable external diff for review diff previews (pingdotgg#4854)

* Fix editable file focus and live syntax highlighting (pingdotgg#3979)

* fix(web): remember the rendered-markdown choice across threads (pingdotgg#4853)

Co-authored-by: Simon Doba <simon.doba@orbit.de>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* chore(release): prepare v0.0.31

* fix(mobile): reduce thread feed scroll jank (pingdotgg#4874)

* fix(web): restore sidebar v2 thread actions and terminal icon (pingdotgg#4712)

* fix(web): settle button now works on hover, not just right-click (pingdotgg#4905)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(clients): disable add project while disconnected (pingdotgg#4834)

* fix(composer): hide default Codex service tier (pingdotgg#4784)

* docs: link iOS and Android app store downloads (pingdotgg#4902)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(web): align remote server update action (pingdotgg#4731)

* fix(connect): suggest a serve command that matches how you ran connect (pingdotgg#4897)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(mobile): stop shared content errors in Personal Team builds (pingdotgg#4943)

* perf(mobile): sends respond instantly, thread opens stop freezing (pingdotgg#4882)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(web): show Codex fast mode as a bolt (pingdotgg#4947)

* docs: seed worktrees with a copy of real userdata instead of banning it (pingdotgg#4949)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(mobile): support dragged images in the composer (pingdotgg#4953)

* fix(mobile): stop long iOS threads from jumping while scrolling up (pingdotgg#4867)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(web): keep worktree default when switching a draft's machine (pingdotgg#4964)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* perf(mobile): reconnect environments immediately on resume (pingdotgg#4878)

* feat(web): pasting a huge screenshot now compresses it instead of erroring (pingdotgg#4967)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(web): regenerate thread titles from sidebar (pingdotgg#4810)

* fix(web): show server update progress through reconnect (pingdotgg#4903)

---------

Co-authored-by: Alex <me@pixp.cc>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: wukko <me@wukko.me>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com>
Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
Co-authored-by: ohbentos <72638975+ohbentos@users.noreply.github.com>
Co-authored-by: Jake Leventhal <jakeleventhal@me.com>
Co-authored-by: Simon Doba <simon.doba@hotmail.de>
Co-authored-by: Simon Doba <simon.doba@orbit.de>
Co-authored-by: t3-code[bot] <269035359+t3-code[bot]@users.noreply.github.com>
Co-authored-by: Gabriel De Andrade <30420087+gabrielelpidio@users.noreply.github.com>
Co-authored-by: Jono Kemball <Noojuno@users.noreply.github.com>
Co-authored-by: Theo Browne <me@t3.gg>
Co-authored-by: Max Katz <me@maxkatz.me>
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Jul 30, 2026
## What's Changed
* fix(mobile): support dragged images in the composer by @t3dotgg in pingdotgg/t3code#4953
* fix(mobile): stop long iOS threads from jumping while scrolling up by @t3dotgg in pingdotgg/t3code#4867
* fix(web): keep worktree default when switching a draft's machine by @t3dotgg in pingdotgg/t3code#4964
* perf(mobile): reconnect environments immediately on resume by @t3dotgg in pingdotgg/t3code#4878
* feat(web): pasting a huge screenshot now compresses it instead of erroring by @t3dotgg in pingdotgg/t3code#4967
* feat(web): regenerate thread titles from sidebar by @t3dotgg in pingdotgg/t3code#4810
* fix(web): show server update progress through reconnect by @t3dotgg in pingdotgg/t3code#4903
* feat(search): find threads by conversation content by @t3dotgg in pingdotgg/t3code#4959
* fix: marketing site Vercel builds no longer die after ~100 deploys by @t3dotgg in pingdotgg/t3code#4975


**Full Changelog**: pingdotgg/t3code@v0.0.32-nightly.20260730.955...v0.0.32-nightly.20260730.956

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.32-nightly.20260730.956
patroza added a commit to patroza/t3code that referenced this pull request Jul 30, 2026
…ng up (pingdotgg#4867)" (#235)

This reverts commit 90f3913.

Co-authored-by: T3 Code PR Stack <41898282+github-actions[bot]@users.noreply.github.com>
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 31, 2026
Refresh against the approved codex-turn-mapping tip. CTM's LegendList 3.3.3
patch threads contentInsetStartAdjustment through the list's own scroll math,
and pingdotgg#4867/pingdotgg#4874 carry the gated feed-item layout transition, so this merge
keeps CTM's versions of both. The keyboard-side wiring is not upstream: the
3.3.3 patch never passes contentInsetStartCompensation into
KeyboardChatScrollView, whose clamp floor defaults to 0, so the pingdotgg#4036 wiring
is re-ported onto the 3.3.3 patch. The pingdotgg#4036-unique short-content underflow
correction, Markdown rendering, and thread-detail changes are retained.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant