Skip to content

iOS terminal: keep live tail pinned through keyboard/font layout resizes - #700

Merged
arul28 merged 3 commits into
mainfrom
ade/ios-terminal-keyboard-pinning-d6617e83
Jul 4, 2026
Merged

arul28 merged 3 commits into
mainfrom
ade/ios-terminal-keyboard-pinning-d6617e83

Conversation

@arul28

@arul28 arul28 commented Jul 4, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes the user-reported iOS bug where tapping to type in a CLI session terminal raised the keyboard but the terminal content did not follow — in sessions with large scrollback (e.g. long-running Claude Code sessions) the prompt/input line stayed hidden behind the keyboard.

Root cause. When the keyboard shrinks the SwiftTerm view, SwiftTerm only re-snaps the viewport to the bottom when the resize changes cols/rows (partly via a deferred main-queue hop), and a mouse-mode TUI like Claude Code repaints in place afterward — no scroll events ever arrive to self-heal a stranded offset. With isPinnedToBottom still true there was not even a "↓ Live" pill to escape with. Short transcripts survived because the content fits the shrunken viewport anyway.

Fix (surgical, no pinning redesign):

  • On any layout-driven bounds-size change while pinned, re-assert the live tail (resumeAutoScroll) one runloop later — after SwiftTerm's own deferred scroller sync. Same re-assert after pinch-zoom font changes (content height changes without a bounds change).
  • scrollViewDidScroll now gates on isDragging instead of isTracking, so a programmatic bottom snap landing during a stationary tap-to-focus can't be misread as a user scroll-up.
  • Extracted the pure isAtLiveTail predicate (named liveTailSlack constant) used by updatePinState, with regression tests anchoring the keyboard-shrink geometry flip.

Real scroll-up behavior is unchanged: dragging up during output still un-pins and shows the "↓ Live N" pill; tapping it returns to the tail; readers who scrolled up keep their spot through keyboard transitions.

Validation

  • Direct xcodebuild Debug build: BUILD SUCCEEDED
  • ADETests/TerminalLiveTailPinningTests (3 tests): pass on iPhone 17 Pro simulator
  • /quality dual-review: no Blocker/High/Medium; both tracks traced pill flow, history paging, and layout-loop safety
  • docs: ios-companion.md terminal-viewer prose updated with the new invariant

🤖 Generated with Claude Code

Greptile Summary

This PR keeps the iOS terminal pinned to the live tail during keyboard and layout changes. The main changes are:

  • Reasserts SwiftTerm auto-scroll after layout size and font metric changes when the viewport is already pinned.
  • Updates scroll-state tracking so only real drags and deceleration can unpin the terminal.
  • Adds regression tests for the live-tail predicate and keyboard-shrink behavior.
  • Documents the terminal keyboard pinning behavior in the iOS companion guide.

Confidence Score: 5/5

Safe to merge with low risk.

The changes are narrowly scoped to terminal scroll-pinning behavior and include regression coverage for the key keyboard-shrink case.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Observed the terminal live-tail tooling availability check and recorded exit statuses for xcodebuild, swift, and pod as 127.
  • Noted a blocker when attempting the narrow XCTest command, with xcodebuild not found.
  • Reviewed the code evidence showing the changed XCTest methods and related implementation lines relevant to keyboard live-tail pinning.
  • Consolidated the findings into artifacts for reviewer inspection.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
apps/ios/ADE/Views/Work/SwiftTermSessionView.swift Adds live-tail pin reassertion on terminal layout/font changes and limits pin updates to user scroll gestures.
apps/ios/ADETests/ADETests.swift Adds focused XCTest coverage for the extracted live-tail predicate and keyboard-shrink regression behavior.
docs/features/sync-and-multi-device/ios-companion.md Documents the new iOS terminal keyboard and layout pinning behavior.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Layout as SwiftTerm layout/font change
participant Controller as TerminalSessionController
participant SwiftTerm as ADESwiftTermView
participant User as User scroll input

Layout->>Controller: handleTerminalLayout() / handlePinch()
Controller->>Controller: sendResizeIfNeeded(cols, rows)
alt viewport size changed or font metrics changed
    Controller->>Controller: reassertLiveTailIfPinned()
    Controller-->>SwiftTerm: resumeAutoScroll() on next runloop
end
User->>SwiftTerm: drag/decelerate scroll
SwiftTerm->>Controller: scrollViewDidScroll()
Controller->>Controller: updatePinState() only for isDragging/isDecelerating
alt user returns near live tail
    Controller-->>SwiftTerm: allow output auto-scroll
else user scrolls up
    Controller-->>SwiftTerm: hold scroll on output
end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Layout as SwiftTerm layout/font change
participant Controller as TerminalSessionController
participant SwiftTerm as ADESwiftTermView
participant User as User scroll input

Layout->>Controller: handleTerminalLayout() / handlePinch()
Controller->>Controller: sendResizeIfNeeded(cols, rows)
alt viewport size changed or font metrics changed
    Controller->>Controller: reassertLiveTailIfPinned()
    Controller-->>SwiftTerm: resumeAutoScroll() on next runloop
end
User->>SwiftTerm: drag/decelerate scroll
SwiftTerm->>Controller: scrollViewDidScroll()
Controller->>Controller: updatePinState() only for isDragging/isDecelerating
alt user returns near live tail
    Controller-->>SwiftTerm: allow output auto-scroll
else user scrolls up
    Controller-->>SwiftTerm: hold scroll on output
end
Loading

Reviews (1): Last reviewed commit: "docs: note layout-resize live-tail re-as..." | Re-trigger Greptile

arul28 and others added 3 commits July 4, 2026 10:01
A keyboard-driven view shrink stranded the viewport above the live tail
in sessions with large scrollback: SwiftTerm only re-snaps to the bottom
when cols/rows change, and a mouse-mode TUI (Claude Code) repaints in
place afterward, so no scroll event ever self-healed the offset — the
prompt stayed hidden behind the keyboard with no Live pill to escape.

- Re-assert the live tail (resumeAutoScroll) one runloop after any
  layout size change or pinch font change while pinned; users who
  scrolled up keep their spot and the Live pill flow is untouched.
- Guard scrollViewDidScroll on isDragging (not isTracking) so a
  programmatic bottom snap landing during a stationary tap-to-focus
  can't be misread as a user scroll.
- Extract the pure isAtLiveTail predicate + regression tests anchoring
  the keyboard-shrink geometry flip.

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

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Jul 4, 2026 2:08pm

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@arul28, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8e9093d9-732a-4568-9bc7-a6c4cdec8c2b

📥 Commits

Reviewing files that changed from the base of the PR and between 9a70900 and ca3cb1e.

⛔ Files ignored due to path filters (1)
  • docs/features/sync-and-multi-device/ios-companion.md is excluded by !docs/**
📒 Files selected for processing (2)
  • apps/ios/ADE/Views/Work/SwiftTermSessionView.swift
  • apps/ios/ADETests/ADETests.swift
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ade/ios-terminal-keyboard-pinning-d6617e83

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.

@arul28

arul28 commented Jul 4, 2026

Copy link
Copy Markdown
Owner Author

@copilot review but do not make fixes

@arul28 arul28 changed the title Ios Terminal Keyboard Pinning iOS terminal: keep live tail pinned through keyboard/font layout resizes Jul 4, 2026
@arul28
arul28 merged commit 65314db into main Jul 4, 2026
5 of 6 checks passed
@arul28
arul28 deleted the ade/ios-terminal-keyboard-pinning-d6617e83 branch July 4, 2026 14:34
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.

1 participant