fix(mobile): Android feed stays at its end after the keyboard or sidebar changes#33611

merged2
main
t3code/android-feed-end
opened12h agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 8h ago

Stacked on #33599 (t3code/android-workspace-sequencing), so one build tests both.

Problem

On Android, start at the true end of a long thread. Focusing the composer, or toggling the sidebar once, leaves the feed short of the end: the last reply and its timestamp footer stay under the composer, even after the keyboard closes. Opening a thread also landed short. iOS was fine.

Cause

ThreadFeed passed the composer height as a static contentInset on Android only (added in #5585 to seed the first open). Under Legend List 3.7.2 that prop now works against us twice:

  • Legend's core end math (getContentInsetEnd) ignores props.contentInset off iOS, and RN's Android ScrollView has no contentInset.
  • KeyboardAwareLegendList subtracts the static contentInset.bottom from every padding report from KeyboardChatScrollView on non-iOS.

So Legend was told the composer padding was 92 − 92 = 0. The native scroll view still pads by the composer height, which puts the true end one composer height past where Legend thinks it is. Every end target came out short by that amount: initial open, keyboard settle, and the width-change end hold from #33459. Instrumented emulator logs before the fix: Legend's end was 2877, the native end reached by swiping was 2969, and the reported inset was 0. After the fix both are 2969, with an inset of 92.

Change

Drop the Android-only static inset (deriveThreadFeedInitialContentInset and its test). The list already reports the live composer inset when it attaches (attachList), which covers the first open and remounts. iOS is unchanged: the helper always returned undefined there.

Verification

Local Pixel_10_Pro_Fold emulator (unfolded, Gboard), dev build, isolated backend, seeded replay thread. The same script ran before (#33599 head c82a436) and after. Frames left to right: true end, keyboard open, keyboard closed, true end again, sidebar collapsed, sidebar expanded.

Before: after the keyboard opens or the sidebar changes, step 24's reply and its footer stay hidden under the composer.

before

After: the last reply and its footer stay visible in every state.

after

Recordings (1.5×, feed column only):

There's no test. The bug is in how two Legend modules disagree about a prop, so the only test available would mirror mock calls; the recordings are the proof. The iOS code path doesn't change. vp run check:changed passes.

Follow-up: dismissing the keyboard by tapping message text

Problem. Testing on the Fold: with the keyboard open at the end of a thread, tapping the empty area to dismiss kept the end, but tapping a message's text left the feed scrolled up.

Cause. Message text is a natively selectable TextView. A tap on it moves focus from the composer to that TextView, and React Native's Android ScrollView scrolls a newly focused child into view (requestChildFocus → scrollToChild). For a reply taller than the viewport, that's a large native scroll with no drag. Logs on the emulator: the offset jumped 833 → 376 right after the tap, before any inset change, and Legend's updateScroll treated it as a user scroll (interruptMaintainScrollAtEnd). End following was therefore off when the keyboard inset shrank. Taps on empty space take no focus, so they kept the end. Both paths run the same dismiss code (onTouchEnd → blur()), and neither starts a drag, which rules out the other two hypotheses. A short reply that's fully on screen doesn't need a scroll, so it only shows with replies taller than the visible area.

Change. scrollsChildToFocus={false} on the feed list. It's an Android-only ScrollView prop, so iOS is unchanged. Long-press text selection still works.

Verification. Same emulator, a seeded thread whose last reply is taller than the viewport, and a keyboard-open tap on the reply's text or on the blank footer row. Frames: text tap with keyboard open, after; blank tap with keyboard open, after.

Before (eaea60b): the text tap leaves the feed on Paragraphs 5–11, while the blank tap keeps Paragraph 14 and its footer.

before

After (8faf945): both taps keep Paragraph 14 and its footer above the composer.

after

Long-press selection still works after the change:

selection still works

Stack update: tested on the Fold

Rebased onto #33599's new head (5965c6c), where the sidebar stays put while the keyboard is up and the thread header keeps its height while typing. That removes the mid-history reflow reported on the Fold: the pane width no longer changes with the keyboard, so rows don't re-wrap when the keyboard opens or closes. The two commits here are unchanged.

Full stack on the physical Galaxy Z Fold (dev client, seeded thread whose last reply is taller than the viewport, sidebar titles blurred). Frames: at the end; keyboard open; keyboard closed; text-tap dismiss; sidebar collapsed; sidebar expanded. Each one keeps the last paragraph and its footer above the composer. With the keyboard up, the sidebar stays.

from the end on the Fold

Mid-history, pressing the feed with the keyboard open or closed didn't move it in any run (offset stable within a few px, no end-following interrupt). The keyboard-open mid-history case is in #33599.

🤖 Generated with Claude Code

Checks

1 failing · 23 successful · 16 skipped
Some checks failed

Failing1

fd7e211
Synced from GitHub

Approvability

Verdict: Approved at fd7e211

Macroscope's review found this PR approvable — This adjusts Android thread-feed inset and focus scrolling to preserve end-following when the keyboard or sidebar changes. Its effects are limited to feed presentation, with no persistent-data or access changes.

Notes:

  • Diff unchanged. Approvability was decided on eligibility alone.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

🚀 Expo continuous deployment is ready!

  • Project → t3-code
  • Platforms → android, ios
  • Scheme → t3code-preview
 🤖 Android🍎 iOS
Fingerprint9fd8656574d836abb66594826ddf766639eff49cbfb4edba8f9a306e923f1329a6994f1c94bd42cb
Build DetailsBuild Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: 9fd8656574d836abb66594826ddf766639eff49c
App version: 2.2.0
Git commit: 6ec4424ae511af7e385772bafda78b0e7da9139b
Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: bfb4edba8f9a306e923f1329a6994f1c94bd42cb
App version: 2.2.0
Git commit: 9c5fd30e0fc4a3fd05519e5c8612fe560dab77bd
Update DetailsUpdate Permalink
DetailsBranch: pr-33611
Runtime version: 9fd8656574d836abb66594826ddf766639eff49c
Git commit: 6ec4424ae511af7e385772bafda78b0e7da9139b
Update Permalink
DetailsBranch: pr-33611
Runtime version: bfb4edba8f9a306e923f1329a6994f1c94bd42cb
Git commit: 6ec4424ae511af7e385772bafda78b0e7da9139b
Update QRExternal imageExternal image

Added 2 commits

  • fix(mobile): Android feed stays at its end after the keyboard or sidebar changesadc45e6
  • fix(mobile): tapping Android message text to close the keyboard keeps the feed endfd7e211
Synced from GitHub

Explain Change →

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts