Skip to content

fix(mobile): keep the iOS composer scroll position on refocus - #22021

Open
vdoshi96 wants to merge 1 commit into
pingdotgg:mainfrom
vdoshi96:fix/ios-composer-scroll-position
Open

vdoshi96 wants to merge 1 commit into
pingdotgg:mainfrom
vdoshi96:fix/ios-composer-scroll-position

Conversation

@vdoshi96

Copy link
Copy Markdown
Contributor

Problem

A long iOS thread draft can reopen at its first line after the editor loses focus, leaving the caret off-screen. Rebuilding the attributed document for programmatic text updates also fails to explicitly keep the new caret position visible.

Change

Preserve the native editor's scroll offset before disabling scrolling or rebuilding its attributed text, then restore and clamp it after layout. Reveal the caret rectangle after expansion, text edits, and explicit selection changes. Unchanged controlled documents and typography-only rebuilds preserve manual scrolling.

The production change adds 43 lines in T3ComposerEditorView.swift. The accompanying UIKit regression runner compiles the production editor with stubbed Expo module plumbing, following the existing native Mac Catalyst test approach.

Scope and approval

Fixes #22006.

This qualifies as a focused fix of an obvious bug: native scroll state is lost during resize and the editor does not maintain caret visibility. Composer heights, line counts, focus rules, and collapse behavior stay unchanged. Sizing and fullscreen editing remain out of scope per #21126. The change stays local to the iOS view to minimize overlap with #20812.

Android was inspected and left unchanged: its setScrollEnabled only toggles scrollbar visibility, so it does not share the iOS scrolling-disable root cause. Web, desktop, providers, contracts, and connection behavior are unaffected.

Verification

On macOS arm64 with Node 24 and Xcode 27.0:

  • vp i — completed successfully; no dependency or lockfile changes.
  • vp run --filter @t3tools/mobile typecheck — passed.
  • vp test run apps/mobile/src/native/composerEditorRevision.test.ts apps/mobile/src/features/voice-input/voiceInputSession.test.ts apps/mobile/src/features/threads/use-composer-command-menu.test.ts — 52 tests passed across three files.
  • apps/mobile/modules/t3-composer-editor/tests/run-ios.sh — 26/26 UIKit checks passed. The same runner against the original editor from 48b486bd73855c109735e84337735658700b0599 fails 12 assertions (14/26 pass). Checks cover both resize/prop orders, offset restoration, controlled and native insertion, explicit caret moves, unchanged documents, typography rebuilds, collapsed updates, and clearing the draft.
  • vp lint apps/mobile/src/features/threads/ThreadComposer.tsx apps/mobile/src/native/T3ComposerEditor.ios.tsx — exited 0, with 11 existing React warnings in these unchanged files.
  • vp run lint:mobile — SwiftLint, ktlint, and detekt passed.
  • swiftlint lint --strict --config apps/mobile/.swiftlint.yml apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift apps/mobile/modules/t3-composer-editor/tests/ios/ExpoModulesCore.swift apps/mobile/modules/t3-composer-editor/tests/ios/main.swift — zero violations.
  • bash -n apps/mobile/modules/t3-composer-editor/tests/run-ios.sh and git diff --check — passed.

Simulator evidence: iPhone 17 Pro, iOS 27.0. A standalone native harness compiles the production editor unchanged, with the Expo view, event dispatchers, and unused clipboard helpers stubbed. It drives a 40-line draft through focus, scroll to the end, blur/collapse (36 pt, scrolling disabled), refocus/expand (160 pt, scrolling enabled), controlled insertion, and native insertText.

On the baseline, refocus resets offset 640 → 0; the caret at y=779 is off-screen. With this fix, refocus retains offset 640 and the caret is visible. Controlled insertion and native typing at the end remain visible afterward, at offset 720. Those insertions also settle visibly on the baseline in this focused simulator harness; the immediate programmatic-update failure is demonstrated by the Catalyst regression checks.

Before refocus:

Baseline native editor after refocus: first lines visible, end caret off-screen

After refocus:

Fixed native editor after refocus: last lines and end caret remain visible

Before recording · After recording · After insertion screenshot · After typing screenshot

Baseline measurements · Fixed measurements · Baseline regression log · Fixed regression log · Harness source · Harness runner

Evidence and harness files live only on the fork's separate orphan branch pr-assets/ios-composer-scroll-22006; no PR-only assets are included in this diff.

Limits: full Expo/React Native app verification is incomplete. node scripts/mobile-native-client.ts ensure ios 4722D760-7249-4D16-B3F6-7F64939CB126 completed prebuild but stopped at pod install with spawn pod ENOENT because CocoaPods is missing. T3's Device panel also returned “Agent device access is turned off for this environment.” The captures show native focus/blur and resize; the software keyboard is not displayed. Actual dictation, React Native prop scheduling, physical-device behavior, and the issue reporter's iOS 27.2 beta were not verified.

Implemented with GPT-6.1-Sol (Codex) in T3 Code, orchestrated by Claude Opus 5.5 (Claude Code).
🤖 Generated with Claude Code

Restore the native editor offset after expansion and keep the caret visible
following text and selection changes. Preserve manual scrolling on unchanged
documents and typography rebuilds. Add focused UIKit regression checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 19, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 2494c78

Macroscope's review found this PR approvable — This is a focused iOS composer bug fix that preserves scroll position and caret visibility during existing resize, focus, and text-update flows. Runtime changes are confined to the existing native editor, while the other three files add only a local regression harness; no defaults or static-analysis suppressions are changed.

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

@coderabbitai

coderabbitai Bot commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c614c7fc-da54-418f-8aff-76e800cb29ea
📥 Commits

Reviewing files that changed from the base of the PR and between 012e4ec and 2494c78.

📒 Files selected for processing: 4
  • apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift
  • apps/mobile/modules/t3-composer-editor/tests/ios/ExpoModulesCore.swift
  • apps/mobile/modules/t3-composer-editor/tests/ios/main.swift
  • apps/mobile/modules/t3-composer-editor/tests/run-ios.sh

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

The iOS composer preserves its text-view scroll offset when scrolling is toggled or controlled content is rebuilt. It also schedules caret visibility handling after layout and editor updates. New iOS tests cover these behaviors and related controlled-document changes.

Changes: Composer scroll and caret behavior

Layer / File(s) Summary
Offset restoration and caret reveal
apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift
The editor preserves the offset when scrolling is disabled and restores it within valid content bounds after layout. It schedules caret reveal after selection, content, editing, and scrolling updates.
iOS behavior test harness
apps/mobile/modules/t3-composer-editor/tests/ios/*, apps/mobile/modules/t3-composer-editor/tests/run-ios.sh
The new harness tests offset restoration, caret visibility, controlled updates, selection retention, typography rebuilds, insertion, and draft clearing. The runner builds and executes the tests.

Priority: ➖ Normal

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

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to 2494c

The iOS composer change is ready to merge after normal checks; no actionable scroll-position or caret-visibility issue was identified.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: preserving the iOS composer scroll position when the editor regains focus.
Description check Passed The description includes all required sections. It explains the problem, change, scope and approval basis, focused verification results, visual evidence, limitations, and tool attribution. It also ide…
Linked Issues check Passed The PR addresses the active direct issue #22006. T3ComposerEditorView.swift saves the UITextView content offset before scrolling is disabled or controlled text is rebuilt, restores a clamped offse…
Out of Scope Changes check Passed The production change is limited to the iOS composer view and implements the scroll and caret behavior in #22006. The added Expo stub, UIKit harness, and runner directly test that behavior. No unrelat…
Full details: Description check

Explanation

The description includes all required sections. It explains the problem, change, scope and approval basis, focused verification results, visual evidence, limitations, and tool attribution. It also identifies incomplete full-app verification and its cause.

Full details: Linked Issues check

Explanation

The PR addresses the active direct issue #22006. T3ComposerEditorView.swift saves the UITextView content offset before scrolling is disabled or controlled text is rebuilt, restores a clamped offset after layout, and schedules caret visibility after expansion, controlled updates, native text changes, and explicit selection changes. The added UIKit runner covers refocus offset restoration, controlled insertion, native insertion, collapsed updates, clearing, unchanged documents, and typography rebuilds. The reported 26/26 checks passed.

Full details: Out of Scope Changes check

Explanation

The production change is limited to the iOS composer view and implements the scroll and caret behavior in #22006. The added Expo stub, UIKit harness, and runner directly test that behavior. No unrelated Android, web, desktop, provider, contract, connection, sizing, focus-rule, or collapse-behavior changes are present in the reviewed change summary and focused diff.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@shivamhwp

Copy link
Copy Markdown
Collaborator

Thanks for this fix. #23096 includes your commit unchanged, with you as the author. It adds one more change on top: on the simulator, the tap that reopens the collapsed composer still moved the caret to the first line, so the view went back to the top even with the offset restored. #23096 keeps the caret where you left it for long drafts, and has before/after recordings from the app.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: iOS composer loses its scroll position and jumps to the first line when the keyboard hides or text is dictated

2 participants